Repository navigation
Post-transcribe: LLM rewrite before paste, tray picker, overlay tag - #40
Conversation
|
Upsun Dispatch™ did not run this request: no model provider API key is registered for this organization. Register one in settings, then trigger again. |
There was a problem hiding this comment.
Note
Reviewed — No blocking findings · 🔵 3 minor points · ⚪ 1 nitpick
🔍 Full review · 7 files reviewed
🔵 Minor points
posttranscribe/posttranscribe_test.go:75—strings.Contains(sys, "Vocabulary:")looks for a header the code never writes.systemPromptwrites"Vocabulary — names and terms the user says often:", so the string never containsVocabulary:. The check passes whether or not empty hints add a vocabulary section, which means it cannot catch the regression it was written for.posttranscribe/posttranscribe.go:159— On a non-200 status, the whole raw response body goes into the error (string(resp.Body)). main.go shows that error verbatim throughtray.SetError("Post-transcribe failed, ..." + err.Error()). A Groq JSON error, or an HTML page from a proxy or captive portal, lands in the tray as one unbounded blob with nothing to shorten it.posttranscribe/posttranscribe.go:27— Two choices rest on measurements and a rejected alternative that appear only in the PR description:DefaultModelis gpt-oss-120b because 20b answered questions instead of rewriting them, andreasoning_effort: lowbecausemediumfixed no more vocabulary. docs/design-notes.md gets no entry. CLAUDE.md says a decision resting on a measurement or a rejected alternative must be recorded there, with a date and version.
⚪ Nitpick
main.go:1172— On the first recording with a profile active,go posttranscribe.Warm()callsclient(). That builds the client withtranscriber.NewTracedClient, which already runsgo tc.Warm()itself.Warmthen sends a second HEAD right away, so the first warm-up makes two parallel requests and may open two connections. Every later recording also sends a warm-up HEAD whether or not an idle keep-alive connection already exists.
Verification
- In stream mode with a profile active, the updates goroutine skips incremental pastes, and finishTranscription's
(!cfg.stream || cfg.post.Active != "")pastes the final text once. - The rewrite is skipped on closeErr and on NoSpeech, and on a Rewrite error the paste falls back to the raw
result.Text. - log.Transcription and setLastRecording still get the raw
result.Text; only clip.SetLastText and the paste get the rewritten text. - PostTranscribeMs is added both to the serial sum and to the felt_latency fields, so unaccounted_ms stays correct.
- The 15 s context timeout governs the request even though TracedClient's own client timeout is 2 min, so a slow Groq reply blocks re-recording for at most 15 s.
The new posttranscribe_test.go tests Rewrite against an httptest fake server: profile choice, user prompts replacing built-ins, the reasoning_effort gating, and the error paths. The main.go wiring (streaming paste suppression, fallback to the raw text, latency fields) has no test, and the empty-hints assertion can never fail.
Review details
- Commit: 3b5a5a6
- Model: claude-opus-5-5
Review 1 of 10 for this pull request · View the full run
|
📋 PR Summary This PR adds an optional LLM rewrite of each transcript before it's pasted. It's off by default. You turn it on by picking a profile from the tray's Post-Transcribe submenu or setting it in Changes
|
There was a problem hiding this comment.
Warning
Changes suggested — 🟡 1 warning · 4 still open (1 nitpick)
🔁 Incremental · 6 files reviewed
Outstanding from earlier reviews:
- 🔵
posttranscribe/posttranscribe_test.go:75: A check that can never fail gives false assurance that empty hints add no vocabulary block. — posttranscribe_test.go:75 still checks for "Vocabulary:", a string systemPrompt never writes. (first raised) - ⚪
main.go:1172: Redundant network requests at record start; harmless but wasteful. — main.go still runsgo posttranscribe.Warm()on each recording, and NewTracedClient still startsgo tc.Warm()itself the first time. (first raised) - 🔵
posttranscribe/posttranscribe.go:162: Users get an unreadable error message whenever the rewrite fails at the HTTP level. — Rewrite still putsstring(resp.Body)unbounded into the error, and that error is shown in the tray. (first raised) - 🔵
posttranscribe/posttranscribe.go:27: A later contributor may switch back to 20b for speed without knowing it was already measured and rejected. — docs/design-notes.md still has no post-transcribe or gpt-oss entry. (first raised)
Verification
postis filled only inside the branch where post-transcribe ran, so for a recording with post-transcribe off,Recording.Poststays zero and saveRecording writes nopost_transcribekey and no post_transcript.txt.- When the rewrite fails,
post.Textstays empty, so the log line keeps four fields while info.json still records the profile, the model and the error. posttranscribe.Modelis now the only placecmp.Or(c.Model, DefaultModel)appears, so the model recorded in info.json is the same model Rewrite sends.
This change adds log_test.go's TestTranscriptionPostTranscribe, which covers a successful rewrite and a failed one, and extends TestTranscription to check that no post_transcript.txt is written. No test covers a rewrite that contains newlines or tabs, or the main.go wiring that fills post.
Review 2 of 10 for this pull request · View the full run
There was a problem hiding this comment.
Note
Reviewed — No new issues found · 5 still open (1 nitpick)
🔁 Incremental · 1 file reviewed
Outstanding from earlier reviews:
- 🟡 #4349630394 —
log/log.go:335: The transcribe log stops being parseable line by line exactly when the most common rewrite profiles are in use. — log.go still adds"\t" + r.Post.Textto the line without escaping, so a rewrite containing newlines or tabs still breaks the one-record-per-line format. - 🔵
posttranscribe/posttranscribe_test.go:75: A check that can never fail gives false assurance that empty hints add no vocabulary block. — posttranscribe_test.go:75 still checks forVocabulary:, a header systemPrompt never writes (it writesVocabulary — ...). (first raised) - ⚪
main.go:1172: Redundant network requests at record start; harmless but wasteful. — main.go:1172 still runsgo posttranscribe.Warm()on every recording, and the first call builds the client with NewTracedClient, which starts its own warm-up too. (first raised) - 🔵
posttranscribe/posttranscribe.go:165: Users get an unreadable error message whenever the rewrite fails at the HTTP level. — On a non-200 status, Rewrite still puts the wholestring(resp.Body)into the error, which then reaches the tray. (first raised) - 🔵
posttranscribe/posttranscribe.go:27: A later contributor may switch back to 20b for speed without knowing it was already measured and rejected. — docs/design-notes.md still has no entry for gpt-oss-120b or reasoning_effort; the model appears only in docs/reference.md. (first raised)
Verification
- The new vocabulary sentences are inside the
if hints != ""branch, so an empty hints list still produces a prompt with no vocabulary section. - The added string concatenation is well-formed: line 86 now ends with
+and joins the three new literals into the sames +=expression. - The new guidance names only terms "from this list", so the cloud→Claude example does not tell the model to swap words that are not in the user's hints.
This change only edits the prompt text, and no test checks the new sentences. The Vocabulary: check in posttranscribe_test.go still looks for a header the code never writes.
Review 3 of 10 for this pull request · View the full run
There was a problem hiding this comment.
Warning
Changes suggested — 🟡 1 warning · 5 still open (1 nitpick)
🔁 Incremental (head + base moved) · 16 files reviewed
Outstanding from earlier reviews:
- 🟡 #4349630394 —
log/log.go:335: The transcribe log stops being parseable line by line exactly when the most common rewrite profiles are in use. — log.go still appendsr.Post.Textunescaped, so a rewrite containing a newline or tab still breaks the one-record-per-line format. - 🔵
posttranscribe/posttranscribe_test.go:77: A check that can never fail gives false assurance that empty hints add no vocabulary block. — posttranscribe_test.go:77 still checks forVocabulary:, but systemPrompt writesVocabulary — names and terms…. (first raised) - ⚪
main.go:1187: Redundant network requests at record start; harmless but wasteful. — main.go still runsgo posttranscribe.Warm(), andclient()→NewTracedClientwarms the connection again on first use. (first raised) - 🔵
posttranscribe/posttranscribe.go:176: Users get an unreadable error message whenever the rewrite fails at the HTTP level. — posttranscribe.go still puts the wholestring(resp.Body)into the error with no length limit. (first raised) - 🔵
posttranscribe/posttranscribe.go:27: A later contributor may switch back to 20b for speed without knowing it was already measured and rejected. — docs/design-notes.md still has no entry on the gpt-oss-120b default or onreasoning_effort: low. (first raised)
Verification
Profileslists the built-ins inbuiltinOrderand leaves out user prompts that shadow a built-in, so the tray entries and the colour assignment are stable and contain no duplicates.choosePostchecksidxagainstpostEntries()while holdingtrayMu, and calls the callback only after releasing the lock, so the click handler cannot deadlock ontrayMu.SetTagfalls back to the first palette colour when the hex is unreadable, andlayoutTaghides the pill when the text is empty, soActive: ""shows no tag.- Each recording reads the overlay tag from
config.Get()at session start, so a profile picked in the tray shows up from the next recording, as the updated reference.md says.
Added in this push: TestEnsureTagColors, TestParseHex, TestPickTagColor, TestProfiles, TestBuiltinOrder and TestChoosePost. The Objective-C overlay drawing and the systray submenu rendering are not covered by any test.
Review 4 of 10 for this pull request · View the full run
There was a problem hiding this comment.
Caution
1 critical finding in this review. Details below.
Caution
Changes requested — 🔴 1 critical · 🟡 4 warnings · 🔵 1 minor point · 4 still open (1 nitpick)
🔁 Incremental · 3 files reviewed
🔴 Critical (P0 — merge blockers) — outside diff range
general· merge-conflictThis PR does not merge cleanly onto the current base branch:
docs/design-notes.mdconflicts. The new "Post-transcribe prompt" section is appended at the end of the file, and the base has changed there too. Rebase or resolve the conflict before merging.
Outstanding from earlier reviews:
- 🟡 #4349630394 —
log/log.go:335: The transcribe log stops being parseable line by line exactly when the most common rewrite profiles are in use. — log/log.go:335 still appendsr.Post.Textunescaped, so newlines or tabs in the rewrite still break the one-record-per-line format. - 🟡 #4350272205 —
main.go:490: A silent config write pins today's defaults, so a future default change (such as the tail-wait trial) never reaches existing users. —ensureTagColors()still runs on every startup (main.go:490) and on reload, and callsconfig.Updatewhenever a built-in profile has no colour, so it still writes the full Settings to disk. - ⚪
main.go:1187: Redundant network requests at record start; harmless but wasteful. — main.go:1187 still runsgo posttranscribe.Warm()on every recording, and the client is still built withNewTracedClient. (first raised) - 🔵
posttranscribe/posttranscribe.go:170: Users get an unreadable error message whenever the rewrite fails at the HTTP level. —complete()still formatsstring(resp.Body)into the error with no truncation. (first raised)
Verification
TestRewriteUserProfileShadowsBuiltinstill works: its new"Vocabulary"check cannot false-positive, because the newrulestext never contains that word.complete()sends thesystemandmodelit is given unchanged, so moving the request out ofRewritekeeps the same request shape and the gpt-ossreasoning_effortgating.- The new design-notes entry gives the date, model, reasoning effort and commit (
c2a4673) behind the prompt and model choices, as CLAUDE.md requires.
This increment changes only the prompt text and one assertion in posttranscribe_test.go. The tests check the request structure, not whether the model follows the new rules. The prompt's quality rests on the offline eval described in docs/design-notes.md, which no automated check reruns.
Review 5 of 10 for this pull request · View the full run
There was a problem hiding this comment.
Warning
Changes suggested — 🟡 1 warning · 9 still open (1 nitpick)
🔍 Full review · 20 files reviewed
Outstanding from earlier reviews:
- 🟡 #4349630394 —
log/log.go:335: The transcribe log stops being parseable line by line exactly when the most common rewrite profiles are in use. — Transcription still appends"\t" + r.Post.Textunescaped, so newlines or tabs in the rewrite still break the one-record-per-line format. - 🟡 #4350272205 —
main.go:490: A silent config write pins today's defaults, so a future default change (such as the tail-wait trial) never reaches existing users. — ensureTagColors still calls config.Update on startup and on Reload whenever a built-in has no colour, which writes every default to config.json. - 🟡 #4350590746 —
posttranscribe/posttranscribe.go:78: Users who dictate commands or paths get them silently changed in the pasted text, against the documented behaviour. —rulesstill has no line telling the model to keep code, paths, URLs and commands as they are. - 🟡 #4350590753 —
posttranscribe/posttranscribe.go:36: Users who pickgrammardo not get the punctuation and grammar fixes the docs describe. — Thegrammarbuilt-in is still "Change nothing more.", and reference.md still describes spelling/punctuation/grammar fixes. - 🟡 #4350590759 —
posttranscribe/posttranscribe.go:79: User profiles and tone profiles can be silently cancelled by the global "do not rewrite" rule, so a chosen profile appears to do nothing. —rulesstill says "do not rewrite, and add nothing", which contradicts the tone profiles and the documented slack prompt. - 🟡 #4350590766 —
posttranscribe/posttranscribe.go:82: Stray tags or a preamble can end up in the user's pasted text. — The reply rule is still only "Reply with the cleaned text only." and complete() only trims whitespace, so echoed tags or a preamble get pasted. - 🔵 #4350590771 —
docs/design-notes.md:2226: Contradictory measurements make the record hard to trust when the prompt is revisited. — The design note still says 0/24 → 24/24, and the PR description still says 0/18 → 18/18. - ⚪
main.go:1187: Redundant network requests at record start; harmless but wasteful. — client() still builds the client through NewTracedClient, which already startsgo tc.Warm(), and Warm() then calls client().Warm() again. (first raised) - 🔵
posttranscribe/posttranscribe.go:170: Users get an unreadable error message whenever the rewrite fails at the HTTP level. — posttranscribe.go:170 still putsstring(resp.Body)into the error, and main.go shows that error in the tray without shortening it. (first raised)
Verification
SetPostTranscribeat startup runs beforetray.Init, somPostis nil and onReady builds the submenu itself; the reload path calls it fromgo reloadCfgCb(), off the main thread.- The post-transcribe rewrite is skipped on
closeErr,!HasTextorNoSpeech; a rewrite error keepstext = result.Text, so batch paste falls back to the raw transcript. - Logs and saved recordings keep
result.Text(raw);clip.SetLastTextand the paste use the rewrittentext. ReleaseToTextaddsPostTranscribeMsto the serial sum, sounaccounted_msstays correct.
This PR adds unit tests: posttranscribe_test.go (Rewrite against an httptest server, Profiles, error paths), log_test.go (fifth field and info.json), overlay_test.go (parseHex, PickTagColor), tray_test.go (choosePost) and main_test.go (ensureTagColors). No test covers the streaming paste path with a profile active, or finishTranscription's paste or fallback logic.
Review details
- Commit: 8f16b54
- Model: claude-opus-5-5
Review 6 of 10 for this pull request · View the full run
Superseded: the latest Upsun Dispatch review no longer requests changes.
There was a problem hiding this comment.
Caution
1 critical finding in this review. Details below.
Caution
Changes requested — 🔴 1 critical · 🟡 1 warning · 10 still open (1 nitpick)
🔁 Incremental · 14 files reviewed
Outstanding from earlier reviews:
- 🟡 #4349630394 —
log/log.go:335: The transcribe log stops being parseable line by line exactly when the most common rewrite profiles are in use. —line += "\t" + r.Post.Textstill writes the LLM output without escaping newlines or tabs. - 🟡 #4350272205 —
main.go:490: A silent config write pins today's defaults, so a future default change (such as the tail-wait trial) never reaches existing users. —ensureTagColors()still runs at startup (main.go:490) and callsconfig.Updatewhenever a built-in profile has no colour. - 🟡 #4350590746 —
posttranscribe/posttranscribe.go:78: Users who dictate commands or paths get them silently changed in the pasted text, against the documented behaviour. —rulesstill has no line telling the model to keep code, paths, URLs or commands as they are. - 🟡 #4350590753 —
posttranscribe/posttranscribe.go:36: Users who pickgrammardo not get the punctuation and grammar fixes the docs describe. —grammaris still "Change nothing more." while docs/reference.md:83 still promises spelling, punctuation and grammar fixes. - 🟡 #4350590759 —
posttranscribe/posttranscribe.go:79: User profiles and tone profiles can be silently cancelled by the global "do not rewrite" rule, so a chosen profile appears to do nothing. —rulesstill says "do not rewrite", which contradicts the tone profiles and the user rewrite prompts. - 🟡 #4350590766 —
posttranscribe/posttranscribe.go:82: Stray tags or a preamble can end up in the user's pasted text. — The reply rule is still just "Reply with the cleaned text only.", andcomplete()only trims the reply, so tags, quotes or a lead-in can still be pasted. - 🔵 #4350590771 —
docs/design-notes.md:2146: Contradictory measurements make the record hard to trust when the prompt is revisited. —⚠️ regressed this push: Not fixed: the design note was deleted outright rather than reconciled with the description, which leaves no record of the measurement at… - 🟡 #4352560288 —
main.go:1331: A streaming user with a profile on loses a whole dictation after a mid-session error or an auto-close, where before this change it was… — The final paste still needscloseErr == nil && !skipPaste, and the stream loop is still gated oncfg.post.Active == "", so streamed text is still lost on a session error or auto-close. - ⚪
main.go:1187: Redundant network requests at record start; harmless but wasteful. —Warm()still callsclient().Warm(), and the firstclient()call buildsNewTracedClient, which already runsgo tc.Warm(), so the first warm-up still sends two requests. (first raised) - 🔵
posttranscribe/posttranscribe.go:170: Users get an unreadable error message whenever the rewrite fails at the HTTP level. — The non-200 error still embeds the wholestring(resp.Body), and main.go passes it totray.SetErrorwithout shortening it. (first raised)
Verification
saveRecordingnow createsrecordings/first and then claims a unique folder withos.Mkdir, adding a-Nsuffix onIsExist, so two saves in the same millisecond no longer overwrite each other.Transcriptionstill writesline + "\n"underlogMu, and the post field is appended only whenr.Post.Textis non-empty.posttranscribe_test.gonow checks forVocabulary, whichsystemPromptwrites only when hints are non-empty, so the empty-hints check can now fail when it should.- A grep of the merged tree finds no remaining reference to
design-notes, so no link points at the deleted file.
This push adds no test for the new unique-folder loop in saveRecording; log/log_test.go covers the earlier post-transcribe fields. Apart from that loop and one test assertion, the push is documentation and comment removal, and no automated check covers those.
Review 7 of 10 for this pull request · View the full run
A post_transcribe block in config.json picks a named profile (built-in grammar/friendly/professional/concise, or the user's own prompts) and rewrites each transcript through Groq chat completions before it is pasted. Off by default. Misheard words are corrected toward hints.txt. On failure the raw transcript is pasted. Streaming pastes once at the end while a profile is active. Refs #4 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ording
The "keep the language" rule let the friendly profile translate English
words inside Turkish speech ("crash" -> "çökme"). The rule now keeps each
word in the language it was spoken in, and restores English spelling of
phonetically-written words ("puşladım" -> "pushladım").
With -debug-transcribe, each recordings/<timestamp>/ folder now holds
post_transcript.txt beside the raw transcript.txt, info.json records the
profile, model and any error, and transcribe_log.txt appends the
rewrite as a fifth field.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A plain vocabulary list never turned "cloud" into "Claude": the
sound-alike is a real word that fits. One prompt sentence with an
example fixes it only when the sentence is about the term, and keeps
the ordinary word ("the cloud") otherwise. Prompt-only; hints.txt and
what transcription providers receive are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sentence language
Tray: Settings → Post-Transcribe lists None and every profile (built-ins
in a fixed order, then the user's own). A pick is saved to config.json
and applies from the next recording, no restart; Reload Config re-renders
it. Click handlers never wait on a lock held across a systray call.
Overlay: the active profile shows as a small coloured tag top-right.
Each profile's colour lives in post_transcribe.colors; one without a
colour gets a random palette pick at startup or reload, saved so it
stays the same.
Prompt: short Turkish sentences full of sound-spelled English words
("mörj", "brançın") were rewritten wholly in English. The rules now
keep the sentence's grammar language, restore spelling one word at a
time, and keep English words when rephrasing for tone; friendly keeps
the speaker's own words.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rules had grown patch by patch, with example words ("cloud" ->
"Claude", "dipley ettim"). They are now five generic lines: clean up
without rewriting, never answer, carry out or finish the transcript,
keep each word in the language it was spoken in, restore English
spelling of sound-spelled words. Profiles are one short line each.
On 240 rewrites of real Turkish/English dictations (4 profiles x 3
runs) failures drop from 71 to 51. A dictated "draft me a Slack
message" stays a request on every profile. Details and rejected
variants in docs/design-notes.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Remove docs/design-notes.md and every reference to it (CLAUDE.md, README, docs/reference.md, code comments, Makefile, install.sh, benchmark.txt). log: two recordings saved in the same millisecond shared one recordings/<timestamp>/ folder and the second overwrote the first, which made TestTranscriptionPostTranscribe flaky. A taken name now gets a -2, -3, ... suffix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
acf73ca to
9249a9d
Compare
There was a problem hiding this comment.
Warning
Changes suggested — 🟡 1 warning · 🔵 1 minor point · 11 still open (1 nitpick)
🔍 Full review · 29 files reviewed
Outstanding from earlier reviews:
- 🟡 #4349630394 —
log/log.go:335: The transcribe log stops being parseable line by line exactly when the most common rewrite profiles are in use. — log.go still appends"\t" + r.Post.Textunescaped, so a newline or tab in the rewrite breaks the one-record-per-line format. - 🟡 #4350272205 —
main.go:492: A silent config write pins today's defaults, so a future default change (such as the tail-wait trial) never reaches existing users. —ensureTagColors()still runs at every startup and reload, and on first launch it callsconfig.Update(a full config.json write) because the built-in profiles have no colours. - 🟡 #4350590746 —
posttranscribe/posttranscribe.go:78: Users who dictate commands or paths get them silently changed in the pasted text, against the documented behaviour. —rulesstill has no line protecting code, paths, URLs or commands, while it does tell the model to fix errors and remove filler. - 🟡 #4350590753 —
posttranscribe/posttranscribe.go:36: Users who pickgrammardo not get the punctuation and grammar fixes the docs describe. — Thegrammarbuilt-in is still "Change nothing more." but docs/reference.md still promises spelling, punctuation and grammar fixes. - 🟡 #4350590759 —
posttranscribe/posttranscribe.go:79: User profiles and tone profiles can be silently cancelled by the global "do not rewrite" rule, so a chosen profile appears to do nothing. —rulesstill says "do not rewrite, and add nothing", which contradicts the tone profiles and the documentedslackrewrite example. - 🟡 #4350590766 —
posttranscribe/posttranscribe.go:82: Stray tags or a preamble can end up in the user's pasted text. — Only "Reply with the cleaned text only." remains: nothing forbids quotes or echoing the<transcript>tags, andcomplete()only trims whitespace. - 🟡 #4352560288 —
main.go:1346: A streaming user with a profile on loses a whole dictation after a mid-session error or an auto-close, where before this change it was… — With a profile active in streaming mode, the only paste still requirescloseErr == nil && !skipPaste, so a mid-session error or an auto-close loses all the streamed text. - 🔴 #4357026741 —
docs/design-notes.md:1: The project's whole decision record is lost as a side effect of a feature PR, and the guards it explained become unexplained code that is… — docs/design-notes.md is still deleted, along with every pointer to it. - 🟡 #4357026745 —
CLAUDE.md:9: Contributors and coding agents lose the rule that kept measurements recorded and dated, so future decisions are made without evidence. — The design-notes rule and its clause on where measurements go are still removed from CLAUDE.md. - ⚪
main.go:1202: Redundant network requests at record start; harmless but wasteful. —NewTracedClientstill callsgo tc.Warm()itself, andposttranscribe.Warm()then sends a second HEAD on every recording. (first raised) - 🔵
posttranscribe/posttranscribe.go:170: Users get an unreadable error message whenever the rewrite fails at the HTTP level. — posttranscribe.go:170 still puts the wholestring(resp.Body)into the error that the tray shows. (first raised)
Verification
choosePostdrops the tray lock before calling the callback, andrenderPostChecksholds no lock during systray calls, so a click handler cannot deadlock againstrefreshPostMenu.saveRecording's Mkdir loop with a-Nsuffix stops two saves in the same millisecond from sharing a folder, and returns any error other than 'already exists'.- In streaming mode with a profile active, the clipboard save still happens:
go saveClip()runs after recording ends, sofinishTranscription's<-clipChcannot block forever. ReleaseToTextaddsPostTranscribeMsto the serial sum, so the rewrite's time is not counted asunaccounted_ms.
New unit tests cover profile resolution and HTTP error handling (posttranscribe_test.go), log fields (log_test.go TestTranscriptionPostTranscribe), colour assignment (main_test.go TestEnsureTagColors), the palette (overlay_test.go) and tray selection (TestChoosePost). They run under make test (go test -race ./...). No test covers the streaming paste path, the overlay drawing, or concurrent config access.
Review details
- Commit: 9249a9d
- Model: claude-opus-5-5
Review 8 of 10 for this pull request · View the full run
A multi-paragraph LLM rewrite broke transcribe_log.txt's one-record-per-line, tab-separated format. post_transcript.txt keeps the real text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…batim The base rules tell every profile to fix phonetic errors, so a dictated command or path could be "corrected". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ript> wrapper Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… tray Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ensureTagColors ran at startup and on Reload Config and saved the whole Settings for every user, pinning implied defaults even for users who never use post-transcribe. Now a profile gets its colour when it is picked in the tray or, as a fallback, when a recording starts with it active. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…irst The first Warm created the TracedClient, whose constructor already warms it, then warmed it again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With a streaming provider and a post-transcribe profile, live pastes are off and the only paste is at the end. A mid-session error (Close returns the committed text with the error) or a silence auto-close dropped the whole dictation, which without a profile had been pasted live. Now the raw text is pasted and kept as last text on an error, and an auto-close pastes as the live path did. Batch providers and recordings without a profile are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Note
Reviewed — No new blocking findings · 🔵 1 minor point · 7 still open (1 nitpick)
🔁 Incremental · 8 files reviewed
🔵 Minor point
log/log.go:343—lineEscaperturns newline, CR and tab into the two-character sequences\n,\rand\t, but it leaves a literal backslash as it is. A rewrite that already contains the text\ntherefore looks the same in transcribe_log.txt as one that contained a real line break. Dictated code is the likely trigger:printf("a\n"), or a Windows path such asC:\new\tmp(contains\nand\t). Both kinds of text land in the fifth field as identical bytes, so the field cannot be decoded back to the text that was pasted. The new rule says to keep code and paths exactly, which makes this input more likely.
Outstanding from earlier reviews:
- 🟡 #4350590753 —
posttranscribe/posttranscribe.go:36: Users who pickgrammardo not get the punctuation and grammar fixes the docs describe. — Thegrammarbuilt-in is still just "Change nothing more." and nothing asks for punctuation or grammar fixes, while docs/reference.md still says "spelling, punctuation, grammar". - 🟡 #4350590759 —
posttranscribe/posttranscribe.go:79: User profiles and tone profiles can be silently cancelled by the global "do not rewrite" rule, so a chosen profile appears to do nothing. — The rules still say "Keep the speaker's words… do not rewrite". That rule wraps tone changes (friendly/professional), "Make it concise" and any user profile; only the docs example was softened. - 🔴 #4357026741 —
docs/design-notes.md:1: The project's whole decision record is lost as a side effect of a feature PR, and the guards it explained become unexplained code that is… — docs/design-notes.md is still deleted (docs/ holds only reference.md), and the pointers to it are still removed. - 🟡 #4357026745 —
CLAUDE.md:9: Contributors and coding agents lose the rule that kept measurements recorded and dated, so future decisions are made without evidence. — CLAUDE.md still drops the design-notes rule and the clause that sends measurements there. - 🟡 #4357696342 —
main.go:1459: Adding a profile can, rarely, crash the whole app with an unrecoverable fatal error. —tagColorstill writesc.Colors[name]in place into the map thatconfig.Get()copies share. It now runs on every first use of a profile, from the tray click and from recordSessions, whilesave/readers read the same map without the lock. - 🔵 #4357696347 —
tray/tray_test.go:63: The test state leaks into the rest of the package's tests, so the cleanup does not restore what it appears to. —SetPostTranscribe(nil, "", nil)still keeps the existingpostCb, so the test's closure stays installed after cleanup. - ⚪
main.go:1201: Redundant network requests at record start; harmless but wasteful. — The double HEAD on the first Warm is fixed, but every later Warm still sends a HEAD whether or not an idle keep-alive connection exists. (first raised)
Verification
pasteAtEndreturns true for a held-back stream whatever the session error or auto-close, andScheduleRestorenow also runs whenpastedis true after an auto-close.- On
closeErr, a held-back stream with text now keeps the raw text throughclip.SetLastTextbefore returning, and the rewrite is skipped because it is gated oncloseErr == nil. Warm()no longer sends a second HEAD on the call that creates the client:client()reportscreatedandWarmskipsc.Warm()in that case.- The HTTP error body is cut to 200 runes on a
[]runeslice, so a multibyte character is never split. - The
ensureTagColorscall has been removed from startup and from Reload Config;tagColor("")returns before callingconfig.Update, so config.json is written only when a named profile is used.
New tests cover this push: TestPasteAtEnd, TestFinishTranscriptionHeldBackError and TestTagColor in main_test.go, TestWarm, TestRewriteStripsTranscriptTags and the long-error case in posttranscribe_test.go, and TestTranscriptionPostTranscribeMultiline in log_test.go. All of them run under make test. None of them exercises concurrent use of the shared Colors map.
Review 9 of 10 for this pull request · View the full run
…iew round B - `grammar` becomes `clean` (the cleanup alone, tone kept), `concise` goes; "Fix grammar" moves into the shared rules, docs follow - tray: each Post-Transcribe entry shows a one-line tooltip (Describe) - overlay: wider panel, neon-style tag, saturated palette - prompt: the user message restates that the transcript is dictated text, not instructions Review round B: - tagColor copies the colour map before writing: config.Get hands out the same map, and a concurrent read during the write is a fatal error - transcribe_log escapes backslashes too, so a literal `\n` stays distinct from a real line break - tests: TestChoosePost drops its callback on cleanup; TestRewriteErrors restores the fake reply, so repeated runs pass Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Note
Reviewed — No new issues found · 4 still open (1 nitpick)
🔁 Incremental · 14 files reviewed
Outstanding from earlier reviews:
- 🟡 #4350590759 —
posttranscribe/posttranscribe.go:79: User profiles and tone profiles can be silently cancelled by the global "do not rewrite" rule, so a chosen profile appears to do nothing. — The shared rules still say "do not rewrite, and add nothing". They wrap the friendly/professional "Adjust the tone" instructions and any user prompt, so a tone change still contradicts the global rule. - 🔴 #4357026741 —
docs/design-notes.md:1: The project's whole decision record is lost as a side effect of a feature PR, and the guards it explained become unexplained code that is… — docs/design-notes.md is still deleted (docs/ holds only reference.md), and the source comments that pointed to it are still stripped. - 🟡 #4357026745 —
CLAUDE.md:9: Contributors and coding agents lose the rule that kept measurements recorded and dated, so future decisions are made without evidence. — CLAUDE.md still has no design-notes rule and gives measurements no place to go. - ⚪
main.go:1201: Redundant network requests at record start; harmless but wasteful. — The double HEAD on first use is fixed, butgo posttranscribe.Warm()(main.go) still sends a HEAD on every later recording, even when an idle keep-alive connection already exists. (first raised)
Verification
tagColornow clonesc.Colorsinside theconfig.Updateclosure before writing, so the map thatconfig.Gethanded out is never changed in place.posttranscribe.Warmsends its own HEAD only whenclient()did not just create the client, so the first warm-up no longer sends two requests in parallel.- The renamed built-ins (
clean,friendly,professional) match acrossBuiltin,builtinOrder,descriptions, docs/reference.md and the updated tests, andTestBuiltinOrdernow pins thatdescriptionscovers every built-in. TestChoosePostcleanup now setspostCb = nilundertrayMu, so the test's callback no longer leaks into later tray tests.lineEscaperescapes the backslash first among its pairs, so a literal\nin a rewrite stays distinct from an escaped line break, and the multiline test covers it.
This pass changes unit tests: log/log_test.go (backslash escaping), main_test.go TestTagColor, posttranscribe_test.go (descriptions coverage, profile renames) and tray_test.go TestChoosePost. The overlay neon restyle in overlay_darwin.m and the new tray tooltips (SetTooltip) have no automated test; the only check is the manual overlaydemo.
Review 10 of 10 for this pull request · View the full run
All findings are fixed or answered.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rewrite runs while the recorder is held, so a down provider or a stalled network made each dictation wait the full timeout before the raw text was pasted, and refused the hotkey meanwhile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
transcribe_log.txt is one tab-separated record per line. The rewrite was escaped but the raw transcript was not, so a transcript with a tab or a line break shifted the columns. transcript.txt keeps the real text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A typo in post_transcribe.active gave the name an overlay colour, showed its tag, and made every recording run a rewrite that failed with "unknown profile" and set a tray error. posttranscribe.Resolve clears an Active that names no profile; main uses it for the overlay tag, the recording config and the tray, and warns once on load and reload. config.json keeps the name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tray's post-transcribe pick ran config.Update for Active, then tagColor ran a second one on a first pick: two config.json writes in the click handler. setTagColor works on *config.Settings, keeps the copy-on-write of the Colors map, and serves both tagColor and the tray callback, which now does a single config.Update. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…comment Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closes #4
What
An optional LLM pass rewrites each transcript before it is pasted. Off by default.
config.json. A pick is saved and applies from the next recording — no restart.post_transcribe.colorsholds each profile's colour; a profile without one gets a random palette pick at startup/reload, saved so it stays the same.groqkey fromcredentials.json; any transcription provider.hints.txt, including terms heard as ordinary words ("cloud" → "Claude" only when the sentence is about Claude).-debug-transcribe: eachrecordings/<timestamp>/folder keepstranscript.txt(raw) besidepost_transcript.txt;info.jsonrecords profile, model and any error;transcribe_log.txtappends the rewrite as a fifth field.felt_latencygainspost_transcribe_ms.Where
posttranscribe/— profiles (fixed built-in order), prompt rules, Groq call (reusestranscriber.TracedClient).config/—PostTranscribeschema (here to avoid atranscriber → config → posttranscribecycle).tray/— Post-Transcribe submenu; click handlers never wait on a lock held across a systray call (the deadlock class fixed in fix: blocking-bug and silence-warning fixes #38).overlay/—SetTag+ palette (Apple dark-mode colours minus red/amber, which mean Recording/Silent).main.go— warm on record start, rewrite before paste, tag per recording, colour assignment.log/— post-transcribe fields in saved recordings.Measured (2026-10-22, Groq gpt-oss-120b,
reasoning_effort: low)friendly, 8 runs each, before → after the language rules: Turkish sentence flipped to English 1/8 → 8/8 kept; "crash" kept 5/10 → 8/8. Still open: "silence detection" translated to Turkish ~3 in 8.Test
go test -race ./...passes. Manual: pick a profile in the tray, dictate, check the overlay tag and the pasted text.🤖 Generated with Claude Code