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

Deliberation: comments, questions and counter-proposals on a proposal #32

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

Deliberation lands on the proposal page: a `remark` table (migration 6) holds comments, questions and counters — one to 2000 characters, cascading with its proposal, with the option a counter wants in `option_label` and `accepted_at` once that option has been taken. Five actions carry every rule: `listRemarks` reads for anyone, `addRemark` refuses a guest, an unknown kind and a missing proposal bu…

shipped production is serving it deploy 2adbe7d

opened this

Deliberation lands on the proposal page: a remark table (migration 6) holds comments, questions and counters — one to 2000 characters, cascading with its proposal, with the option a counter wants in option_label and accepted_at once that option has been taken. Five actions carry every rule: listRemarks reads for anyone, addRemark refuses a guest, an unknown kind and a missing proposal but takes remarks on a withdrawn or decided one, deleteRemark is its author or an admin, and acceptCounter is the proposal's author or an admin on an open proposal only — it appends the counter's label as one more option and stamps the remark, and nothing else about the proposal moves, so ballots already cast still mean what they meant.

Every door calls the same functions: the thread and its composer on /p/<id>, GET/POST /api/proposals/:id/remarks, DELETE /api/remarks/:id, POST /api/remarks/:id/accept, the list_remarks and add_remark MCP tools, and the terminal, where an open proposal shows its thread and c writes one. Worth looking at in the preview: open a proposal, counter it with an option label from a second account, and accept it — the option appears and the remark says earlier ballots stand. The thread is the last region on the page and grows at the bottom, live on remark.added, remark.removed and remark.accepted, so a remark arriving never moves the box someone is typing in; open two windows to see it.

Two things to look at beyond the checklist. src/plain.ts is new and has no dependencies: it turns every C0 and C1 control character in somebody else's text into a space, and src/tty.ts now prints all foreign text through it, so a remark cannot drive a terminal's cursor (escape already does this job for HTML). And /about moves deliberation out of "not yet" into what works, which made its assertion in src/server.test.ts wrong — that test was right when it was written and I changed it because the feature landed, not because it was failing inconveniently; every other test change is new.

What I did not do: a remark cannot be edited, there is no reply-to-a-reply, no thread is paginated (the record shows all of it), and nobody is told a question is waiting — that last one is now the "Answers" line in the not-yet list on /about. bun test is green at 76 tests, and /p/<id> renders in well under a millisecond with a thread on it.

🤖 Generated with Claude Code

Closes #6

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs