Curvilinear

unlisted

by Seth Davis

A Linear-flavored issue tracker for your vault. Issues are plain notes; boards are just views.

Updated 1mo agoMIT
View on GitHub

Curvilinear

Track issues and projects as plain Markdown notes, on a board that works the way Linear does. Issues are notes with frontmatter; the board is just a view over them.

Why keep issues in your vault

Issues live next to your thinking. This is the whole idea. An issue can be linked from a meeting note, embedded in a spec, backlinked from research, and show up in your graph. When your tracker and your notes are separate tools, you spend real effort keeping them in step. Here there is one set of files.

The data is yours, permanently. Markdown in a folder. Readable in twenty years, diffable in git, greppable from a terminal. There is no export step because it is already exported.

It works with no account and no network. On a plane, behind a firewall, and after any company changes its pricing or its mind.

It is free and unlimited. No seats, no issue caps, no workspace admin.

You can change it. Want a client field, or grouping by retainer? Parse it in vault-index.ts, render it on the card, add it to the grouping list. That is a small afternoon rather than a feature request into someone else's backlog.

It inherits the rest of Obsidian. Search, graph view, templates, Canvas, Bases, Dataview, and backlinks all work on your issues without anyone building integrations for them.

Where a hosted tracker is the better tool

Curvilinear is single-player by design. There is no real-time collaboration, no notification inbox, no comment threads, and no PR status sync. Obsidian Sync or git gives you one person across several devices; two people editing the same board produce merge conflicts rather than merges.

If a teammate or a client needs to see the board, reach for a hosted tracker. Use this for the work that lives in your head and your vault. See Known limits for the full picture.

Status

v0.1.0. Board and list views, projects, drag-and-drop, bulk edit, labels, due dates, archive, keyboard navigation.

Layout

Everything lives under one folder, and the folder tree is the data model:

Curvilinear/
  Projects/
    Rivet UI v2/
      ABOUT.md                    <- the project note
      ENG-8 Token contrast audit.md
      ENG-9 Slider primitive.md
    Auth Hardening/
      ABOUT.md
      ENG-1 Fix login redirect.md
  Inbox/
    ENG-5 Virtualize long columns.md    <- no project yet
  Archive/
    Projects/Rivet UI v2/
      ENG-3 Old idea.md                 <- archived, off the board

The folder an issue sits in is its project. Nothing else decides membership, which means you can reassign an issue by dragging it between folders in Obsidian's own file explorer, and archive a project by moving one folder out. Issues that belong to no project live in Inbox/.

A project is any folder directly inside Projects/ containing a note with type: project. New projects get an ABOUT.md, which sorts above the issues beside it. The filename is not load-bearing: the plugin finds the project note by its type: project frontmatter, so rename it to README.md or anything else and nothing breaks. The project's name comes from the folder, so renaming the folder renames the project.

Issue format

An issue is any note under the Curvilinear folder with type: issue in its frontmatter.

---
type: issue
id: ENG-142
status: in-progress      # backlog | todo | in-progress | done | canceled
priority: 1              # 0 none, 1 urgent, 2 high, 3 medium, 4 low
assignee: seth
project: "[[Curvilinear/Projects/Rivet UI v2/ABOUT|Rivet UI v2]]"  # derived
estimate: 3
due: 2026-09-01
labels:
  - design
  - tech-debt
created: 2026-08-15
sort: 0.5
---

The note body is the issue description. Write whatever you want here.

The project field is written and kept in sync purely so project notes collect backlinks and Bases queries work. Editing it does nothing. Move the note to change its project.

The filename is ENG-142 Fix login redirect.md, so wikilinks read naturally and the title comes from the filename with the identifier stripped off.

sort is a fractional index for manual ordering within a column. Dropping a card writes the midpoint between its new neighbours, so a reorder rewrites one file instead of the whole column.

Project format

