Repository navigation
fix(notes): summarise a plain-text transcript with checklist action items (#2666) - #2815
Merged
Merged
Conversation
A transcript with no speaker segments was compiled as a personal note: no checklist rule, "Do NOT list or guess participant names", and for sectioned templates "There is no transcript". It now gets its own wrapper and preamble. Action items are `- [ ] Action — Owner` lines with an owner or deadline only when stated, never a placeholder, and never a table unless the instructions ask for one. Meeting and personal-note compiles are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…note runner (#2666) RunActionOptions gains hasPlainTranscript, which both compilers now receive, including the merge pass of a long transcript run in parts on a local model. A plain-text transcript is speech-to-text like a meeting's, so it also gets the custom dictionary. isMeetingNote stays false for it, so parts still split plain text as prose. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h its invitees for mentions (#2666) runNoteAction now flags a transcript that has no speaker segments as hasPlainTranscript instead of running it as a personal note. Its known people are the user and the note's invitees, so an owner the transcript names becomes a mention. It gets no Meeting Context: without speakers the model can't tell who "I" is, and an invitee list invites guessed owners. Input and content hash are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The plain-transcript preamble also forbids guessing who said something, so templates that attribute by speaker (Stand-up, 1:1, User interview) don't guess a label. It now overrides only the Action Items section's "You" or "Them" fallback, not a custom template's own rules. - Tests: a summary action on a plain note with no summary yet; a meeting with a signed-in user and an invitee (Meeting Context, speaker lines, known people); speaker segments winning on the preamble path. Fixtures use neutral names. - Comments: the parts route's plain paragraph and the plain branch's known people. Inline the flag object in compileTemplatePrompt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A local model's part prompt no longer tells a plain transcript's parts that every line has a speaker label, so a part can't guess an owner the merge would then take as stated. The meeting part prompt is unchanged. - The plain preamble now covers sections that attribute by label or by person (Stand-up's Updates by Person, User interview's quotes): only named people, the rest by topic, and never "[name unclear]" as a speaker or owner. - An upload whose provider returned no segments saved its transcript as the note's text; it now runs as a plain transcript instead of as personal notes. The content hash is unchanged.
Chadpiha
approved these changes
Oct 20, 2026
Chadpiha
left a comment
Collaborator
There was a problem hiding this comment.
Approving. I reviewed this end to end and found no blockers. There's one route for every template or summary-action run (runNoteAction → runBackgroundAction), so a plain-text transcript reaches the new prompts from the callout, TemplatePicker, the action chips and picker, the / menu and shorten/lengthen. That covers personal, team and space notes. Cloud mode applies it too, since api/reason uses the client's systemPrompt as-is.
Unchanged:
- Meetings and personal notes compile as before.
- The
NOTHING_TO_SUMMARIZErule still reaches plain transcripts, through the template prompt, the sectioned footer and the first-summary prompt. - The content hash formula is the same, so no existing summary turns stale.
- Chat actions don't depend on the flag.
Follow-up pushed in 2b0fd03:
- Local-model parts:
partNotesPrompttold every part of a plain transcript that each line has a speaker label. A part could guess an owner, and the merge would take it as stated. Plain transcripts now get a no-label variant; the meeting part prompt is byte-identical. - Per-person sections: the plain preamble now covers sections that attribute by label or by person (Stand-up's Updates by Person, User interview's quotes, 1:1): only named people, the rest grouped by topic, and never
[name unclear]as a speaker or owner. - Uploads with no segments: the transcript is saved as the note's content and the transcript column stays empty. These notes used to get the personal-note prompt; they now run as a plain transcript. The content hash is unchanged.
Verified:
- The new tests fail on the base for the stated reasons and pass on the branch.
- Each follow-up fix was checked by reverting it: its test fails.
- The 18 related test files pass, and the builtinActions DB suite passes 41/41 under Electron's Node.
- typecheck, lint and
format:checkare clean, and the branch merges cleanly withmain.
Not covered:
- Real-model behaviour. Worth a short replay of the plain prompt through
scripts/llm-canary.mjs; the note probe only compiles the meeting prompt today. - Low-priority follow-ups:
- A whitespace-only or
"[]"transcript is classified as plain, because the check isn't trimmed. - Recording again into a note with a plain transcript overwrites that transcript. This predates the PR:
seedSegmentscomes back empty for plain text.
- A whitespace-only or
Chadpiha
marked this pull request as ready for review
October 20, 2026 15:25
boseq
added a commit
that referenced
this pull request
Oct 21, 2026
Resolves CLAUDE.md section 19 (keeps main's plain-transcript line, then this PR's default-template line) and templatePrompts.test.js (the AI Summary invariant now also runs with the plain-transcript preamble from #2815). main's flat plain-transcript test now uses a customized flat AI Summary, since the built-in AI Summary is sectioned on this branch; the sectioned plain-transcript path is pinned by the invariant test.
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.
Summary
This is part 2 of #2666. A note whose transcript was only plain text, with no speaker segments, was summarised as a personal note:
On #2666 this produced an action-item table with no owners. The main trigger was a summary run straight after a recording stopped, which read the recording store's leftover plain text; #2672 fixed that. This PR covers what remains: a transcript that is saved as plain text.
Behaviour
For a plain-text transcript:
- [ ] Action — Owner. A stated deadline goes inside the action:- [ ] Fix the build by Friday — Alice.- [ ] Send the revised proposal.## Meeting Contextblock. Without speaker labels the model can't tell who "I" is, and an invitee list invites guessed owners.[name unclear]is never written as a speaker or an owner.What doesn't change:
2b4706f28. The check covered 142 compiles across every built-in and custom template shape and both summary and chat actions.Where plain-text transcripts come from
meetingTranscriptPersistence.ts).uploadNotes.ts), so the run treats that content as the plain transcript.Granola imports and mobile sync store segments or nothing.
Changes
builtinActions.jsadds two prompts that share one owner rule:PLAIN_TRANSCRIPT_SYSTEM_PROMPT, for flat templates such as AI Summary.PLAIN_TRANSCRIPT_INPUT_PREAMBLE, for section-based and standalone templates and for a first summary from a summary action.[name unclear]never stands in for a speaker or an owner.templatePrompts.js:compileTemplatePromptandcompileSummaryActionPrompttake ahasPlainTranscriptoption. Speaker segments win if both flags are set.actionProcessingStore.tsforwards the flag, including on the merge pass of a long note split into parts on a local model. It appends the custom dictionary for plain transcripts, as it does for meetings. The part prompt (partNotesPrompt) has a variant for a transcript without speaker labels; the meeting part prompt is byte-identical.PersonalNotesView.tsx(runNoteAction) sets the flag when a transcript has no segments and collects the user and the invitees as known people. An upload with an empty transcript column runs its content as the plain transcript, under## Meeting Transcriptrather than as the user's notes.CLAUDE.md§19 documents the three cases.Judgment calls (easy to veto)
Tests
Each new test fails on
2b4706f28for the reported reason and passes here.test/helpers/templatePrompts.test.jstest/config/noteFidelityPrompt.test.js: the fidelity rule appears exactly once in the new compiles.test/stores/actionProcessingChunked.test.jstest/components/personalNotesUndoDiscard.test.jsrunNoteActionflags a plain transcript, with the user and the invitee as known people and no Meeting Context.actionProcessingChunked.test.js): a plain transcript's parts get the no-label prompt; a meeting's parts keep the label prompt.Mutation checks. Each of these fails its new test:
[name unclear]sentences from the preamble;Validation
npm test(Node 24.20): 8,320 tests, 17 failures.metaActivation,noteCreateReconciliation,assistantNoteUndoandnoteEditPersistence, where the localbetter-sqlite3build targets Electron's Node ABI.2b4706f28with the same dependencies.npm run lint,npm run typecheck,npm run format:check: all pass. Lint's warnings are pre-existing, in other files.--scope branch --base 2b4706f28) found no actionable regressions.format:checkare clean, and the builtinActions DB suite passes 41/41 under Electron's Node.Not verified
scripts/llm-canary.mjsprobe pattern.Closes #2666 (part 1 landed in #2672).
🤖 Generated with Claude Code