Repository navigation
Plannotator Inbox: decisions (step 3) - #1757
Merged
Merged
Conversation
The card's "Records a decision" tag is the switch, kept on the question record; its words open the decision card, drafted answer first, and Done keeps the words. At Send, each sent answer whose switch is on becomes a decision in the project's decisions.jsonl (Workspaces' fields), and the thread says "Settled". Read-only is decided per question (ui seam questionDecisionLocked). The Decisions page lists Waiting, Settled, and Replaced or retired folded, with a row opening beside the list for Retire and Replace. MCP list_decisions and record_decision. Proved by tests/e2e/inbox-decisions.spec.ts (Playwright against the compiled binary, agents through the stdio shim) and packages/server/inbox-decisions.test.ts.
backnotprop
force-pushed
the
feat/inbox-decisions
branch
from
October 7, 2026 15:26
8349e18 to
30ac4ab
Compare
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The story
The card's "Records a decision" tag is now a real switch saved on the question, and its words open the approved decision card, drafted answer first. At Send, every sent answer whose switch is on becomes a decision in the project's own
decisions.jsonl, and the thread says "Settled: ". The Decisions page lists Waiting, Settled, and "Replaced or retired" folded, with a row that opens beside the list for Retire and Replace. Agents read the list withlist_decisionsand write to it withrecord_decision.Plannotator Inbox step 3 (PLAN.md). Based on main after the window (PR 1755) and notifications (PR 1756,
d5e0062ac).What changed
packages/core/inbox-types.ts:InboxDecision(Workspaces' fields: text, reason, source, statecurrent | replaced | retired, version, replaces_id, replacement_id), thedecisionstore line,dec_ids, anddecision_recording/decision_drafton the question record and wire.inboxAgentNamemoved here from the window'sformat.ts, so the server drafts the same "Asked by " words.packages/core/inbox-questions.ts(appended, browser-safe): the drafted words (draftInboxDecisionText,draftInboxDecisionReason,inboxDecisionWords) and the switch default (inboxDecisionRecording). They live in an existing core subpath on purpose: a new subpath fails the ui package smoke against the published core 0.25.12, and ui does not import these. So core's exports are unchanged and neither core nor ui needs a publish.packages/shared/inbox/store.tsandschema.ts:decisions.jsonlread at start, replayed by seq like every other line, counted in the store's size;writeDecisionandwriteQuestionDecision.writeQuestionDecisionnever touches an answer or its revision, so a switch never races a pick.packages/server/inbox-decisions.ts(new): the rules (setQuestionDecision,recordDecisionsForReply,retireDecision,replaceDecision,recordAgentDecision,waitingDecisions) and the window routes:GET /api/inbox/decisions?project=,POST /api/inbox/messages/:id/decision,POST /api/inbox/decisions/:id/retire|replace.packages/server/inbox.ts, in its own block: the decision routes behind the same Host, Origin andserverSessionguards. Three small edits in existing code: the reply route records decisions after the reply lands, the thread route returns the thread's decisions, and the list model carriesdecisions_waiting(the sidebar count). The event stream also carries decision lines.packages/server/inbox-mcp.ts:list_decisionsandrecord_decision, plus one line in the instructions (now 1,518 characters, under 2,048).apps/hook/server/inbox-mcp-shim.tsfillsproject_pathandagent_sessionon both.packages/inbox):components/DecisionCard.tsx(the card),components/Decisions.tsx(the page and the row pane),Thread.tsx(the switch on the server instead of localStorage, the card, the "Settled" line, the "Not recorded" line),App.tsx(#decisions=prj_…&decision=dec_…),Sidebar.tsx(Decisions live, with its count),inbox.css(the record's values with theib-prefix),api.ts,icons.tsx(folder, diamond).packages/ui/components/Viewer.tsx: one additive seam,questionDecisionLocked(question). It is noted inpackages/ui/HANDOFF.mdunder "unreleased", and Plannotator passes none, so its render is unchanged. A question it returns true for draws its decision tag as a plain tag (no switch, no opener) while the other questions in the same message keep theirs. The Inbox locks a question once its answer is sent, so read-only is decided per question, not per message (review blocker 1).scripts/inbox-sim.ts:listDecisionsandrecordDecisiononSimAgent.AGENTS.md: the Inbox section gains "Decisions".Record sections this makes real
aria-pressedtoggle, words = card opener, ui 0.52.1onOpenQuestionDecision); off is dimmed with a dotted outline and the row hidden (approved Off option A); the card is 400 px with "Record as a decision", "Decision" (focused, the answer), "Why (optional)" ("Asked by Claude Code: "), "In billing-svc, when you send", Cancel and Done. Proof frames:3.1-decision-card,3.1-switch-off,3.1-settled.3.2-decisions,3.2-decision-open,3.2-retired-folded,3.2-replaced-history.Proofs and how to run them
tests/e2e/inbox-decisions.spec.ts, 13 tests, Playwright headless Chromium against the compiled binary, with agents going throughplannotator inbox mcpand the SDK's stdio client:Decision: when answeredand off for a plain question (also read back over MCP).decisions.jsonlon disk. The switched-off question records none, and the thread shows "Settled: ".list_decisionsreturns the decision.record_decisionfrom the simulation appears live, and a retry with the same key appears once.replaced, linked both ways.packages/server/inbox-decisions.test.ts, real server,fetchand the MCP SDK client:inbox-e2e.ymljob now runs both specs (tests/e2e/playwright.config.tstestMatch). No new workflow job.tests/e2e/inbox.spec.tschanges:.local/proof/decisions/survives.Gates run
bun run typecheck: green (it already carries the explicitpackages/inboxandapps/inboxentries).bun test packages apps/hook tests scriptsunder a tempPLANNOTATOR_DATA_DIRand HOME: 5,783 pass, 1 fail. The one failure isapps/hook/server/annotate-cli.test.ts, "annotate CLI on an occupied fixed port". It fails identically on a cleanorigin/mainworktree on this machine, and this PR does not touch annotate.tests/entry-assets.test.tsandtests/favicon-surfaces.test.ts: 399 pass, 0 fail.bun run --cwd packages/ui smoke:package: green (core and ui changed; core's exports did not).DOM_TESTS=1): green apart fromViewer.consumer.test.tsx"readOnly fenced code never opens composers" (a code-highlighting assertion). It fails identically on a cleanorigin/mainworktree.bun run check:release-version: green.bun run test:e2e:inboxagainst the compiled binary, after the rebase ontod5e0062ac: 33 of 33 (window, notifications, decisions).git merge-tree --write-tree origin/main HEAD: no conflict.DOM_TESTS=1allowlist is unchanged.guides-showrebuild needed).What it leaves out
record_decisiononly adds).Deviations from the record, each with its reason
<select>styled as the record's select-like control (same height, border, chevron). It keeps keyboard and screen-reader behaviour for free. Only the open menu is the OS's.align="end").POST /api/inbox/messages/:id/decision{ key, recording?, draft? }. The brief suggested/api/inbox/questions/<id>/decision-recording. This one sits beside the message's existingpicks,replyandresolveroutes.versioncounts changes to one record, so retire and replace bump it and a stale version is refused. A replacement is a new record at version 1 withreplaces_id, and the old record getsreplacement_idand statereplaced. The brief's "replace writes a new version" is read this way.source.kind(answer | agent | person),source.thread_id,replaces_id,replacement_id,changed_at(Workspaces' fields, for the history), andidempotency_key.record_decisiontakes anidempotency_keyso an agent's retry never records twice, assend_messagealready does.Dependencies
None open. This branch was started on
feat/inbox-window, rebased onto main when PR 1755 merged, and rebased again onto PR 1756. Conflicts were resolved by keeping both sides: notifications and decisions side by side inApp.tsx,playwright.config.tsandAGENTS.md.