---
type: project
status: active           # planned | active | paused | completed | canceled
lead: seth
target: 2026-09-30
created: 2026-08-15
sort: 0
---

The note body is the project brief.

The project's name is its folder name. Add name: to display something different.

Projects are one-to-many with issues: a project has many issues, an issue belongs to at most one project. Use the board's project filter to scope to a single project, or No project to find unfiled work in the Inbox.

Usage

ActionHow
Open the boardRibbon icon, or Curvilinear: Open board
New issueCurvilinear: New issue, or c on the board
New projectCurvilinear: New project, or the board button
Scope to a projectProject dropdown in the board header
Move / reorderDrag a card
Navigatej k h l or arrow keys
Open an issueEnter, or double-click
Switch board / listToggle in the header
Set priority04 on the selected card
Reassign projectp on the selected card, or drag the note between folders
Copy git branch nameb, giving eng-21-magic-link-auth-flow
Archivee, or the bulk bar
Select a rangeShift-click within a column or group
Add or remove oneCmd-click
Clear selectionEsc

Creating an issue while the board is scoped to a project files it into that project. Reassigning moves the note on disk, so inbound links follow it.

Select more than one issue and a bulk bar appears: set status, priority, or project for all of them at once, or archive the lot. Keyboard actions apply to the whole selection too, so selecting six issues and pressing 1 makes all six urgent.

List view is the one to use past a screenful of work. Group by status, project, assignee, priority, or label, and sort by manual order, due date, or priority.

Assign a hotkey to New issue in Obsidian's hotkey settings for a global c.

Settings

  • Curvilinear folder: the umbrella, default Curvilinear. Notes outside it are ignored. Changing it does not move existing notes.
  • Team prefix: used for generated identifiers, e.g. ENG in ENG-142.
  • Default status: applied to new issues.
  • Columns: hide statuses you don't want on the board.

Development

bun install
bun run dev          # esbuild watch, writes main.js
bun run typecheck
bun run build        # production bundle

To test in a vault, symlink the repo into <vault>/.obsidian/plugins/curvilinear and enable it under Community plugins. With the obsidian CLI installed, reload without restarting the app:

obsidian plugin:reload id=curvilinear

UI preview

Obsidian can't be driven by a headless browser, so dev/preview.html mounts the board components against an in-memory fake for visual and interaction checks:

bun run preview      # http://localhost:4399/dev/preview.html

Append ?theme=dark for the dark palette.

Due dates, labels, and archive

due: 2026-09-01 puts a date on the card. Anything past today gets a red left edge and red text; today's is orange.

labels is a list property. Chip colour is derived from the label text, so the same label is the same colour everywhere without configuring a palette:

labels:
  - bug
  - ui

Archiving moves a note to Curvilinear/Archive/, keeping the sub-path it had. It leaves the index entirely but stays in the vault, and moving it back restores it to the project it came from. Archiving more than one issue asks first.

Known limits

  • Single user. Obsidian Sync or git gives you async across your own devices; there is no real-time collaboration.
  • Repeatedly halving the same gap in one column will eventually exhaust float precision in sort. A rebalance pass is the fix and is not written yet.
  • Long columns are not virtualized. Expect this to get slow somewhere in the low thousands of issues per column.
  • No sub-issues, cycles, or comments yet.
  • An issue belongs to at most one project. Projects do not nest, and an issue in a sub-folder of a project folder counts as unfiled.
  • Ordering is global per status, not per project view. Reordering inside a project filter also moves the issue relative to hidden issues in that column.
  • There is no projects overview view yet; projects are reached through the board filter or their own notes.
  • Changing the Curvilinear folder setting does not relocate existing notes.
  • Grouping by cycle is not offered because there is no cycle field yet.
  • Due dates are compared as plain ISO strings against the day the view mounted, so a board left open past midnight keeps yesterday's idea of "overdue" until reopened.

License

MIT

For plugin developers

Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.