Skip to content

Let the writer draft their brief from the document - #598

Open
kcarnold wants to merge 5 commits into
mainfrom
claude/paper-acceptance-checklist-preset-rmjjda
Open

Let the writer draft their brief from the document#598
kcarnold wants to merge 5 commits into
mainfrom
claude/paper-acceptance-checklist-preset-rmjjda

Conversation

@kcarnold

Copy link
Copy Markdown
Contributor

Where this started

A prompt that worked well by hand on the Chat page:

Help me think through a checklist of things that this paper should do successfully before we submit it as a registered report. i.e., what are the acceptance criteria. Let's aim for no more than a dozen, and keep them clear and succinct.

The obvious home was a fourteenth button on the Revise grid. That's the wrong shape twice over: every other Revise feature answers what is in my document?, while this answers what would make this document done? — a statement about the goal, not an observation about the text. And Revise results are read and discarded, whereas acceptance criteria are something you want next week, having shaped what the tool says to you in between.

So it goes to the brief instead. But the brief has its own problem, which is what this PR is really about: it's three fields the writer states from scratch, and in practice it's usually blank or thin. That's structural rather than lazy — a writer who can crisply state their audience and success conditions has already done the hardest part. Meanwhile their draft is full of evidence about all three fields; they've been deciding about audience and purpose in every paragraph without writing it down.

What this adds

A "Draft from my document" action in BriefSection. It reads the current draft and offers candidate wording per field, rendered as provisional cards beside the fields.

Constraints is asked for as a checklist — clear, succinct criteria the writer could judge as met or unmet, no more than a dozen. That's the generalization of the original prompt. "Paper" and "registered report" aren't restated anywhere; they come from the document and the existing brief, both of which are in the request, which is what makes it a preset rather than a one-off.

The rule that shapes the code

docs/design/interface-concepts.md requires AI-authored prose to be "framed as draft material to be edited, rendered in a visibly provisional style, and inert until the writer touches it." Concretely:

  • Candidates never land in a field — they render below it, dashed and tinted, even when the field is empty.
  • A candidate for an already-filled field leaves the writer's wording untouched and sits alongside it.
  • Candidates are session state: held in the brief context, deliberately not in DocBrief, never serialized. Nothing the writer hasn't agreed to should follow the file to whoever opens it next.
  • The only path from candidate to brief is acceptProposalsetField, so an accepted candidate is saved, logged, and cleared by exactly the same code as typed text.
  • Typing into a field clears its candidate.

Grounding is strict, and the prompt asks for a field to be omitted rather than guessed at. The failure mode is worse than a bad outline: a plausible invented audience reads as insight, gets accepted without scrutiny, and then silently frames every request on every page. A blank field is visibly blank; a wrong one isn't. "Nothing to suggest yet" and "nothing to read yet" are visible outcomes with their own notices.

Changes

File
frontend/src/api/briefProposal.ts New. Prompt, request, and a defensive JSON parser.
frontend/src/contexts/docBriefContext.tsx Candidate lifecycle: proposals, setProposals, acceptProposal, dismissProposal.
frontend/src/components/briefSection/ The action, the provisional cards, and their styles.
frontend/src/api/logging.ts brief_proposal_requested/_received/_resolved/_error; LOG_SCHEMA_VERSION → 5.
docs/design/co-created-brief.md New. The reasoning above, plus open questions.
frontend/CLAUDE.md The invariant, so it survives the next edit.

_resolved records accepted vs dismissed per field. That's the measurement the feature exists for: candidates almost always accepted unedited would mean the tool is writing the brief rather than co-creating it.

