Public: anyone can read this; the crew can change it.
A question gets an answer: one level of replies, and an unanswered question says so #38
You've hit your weekly limit · resets Sep 20, 2am (UTC)
Interrupted, not the work (outage; strike cleared 2026-10-02) — was: Parked by the crew: the run ended without a pull: no changes pushed: You've hit your weekly limit · resets Sep 20, 2am (UTC) (api_error)
You've hit your weekly limit · resets Sep 20, 2am (UTC)
Interrupted, not the work (outage; strike cleared 2026-10-02) — was: Parked by the crew: the run ended without a pull: no changes pushed: You've hit your weekly limit · resets Sep 20, 2am (UTC) (api_error)
Amendment from the planner — this issue is split; build the first half only.
Keep: the reply_to migration and its index, the validation in addRemark (same proposal, one level only, a reply is a comment), listRemarks returning reading order from one query (ORDER BY coalesce(reply_to, id), id), replies rendered indented under their parent on /p/:id with a stable id="r<id>" anchor and a permalink, a Reply control that composes with the parent set, the cascade on delete, and the migration test against a database that already holds remarks.
Moved to #46, which waits on this one: the "unanswered" mark and its count, the author's list of questions still owed an answer, and replyTo through the API, MCP and the terminal. Three doors and a derived count on top of a migration was two runs in one issue.
The parks on this issue were a weekly limit and a DNS failure, not anything wrong with the work — nothing here has been attempted yet.
The thread is flat. A
questionis addressed to the proposal's author, and there is no way to answer it under it and nothing that tells a reader whether it was ever answered — so asynchronous deliberation loses the one thing it is for. Give a remark one level of replies, and let the reply be the answer.What done looks like
migrationsinsrc/db.tsaddsreply_to INTEGER REFERENCES remark (id) ON DELETE CASCADEtoremark, nullable with no default, and an index on it. Never edit a migration that has run.addRemarktakes an optionalreplyTo, validated in the action: it must name a remark on the same proposal (invalid, “A reply belongs to a remark on this proposal.”), that remark must not itself be a reply (invalid, “A reply cannot be replied to.” — one level, always), and a reply is acomment(invalidfor a reply that claimsquestionorcounter). Anon is stillsignin_required.listRemarksreturns the thread in reading order — each top-level remark followed by its replies, oldest first — from one query, not one per remark.src/pages/proposal.tsrenders replies indented under their parent; every remark gets a stable anchor (id="r<id>") and a permalink to it, so a remark can be linked.POST /api/proposals/:id/remarkstakesreplyTo, MCP'sadd_remarktakes it with the one-level rule in its sentence, and the terminal shows replies indented under their parent.counter; reading order with two parents and interleaved replies; the unanswered count before and after an answer; the cascade on delete; and an existing database with remarks in it migrating forward with its rows intact (src/db.test.ts).Files I expect to change:
src/db.ts,src/db.test.ts,src/actions.ts,src/actions.test.ts,src/pages/proposal.ts,src/routes.ts,src/mcp.ts,src/tty.ts,src/server.test.ts,static/app.css.Risks: The thread is the live region and it grows at the bottom — read the comment at the top of
src/pages/proposal.tsand keep whatever someone is typing outside anything that gets swapped, including a per-remark reply form that is open when an event arrives. A new column on a table that already holds rows is exactly the shape of failure #29 is about: run the app against a database that already has proposals and remarks before writing the page. Reading order must come from SQL (ORDER BY coalesce(reply_to, id), id) rather than from sorting an unbounded list twice in TypeScript. A counter that is accepted (#6) still names an option; a reply must not be able to smuggleoption_labelpast that rule.Fit: Ledger — the argument as a record, every line traceable to the line it answers. A person assigns; I do not.