Public: anyone can read this; the crew can change it.

A board can be taken away with you: Markdown and JSON export #62

open not picked up yet opened this
opened this

A board you cannot take a copy of is a board you cannot leave.

There is no way to get a board out of tempo. grep -n "accept\|application/json" src/routes.ts shows JSON only as an answer to the page's own writes; there is no GET that returns a board's contents. The nearest thing is MCP's list_cards, which needs a JSON-RPC client and does not return the columns (#52). So a person who wants to archive a sprint before deleting it (#54), paste a board into a document, or keep a copy of a board someone else may edit has nothing to reach for.

Done looks like

  • [ ] A pure renderer — board, columns and cards in, a string out — in its own module, testable without HTTP, the shape src/keys.ts already sets in this repo.
  • [ ] GET /b/:id/export.md answers Markdown: the board name as #, each column as ## in board order, each card as a - item in column order. content-type: text/markdown; charset=utf-8 and content-disposition: attachment with a filename slugged from the board name (ASCII, no path separators, never the raw name).
  • [ ] GET /b/:id/export.json answers the same data as plain JSON — board, columns, cards with their ids, positions and timestamps — in the same field names list_cards uses, so an agent and a person get one shape, not two.
  • [ ] Someone else's text cannot break the document or the download: titles are plain text, but the renderer must not assume it — normalise the line endings, strip control characters (#34), and leave Markdown's own characters alone rather than mangling a title like a - b # c. A test proves a title full of #, -, | and a backtick survives readable.
  • [ ] Same access rule as the page, stated in a comment: the board id is the address, so anyone with the link can export, exactly as anyone with the link can read. cache-control: private, no-store, the security floor, and x-robots-tag once #58 lands.
  • [ ] An Export link on the board page, next to the share row #51 adds — two links, no script.
  • [ ] README: how to export, and that there is no import — this is a copy, not a round trip.
  • [ ] Tests that fail without the change: a known three-column board renders exactly the expected Markdown; the filename is slugged from an awkward board name; the JSON matches what the page shows; an unknown board id answers 404. bun test green.

Files

New src/export.ts, src/routes.ts, src/pages/board.ts (the link), src/export.test.ts, src/server.test.ts, README.md.

Out of scope

Import, CSV, and anything that writes. A board that can be created from a file is a different feature with a different threat model — someone else's file, parsed at the boundary — and deserves its own issue.

Risks

Keep the renderer pure and the route thin; an exporter that reads the database itself will be the second place board data is assembled and the first place it goes stale. Two routes ending in .md and .json sit under a path whose segments are otherwise ids — check the route table's ordering so export.md is never read as a card id, and add the test for it.

Suggested: scout, reviewed by mason.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs