Give a card a date, so a shared board can say what is due and what is late instead of being a wall of undated text.
Why. A board people share for real work is planned in time: "Ship it" matters on Friday. Today a card holds a title and nothing else (src/db.ts's card table has title, position, created_at, updated_at, and none of the last three is ever shown). #89 gives a column a WIP limit and #49 gives a card a note; a due date is the third thing every person who has used a kanban board reaches for, and it costs one nullable column, one validated field and one rendered <time>.
Done looks like
[ ] card.due_on — a nullable TEXT holding a plain YYYY-MM-DD date, added through the ordered migration list from #97 (no second hand-written PRAGMA table_info patch).
[ ] POST /b/:id/cards/:cardId/due sets or clears it: a date validated at the boundary against /^\d{4}-\d{2}-\d{2}$/andDate.parse, an empty value clearing it, anything else refused with the board's own error shape. The write is one transaction, logs its previous value for undo, bumps the revision and publishes card.edited — the existing event is enough, no new kind.
[ ] The card shows it as <time datetime="2026-09-25">25 Sep</time>, escaped like everything else, with a "late" style when the date is before today (computed server-side, so a wrong client clock cannot make a card look late) and no layout shift when the slot is empty.
[ ] The filter (q) still only matches titles; due: syntax is explicitly out of scope and said so in the pull.
[ ] list_cards returns due_on, and add_card accepts an optional due_on with the same validation, so the agent seat can plan too.
[ ] The terminal shows the date beside a card's title where the width allows, and has one key to set it — what a person can do on the page they can do over ssh (AGENTS.md).
[ ] Tests that fail without the change: the boundary rejects 2026-13-45, tomorrow, and a 200-character string; clearing works; undo restores the previous date; the rendered card carries the <time> element and the late class only when late; list_cards carries the field.
[ ] The README's card paragraph says what a date is and is not (no time of day, no timezone, no reminders).
Files I expect to change
src/db.ts, src/routes.ts, src/pages/board.ts, src/mcp.ts, src/keys.ts and src/tty.ts, src/db.test.ts, src/server.test.ts, README.md.
Risks
Waits for #97 (one ordered migration list): this is the second nullable column queued behind that, after #89's wip_limit, and adding it by hand is exactly the failure #97 exists to prevent.
#48 moves per-card controls onto a card page. If it has landed, the date control belongs there and the card itself only shows the date — check before building a second inline form.
Timezones: store a date, never an instant, and compare against the server's own day. Say that in a comment; it is the thing that goes wrong later.
Suggested: scout, reviewed by juniper. Waits for #97.
Give a card a date, so a shared board can say what is due and what is late instead of being a wall of undated text.
Why. A board people share for real work is planned in time: "Ship it" matters on Friday. Today a card holds a title and nothing else (
src/db.ts'scardtable hastitle,position,created_at,updated_at, and none of the last three is ever shown). #89 gives a column a WIP limit and #49 gives a card a note; a due date is the third thing every person who has used a kanban board reaches for, and it costs one nullable column, one validated field and one rendered<time>.Done looks like
card.due_on— a nullableTEXTholding a plainYYYY-MM-DDdate, added through the ordered migration list from #97 (no second hand-writtenPRAGMA table_infopatch).POST /b/:id/cards/:cardId/duesets or clears it: a date validated at the boundary against/^\d{4}-\d{2}-\d{2}$/andDate.parse, an empty value clearing it, anything else refused with the board's own error shape. The write is one transaction, logs its previous value for undo, bumps the revision and publishescard.edited— the existing event is enough, no new kind.<time datetime="2026-09-25">25 Sep</time>, escaped like everything else, with a "late" style when the date is before today (computed server-side, so a wrong client clock cannot make a card look late) and no layout shift when the slot is empty.q) still only matches titles;due:syntax is explicitly out of scope and said so in the pull.list_cardsreturnsdue_on, andadd_cardaccepts an optionaldue_onwith the same validation, so the agent seat can plan too.AGENTS.md).2026-13-45,tomorrow, and a 200-character string; clearing works; undo restores the previous date; the rendered card carries the<time>element and the late class only when late;list_cardscarries the field.Files I expect to change
src/db.ts,src/routes.ts,src/pages/board.ts,src/mcp.ts,src/keys.tsandsrc/tty.ts,src/db.test.ts,src/server.test.ts,README.md.Risks
wip_limit, and adding it by hand is exactly the failure #97 exists to prevent.Suggested: scout, reviewed by juniper. Waits for #97.