Specifications v0.1 draft Sheet 02 of 03
kanban@0.1
A board as a plain Markdown file — a level-2 heading is a column, a task item
beneath it is a card, and the file is the whole board. It is a grouped view over
exactly the same item model as [email protected], so that in an ordinary Markdown
viewer a board degrades to a useful sectioned checklist rather than to noise.
- Status
- normative · draft, will change
- Inherits
- shared conventions · the item model
- Artifacts
- schema.json
fixtures/ - Owns codes
- MDTO200–MDTO299 9 allocated
§ 01
The file, and the board
On the left, a conformance fixture —
specs/kanban/fixtures/valid/full-metadata.md, byte for byte. On the right,
mdto render’s read-only board. Same bytes, no second copy, nothing
derived that is not in the file.
[email protected]
static render
--- markdownto: [email protected] title: Markdown To launch id: mdto-launch board-owner: akshay --- # Markdown To launch ## Backlog - [ ] Obsidian Kanban import adapter [priority:: low] #import - [ ] MCP server over the same verbs [priority:: low] [effort:: 1w] ## Doing - [ ] Build the patch engine [owner:: @akshay] [priority:: high] [due:: 2026-08-20] ^k3 - [ ] Fuzz the round-trip [owner:: @akshay] [priority:: medium] #infra ^k7 ## Review - [ ] Landing page copy [owner:: @akshay] [due:: 2026-08-18] ## Done - [x] Settle the shared conventions [owner:: @akshay] ^k1 - [x] Buy the domains #ops
Markdown To launch
2 doneof 7 cards across 4 columnsread-only
Backlog
0/2Obsidian Kanban import adapter #import
MCP server over the same verbs
Doing
0/2Build the patch engine
Fuzz the round-trip #infra
Review
0/1Landing page copy
Done
2/2- done
Settle the shared conventions
- done
Buy the domains #ops
The board scrolls horizontally inside its own container when it is wider than
the pane; the page never does. Every value shown went through the escaper on its way
out, and the pane carries no script — this is the static
render adapter, not the live board. The markup was produced by
@markdownto/web at build time; see site/tools/build-specs.mjs.
It is the only mapping that survives a viewer knowing nothing about Markdown To. Open
a conforming board in GitHub, in Obsidian, or in less and you get a
sectioned checklist that is still genuinely useful. That graceful degradation
is the portability promise, not a side effect of it. Any encoding
that made a board unreadable without the tool — a table of columns, a YAML
block, HTML comments — would have broken the promise on line one.
§ 02
Grammar
Distilled for this page. A card is a todo item — the grammar is
[email protected] verbatim, and the schema says so structurally: kanban’s card
definition is a $ref to todo’s item definition, so the two cannot
drift apart without someone deliberately breaking the reference.
The envelope, and the two keys kanban adds
--- markdownto: [email protected] title: Markdown To launch # optional, reserved id: mdto-launch # optional, reserved columns: # optional, spec-defined - Backlog - Doing - Done ---
columnsabsent- The normal case. The columns are whatever the document’s
##headings say — what a hand-author gets for free, because the document is the state. columnspresent- The board opts in to strict checking. A declared column with no heading is
MDTO206; a heading not declared isMDTO207; a different relative order isMDTO208, a warning. A wrong type on the key isMDTO007. done-column- Optional. Names the column that means finished (§2.3), for a board whose last column is a parking lot. Matched case-insensitively after trimming; a name that is not on the board falls back to the last column, silently — the key carries no diagnostics in 0.1.
Requiring it would mean every hand-authored board starts with a declaration that duplicates the headings directly below it, and duplicated truth drifts. The declaration exists for boards where the column set is a policy — a shared team board where “someone added a Shipped column” should be caught — and declaring it opts into exactly that check, and nothing else.
Document structure
- a leading
# - Board title. Informative; it opens nothing.
##- A column. Column order in the document is the board’s left-to-right
layout. Names are required (
MDTO204) and unique (MDTO205), compared case-insensitively after trimming. An empty column — a heading with no cards — is valid and common. - [ ]at top level- A card. Position within a column is the priority order. Both orders are meaningful; nothing sorts them.
- nesting
- An item on that card’s checklist — not a sub-card. Only top-level task items in a column are cards, at any board depth.
- a card before any
## MDTO202. There is no implicit column. Prose before the first column is fine — a board description is normal.- no columns at all
MDTO201. A board needs at least one.###and deeperMDTO203. A board has one level of grouping.- metadata on a heading
MDTO209.## Doing [limit:: 3]is the obvious next feature, and this code exists to say out loud that column-level metadata is a deferred extension point, not an oversight.
The two specs differ here on purpose. A todo list is fundamentally a list, and
grouping is an optional convenience — so a bare list is valid. A board is
fundamentally a partition: a card in no column has no position on the board,
and rendering it would require inventing a column the file does not contain.
The MDTO202 repair is always to add a heading or move the card,
never to invent an “Inbox”.
The done-role column
In the grammar the two facts stay independent: - [x] marks a card done
wherever it sits, a parser reads exactly what is written, and a checked
card in Doing is a legal file with no diagnostic attached. At the verb and UI layer they
are coupled. The last column in document order carries the
done role — the same rule for Done, Shipped, Live or 完了, because
position is the part of the idiom that is universal — and a move that crosses into
it checks the box, while a move out of it unchecks. Reordering inside a column touches no
box. done-column: in the envelope overrides the default for a board whose
last column is a parking lot.
## Doing - [ ] Build the patch engine [owner:: @akshay] [due:: 2026-08-20] ^k3 - [x] validate - [ ] render - [x] Freeze the item model
A board has one idiom, and it is that you finish work by dragging it right. A board
that also asks you to tick a box has two controls for one fact, and sooner or
later they disagree and the tool has to guess. So move writes both facts
and the live board draws no checkbox — which is also what keeps the promise: a
board edited through the verbs opens in any viewer as a checklist whose ticks are
right. A disagreement you did not create is still a legal file: render it as
written, never tidy it up on the way past.
Read [email protected] in full — the normative text Shared conventions
§ 03
Verb reference
[email protected] owns add, move, done,
rm, edit. There is no undone: un-checking is
routine on a checklist and a rare correction on a board, so kanban spends the slot on
edit instead. That is what “verb vocabularies are spec-owned”
means in practice — each spec spends its verbs on what its domain does often.
add
mdto kanban add "<text>" --column <col>
[--priority p] [--due d] [--owner o]
[--top | --before <ref> | --after <ref>]Inserts one line at the end of the target column.
--column is required: there is no implicit column to fall back on. A
column that does not exist is an error unless --create-column is passed
— which also adds the name to columns: if the board declares one,
because leaving them inconsistent would immediately produce MDTO207.
movethe showpiece
mdto kanban move <ref> <col> [--keep-state]
[--top | --bottom | --before <ref> | --after <ref>]Deletes the card’s line together with its checklist
lines and reinserts them with the text, fields, tags, identifier and relative
indentation verbatim. It collapses at most one blank line left doubled by the
removal. The checkbox is the one thing it may rewrite, and only when the move
crosses the done role: into the last column it is checked, out of it unchecked,
and a reorder inside a column leaves it alone. --keep-state opts out. A
drag on the live board is this verb, and its diff is one removal plus one insertion.
done
mdto kanban done <ref>…A three-character replacement inside the checkbox. The card
does not move, no column changes, no timestamp is stamped, the checklist is untouched.
To uncheck, use edit --uncheck.
rm
mdto kanban rm <ref>…Removes the card’s line and every line of its checklist, then collapses at most one doubled blank line. It never renumbers, reflows or re-indents neighbouring cards. Removing a pinned card retires its identifier permanently.
edit
mdto kanban edit <ref> [--text "…"]
[--set k=v]… [--unset k]…
[--tag +t | --tag -t]… [--check | --uncheck]Rewrites one line in place. --set adds
or replaces a field, keeping its existing position on the line if it is already there
and appending in canonical order if it is not. Unknown fields are only touched when
named explicitly.
Addressing a card, and the common flags
<ref>- A pinned identifier with or without the caret (
k3,^k3); otherwise a case-insensitive substring of the card’s normalized text ("patch engine"). If it matches zero or more than one card, the verb fails with a non-zero exit code and changes nothing. --column- Narrows the search. Column arguments are matched case-insensitively after trimming.
--file- Defaults to the single
*.kanban.mdin the working directory. --dry-run- Prints the unified diff and writes nothing.
--pin- Pins an identifier on the touched card and returns it. No verb pins one
otherwise. The live web board is the standing exception: it must track cards across
re-renders, so it pins on first interaction — which is why exactly one card in
the conventions example carries
^t3.
This is the spec that proves the round-trip promise. Dragging a card on the live board must produce exactly the source edit a person would have made by hand — one line removed, one line inserted, unrelated prose untouched, unknown fields intact — and if the file changed underneath the board, the conflict surfaces via the source hash rather than being overwritten. A board that reformats the file on save has failed at the only thing that makes this format worth having.
What no verb ever does
Reformats a line it was not asked to change·normalizes
bullet markers, indentation or blank lines·reorders
cards or columns it was not asked to reorder·drops an
unknown field or an unrecognized value·checks a box
because of which column a card landed in·pins an
identifier without --pin·rewrites the
markdownto version·writes a file whose
source hash no longer matches the one it read.
§ 04
Diagnostics carry the repair
Everything a card can get wrong produces a shared code from the item model
— the same broken card reports the same diagnostic in both specs, and that overlap
is the point. The board’s own structure owns
MDTO200–MDTO299.
--- markdownto: [email protected] title: Markdown To launch --- ## Backlog [limit:: 3] - [ ] Obsidian Kanban import adapter ### Doing - [~] Build the patch engine - [ ] [priority:: high]
-
MDTO209 error line 6
## Backlog [limit:: 3]Inline field
limiton a column heading: columns carry no metadata in [email protected]. -
MDTO203 error line 10
### DoingHeading level 3 is not valid in a kanban document: a column is a
##heading. -
MDTO031 error line 12
- [~] Build the patch engineUnrecognized checkbox state
~: [email protected] defines only[ ],[x], and[X]. -
MDTO033 error line 13
- [ ] [priority:: high]Card has no text: it consists only of metadata.
A heading invalid for its level does not open a column, and the cards below it
belong to the enclosing one — so they do not additionally report
MDTO202 while a file is being repaired. A heading invalid for its
content still opens a column, so items cannot silently migrate into the
previous one. An unusable columns: declaration is discarded whole rather
than firing a set-mismatch error once per heading.
The codes this spec owns
# title and ## columns.
columns has no
heading in the document.
columns.
§ 05
For agents
Agents are the primary users of the CLI, and the value over freehand editing is determinism: guaranteed-valid output, minimal diffs, no whole-file context required. This sheet is the human edition; the agent edition is the same document, fetched.
$ mdto spec kanban [email protected] — agent-facing specification 1. Purpose what the format is for 2. Grammar heading = column, item = card 3. Rationale why it is shaped that way 4. Examples valid + invalid + diagnostics 5. Verbs add · move · done · rm · edit $ mdto validate launch.kanban.md ok conforms to [email protected] · 7 cards, 4 columns $ mdto kanban move k3 Review --dry-run --- launch.kanban.md +++ launch.kanban.md -- [ ] Build the patch engine [priority:: high] ^k3 ## Review +- [ ] Build the patch engine [priority:: high] ^k3 ok 1 removal, 1 insertion · checkbox untouched
-
One item model, one implementation
One parser, one validator, one patch engine, and one set of habits for anyone — human or agent — who has learned either spec. If you know how to repair a todo item you already know how to repair a card, because they are the same object by construction.
-
Fetchable, not scrapeable
The agent-facing document lives at a stable URL beside an
llms.txtso an agent never has to scrape a human documentation site to author a conforming board.fetch
markdownto.ai/specs/kanban.mdfetch
markdownto.ai/llms.txt -
Fixtures are the contract
An independent implementation conforms if and only if it agrees with
specs/kanban/fixtures/. Valid fixtures parse with no error and round-trip byte for byte; every invalid one carries a sibling.diagnostics.jsonnaming the exact codes, severities and lines.universal
validaterenderspec