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

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

shipped production is serving it opened this has it
opened this

The argument happens on the proposal page, asynchronously, and stays part of the record.

What done looks like

  • [ ] A migration creates remark (id, proposal_id REFERENCES proposal(id) ON DELETE CASCADE, kind CHECK in comment/question/counter, body 1–2000, option_label nullable 1–80, accepted_at nullable, author_id, author_name, created_at), with an index on remark(proposal_id).
  • [ ] listRemarks(db, who, {proposalId}) reads for anyone, oldest first. addRemark(db, who, {proposalId, kind, body, optionLabel?}) refuses anon with signin_required, an unknown kind with invalid, a missing proposal with not_found; a withdrawn or decided proposal still takes remarks, because the record continues after the decision.
  • [ ] deleteRemark(db, who, {id}) is the author or an admin, the rule exported the way the others are.
  • [ ] A counter remark may name the option it wants instead (optionLabel). acceptCounter(db, who, {id}) — the proposal's author or an admin, open proposals only — appends that label as a new option and stamps accepted_at; accepting twice is invalid, a counter with no label is invalid, and a label duplicating an existing option is invalid. Existing ballots are untouched.
  • [ ] The proposal page shows the thread oldest first with the kind as a visible label, a composer with a kind selector for a signed-in person, and an Accept button on an unaccepted counter for whoever may. The thread is live on remark.added and remark.removed and does not shift the page when one arrives.
  • [ ] GET /api/proposals/:id/remarks, POST /api/proposals/:id/remarks, DELETE /api/remarks/:id, POST /api/remarks/:id/accept; MCP tools list_remarks and add_remark; the terminal shows the thread under an open proposal and c writes one.
  • [ ] Tests: a guest reads the thread and cannot write; an unknown kind is refused; 2001 characters refused; the author deletes their own and an admin deletes any; an accepted counter adds exactly one option and cannot be accepted twice; a counter on a decided proposal cannot be accepted.
  • [ ] README.md explains the three kinds in a sentence each, and bun test is green.

Files I expect to change: src/db.ts, src/actions.ts, src/actions.test.ts, src/routes.ts, src/pages/proposal.ts, src/mcp.ts, src/tty.ts, src/server.test.ts, static/app.css, README.md.

Risks: a remark is the longest user text in the app and it renders at three doors — escape at the page, and let the terminal print no control character it was handed. Accepting a counter changes a proposal people may already have voted on; keep it to appending an option and say on the page that earlier ballots stand. If acceptCounter makes this run too long, land the thread and open a follow-up for it rather than half-building both.

Fit: Hearth — a thread that streams and reads the same in a terminal. A person assigns; I do not.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs