The second half of #38, which was cut down to the replies themselves. With reply_to on remark and replies rendering in reading order, make the thread say what asynchronous deliberation is for: which questions are still waiting, and let every door reply, not only the page.
What done looks like
[ ] A question with no reply is marked "unanswered" where it sits, and the line above the thread says how many are ("2 questions unanswered", and nothing at all when none are). The mark is computed from the thread that is already loaded — no new column, no state to keep in sync, no second query.
[ ] Answering clears the mark: the reply is the answer, and the live region shows it clearing without a reload.
[ ] The proposal author sees their unanswered questions first on /p/:id — a line at the top linking to each by its anchor, shown to the author only.
[ ] POST /api/proposals/:id/remarks takes replyTo (a number, validated at the boundary, never coerced from a string), answering 422 for a reply that breaks the one-level rule with the sentence the action wrote.
[ ] MCP's add_remark takes replyTo, its schema saying in one sentence that a reply is a comment, that it may not reply to a reply, and that it must be on the same proposal. get_proposal returns remarks in reading order with each remark's id and replyTo, so an agent can answer a question it was asked.
[ ] The terminal shows replies indented under their parent, marks unanswered questions, and can post a reply naming the parent's number.
[ ] Tests: the unanswered count before and after an answer; a question answered by its own author still counts as answered (the rule is that a reply exists, and the README says so); the API's replyTo refusals; the MCP round trip; a proposal with no questions shows no counter line.
[ ] README.md says what "unanswered" means in one sentence. bun test is green.
Files I expect to change:src/pages/proposal.ts, src/routes.ts, src/mcp.ts, src/tty.ts, src/actions.ts (only if the count belongs beside listRemarks), src/server.test.ts, static/app.css, README.md.
Risks: the unanswered count is a view over the thread, not a column — a column here is a cache that will go stale the first time a reply is deleted. The thread is the live region: a reply form that is open when an event arrives must not be swapped out from under whoever is typing. Do not widen the one-level rule here; it lives in the action from #38.
Fit: Ledger — the argument as a record, with the questions it still owes an answer to counted honestly. A person assigns; I do not.
The second half of #38, which was cut down to the replies themselves. With
reply_toonremarkand replies rendering in reading order, make the thread say what asynchronous deliberation is for: which questions are still waiting, and let every door reply, not only the page.What done looks like
questionwith no reply is marked "unanswered" where it sits, and the line above the thread says how many are ("2 questions unanswered", and nothing at all when none are). The mark is computed from the thread that is already loaded — no new column, no state to keep in sync, no second query./p/:id— a line at the top linking to each by its anchor, shown to the author only.POST /api/proposals/:id/remarkstakesreplyTo(a number, validated at the boundary, never coerced from a string), answering 422 for a reply that breaks the one-level rule with the sentence the action wrote.add_remarktakesreplyTo, its schema saying in one sentence that a reply is a comment, that it may not reply to a reply, and that it must be on the same proposal.get_proposalreturns remarks in reading order with each remark's id andreplyTo, so an agent can answer a question it was asked.replyTorefusals; the MCP round trip; a proposal with no questions shows no counter line.README.mdsays what "unanswered" means in one sentence.bun testis green.Files I expect to change:
src/pages/proposal.ts,src/routes.ts,src/mcp.ts,src/tty.ts,src/actions.ts(only if the count belongs besidelistRemarks),src/server.test.ts,static/app.css,README.md.Risks: the unanswered count is a view over the thread, not a column — a column here is a cache that will go stale the first time a reply is deleted. The thread is the live region: a reply form that is open when an event arrives must not be swapped out from under whoever is typing. Do not widen the one-level rule here; it lives in the action from #38.
Fit: Ledger — the argument as a record, with the questions it still owes an answer to counted honestly. A person assigns; I do not.