Testing

  • npm test — 241 pass (29 files), including 13 new: parser edge cases (fenced JSON, preambles, non-string values, truncated output) and section behaviour (a candidate stays out of its field, accept/dismiss reach the context, an empty document doesn't generate).
  • npm run lint — 0 errors (3 pre-existing warnings, unrelated files).
  • npm run format:check — my files clean; 5 pre-existing failures in files I didn't touch.
  • Playwright: 27 passed, 4 failed, all visual regression. Those 4 fail identically on unmodified main in this environment — the sandbox ships Chromium build 1194 and the snapshots were made with 1223. I verified my diff doesn't move any snapshot: chat-start is byte-identical between main and this branch, and no snapshot renders the Revise page or an expanded brief. Worth flagging separately: chat-two-turns produced three different images across three runs of identical code, so it looks inherently flaky.

Not in scope

The Revise feature grid is untouched — it's now #597, which also notes that all 13 features carry an icon: field that's never rendered, and that isOverall silently splits them into whole-document and part-by-part sweeps with no UI signal.

Open questions (in the design doc)

  • Chat should be able to propose brief updates. The plumbing is shaped for it — setProposals is on the shared context — but deciding when to propose without turning every message into an interruption isn't answered yet.
  • Does the checklist deserve to be its own artifact? It's Constraints today, which fits ("what does this have to satisfy?") but tightly — a dozen criteria is a lot for one textarea, and criteria are the sort of thing you'd want to grade individually. It isn't a fourth field because interface-concepts.md reserves that vocabulary for the Charter's Rubric, and a field named "Success criteria" would squat on the name while providing none of the mechanic.

Generated by Claude Code

claude added 5 commits July 31, 2026 21:35
Groundwork for letting the assistant offer wording for the brief's fields.
A candidate is not part of the brief until the writer accepts it, and the
types say so: BriefProposal is deliberately separate from DocBrief, so there
is no way to serialize one into the document by accident. The state is
session-only for the same reason — something the writer has not agreed to
should not follow the file to whoever opens it next.

It lives in the context rather than in the section component so a candidate
survives collapsing the section or moving between pages, which is why the
brief itself is in a context too.

acceptProposal routes through setField, so an accepted candidate is saved,
logged, and cleared by exactly the same code as text the writer typed —
there is no second way for a field to change. setField also clears the
field's candidate outright: a field the writer has answered themselves has a
spent candidate, whether they accepted it or ignored it and wrote their own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGZQEsAGYQsV7anwsPS6xk
The brief is usually blank or thin, and that is structural rather than lazy:
a writer who can crisply state their audience and their success conditions
has already done the hardest part of the work, so the writers who most need
the brief are the ones least able to fill it in cold. Their draft, meanwhile,
is full of evidence about all three fields — they have been making decisions
about audience and purpose in every paragraph without writing them down.

The Constraints guidance generalizes a prompt that worked by hand in Chat:
a checklist of clear, succinct criteria the writer could judge as met or
unmet, no more than a dozen. The venue-specific parts of that prompt are
deliberately not restated — they belong to the document and to whatever the
writer has already put in the brief, both of which are in the request.

The instructions push hard on staying inside the document and omitting a
field rather than guessing at it. The failure mode is worse than a wrong
outline: a plausible invented audience reads as insight, gets accepted
without much scrutiny, and then silently frames every request on every page.
A blank field is visibly blank; a wrong one is not.

The parser is defensive for the same reason parseDocBrief is — it runs on
whatever came back, and a malformed response should cost an error notice
rather than crash a page the writer just opened.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGZQEsAGYQsV7anwsPS6xk
brief_proposal_resolved is the one that matters: it records accepted vs
dismissed per field. Candidates that are almost always accepted unedited
would mean the tool is writing the brief rather than co-creating it, which
is the failure this whole feature has to be measured against.

The candidate text rides in `result` so the consent gate treats it as AI
output. An empty `fields` array is the "nothing to propose" outcome, not a
failure — those are distinguished by the presence of _error.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGZQEsAGYQsV7anwsPS6xk
Reads the current draft and offers candidate wording for each field.

The candidates render below their field rather than in it, dashed and
tinted, so a candidate is never mistakable for something already in the
brief — including when the field is empty, and including when the field is
already filled and the candidate is only a sharper version of it. That is
interface-concepts.md's rule for AI-authored prose: draft material to be
edited, visibly provisional, inert until the writer touches it.

The document is pulled at request time rather than tracked. Reading it is an
Apps Script round-trip on the Google Docs surface, and the page already
holds its own copy for its own requests, so a second useDocContext here
would double every read.

"Nothing to suggest yet" and "nothing to read yet" are visible outcomes with
their own notices — a run that quietly changes nothing reads as a broken
button. The first one's wording points at the finding: a draft that does not
settle who it is for is telling the writer something worth knowing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGZQEsAGYQsV7anwsPS6xk
Records the reasoning the code cannot carry: why this is not a fourteenth
Revise button (every other feature there answers "what is in my document?",
this one answers "what would make it done?"), why the checklist is not a
fourth brief field (interface-concepts.md reserves that vocabulary for the
Charter's Rubric, and a field named "Success criteria" would squat on the
name while providing none of the mechanic), and what is still open — chat
proposing brief updates, and whether the checklist deserves to be its own
gradeable artifact.

The CLAUDE.md note states the invariant rather than the feature, so the next
person editing this code knows which property they would be breaking:
putting a candidate into a field, or into the saved JSON, is how this turns
into a tool that writes the writer's brief for them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGZQEsAGYQsV7anwsPS6xk
@kcarnold
kcarnold force-pushed the claude/paper-acceptance-checklist-preset-rmjjda branch from db56271 to a4de7af Compare July 31, 2026 21:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants