You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(server): a C# dictionary term no longer rewrites standalone letters - #35700
Local speech correction strips symbols from dictionary terms to build fuzzy-match keys, so C# (and C++) became the one-letter key "c", and every standalone "c" equalled that key and was rewritten. The review item was correct: the previous head dropped any key under 4 characters that lost a symbol, which also stopped A.I., I/O, Dr. and AT&T from correcting their spoken forms, a regression from main. This rework narrows the condition to key.length < 2 && lostSymbols, so only terms that collapse to a single letter are left out of fuzzy matching. Typing the term itself ("C#") is still handled by the unchanged exact-spelling pass, and cloud transcription is unaffected. The only regex involved is the existing /\s+/gu over dictionary terms (50 characters at most each), which runs in linear time.
On origin/main cc97303 I added a { term: "C#", aliases: [] } row and the sentence "vitamin c and plan c." to the existing dictionary test in apps/server/src/speech/SpeechService.test.ts. The test passes text through SpeechService.transcribe and startStream().finish; the model returns that text unchanged and has no vocabulary support, so local correction runs. Running vp test run src/speech/SpeechService.test.ts -t "honors exact aliases" returned "... vitamin C# and plan C#." where "... vitamin c and plan c." was expected. I also loaded customWords.ts directly with node --experimental-strip-types, as the reporter's script does. makeSpeechTextCorrector(['C#'], [{term:'C#',aliases:[]}]) turned "vitamin c and plan c" into "vitamin C# and plan C#" and "I write c sharp" into "I write C# sharp". applySpeechAliases, the cloud path, left both unchanged. I did not run a full audio transcription with a downloaded local model; the defect is in deterministic text cleanup after the model.
Evidence
In the real web app, select local Parakeet transcription, add C# without aliases to the Voice dictionary, and use Test model with the same synthetic recording: “vitamin c and plan c. I write c sharp.” Finish recording and inspect the transcript. Both runs used fresh state, Chromium, a 1280×800 viewport, and light theme. Recognition was real; only microphone audio was synthetic.
scenarios:prove exited 0: the scenario failed on base and passed on head.
Set VOICE_EVIDENCE_AUDIO to the generated WAV when running the scenario. The recognizer varied in the final phrase, but the unrelated C letters gained # only on base.
Not shown:
Non-streamed batch transcription, cloud transcription, and other dictionary terms were not exercised; this proof covers real local streamed dictation with C#.
Tests
vp test run src/speech/ in apps/server: 9 files, 82 tests passed, including the repro test from factory/repro-35686. A direct node run of makeSpeechTextCorrector([term], [{term, aliases: []}]): with C#, "vitamin c and plan c" and "I write c sharp" stay unchanged and "C#" is kept; with C++, "plan c" stays unchanged. A.I. turns "a i and ai" into "A.I. and A.I.", I/O turns "the i o layer and io" into "the I/O layer and I/O", Dr. turns "dr smith" into "Dr. smith", AT&T turns "at t" into "AT&T", and R&D turns "r and d" into "R&D", all the same as main. The pre-commit hook passed vp fmt and vp lint.
Not in this change
Pre-existing typecheck errors in apps/server/src/workspace/WorkspaceSearchIndex.ts and WorkspaceSearchIndex.test.ts on origin/factory/repro-35686 (base cc97303).
Existing over-matches on main that the review left for a follow-up: "us" becoming "U.S." (run by the reviewer) and ".NET" matching "net" (expected by the same logic, not run here).
Existing typecheck errors in apps/server/src/workspace/WorkspaceSearchIndex.ts and its test, which this branch does not touch.
Who did the work
Written by Claude Opus 5.5, in Claude Code
Reviewed by GPT-6.1 Sol, in Codex, and by Claude Opus 5.5, each on its own
Proof captured by GPT-6.1 Sol and checked by Claude Opus 5.5
Local speech correction stripped symbols from dictionary terms before
fuzzy matching, so C# became the one-letter key "c" and every standalone
c was rewritten. Short keys that lost symbols are now skipped for fuzzy
matching; exact spellings and aliases still apply.
Fixes#35686
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only skip fuzzy keys that a term's symbols reduce to a single letter
(C#, C++). Terms such as A.I., I/O, Dr. and AT&T match their spoken
forms again, as on main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Macroscope's review found this PR approvable — This narrows speech vocabulary matching so terms such as C# no longer rewrite standalone letters. Explicit dictionary aliases remain unchanged, and the adjustment is confined to transcript correction.
You can add or adjust custom eligibility rules. Learn more.
No actionable comments were generated in the recent review.
Walkthrough
The speech corrector now filters certain short keys produced by symbol removal. A regression test checks that standalone “c” text remains unchanged when “C#” is a custom word.
Changes: Speech dictionary matching
Layer / File(s)
Summary
Filter short custom-word keys apps/server/src/speech/customWords.ts, apps/server/src/speech/SpeechService.test.ts
customWordKeys processes original terms and ampersand-expanded forms, and omits certain keys shorter than two characters. The test checks that “vitamin c and plan c” stays unchanged with “C#” in the custom-word options.
The change stops dictionary terms such as C# from rewriting unrelated standalone letter c in local transcripts. It adds a regression test for that case. No merge-blocking risk is evident.
Pre-merge checks: 4
Check name
Status
Explanation
Title check
The title names the main change: a C# dictionary term no longer rewrites standalone letters. It uses the conventional commit format and matches the linked issue and the PR objectives. The title is spe…
Linked Issues check
Issue #35686 requires that adding C# to the Voice dictionary must not rewrite unrelated standalone letters in local transcripts. The diff to customWordKeys in apps/server/src/speech/customWords.ts…
Out of Scope Changes check
The whole-PR diff against the merge base cc97303 contains two changed files. apps/server/src/speech/customWords.ts contains the fuzzy-key fix for #35686. apps/server/src/speech/SpeechService.test…
Description check
The description is thorough and follows the template's intent. It gives a clear problem statement (C# and C++ turn standalone "c" into the one-letter key, rewriting unrelated letters), a change descri…
Full details: Title check
Explanation
The title names the main change: a C# dictionary term no longer rewrites standalone letters. It uses the conventional commit format and matches the linked issue and the PR objectives. The title is specific and clear, and it does not use vague terms.
Full details: Linked Issues check
Explanation
Issue #35686 requires that adding C# to the Voice dictionary must not rewrite unrelated standalone letters in local transcripts. The diff to customWordKeys in apps/server/src/speech/customWords.ts drops a fuzzy-match key when it has fewer than two characters and symbol removal caused the reduction. For C# and C++, the key c is dropped, so vitamin c and plan c stays unchanged. The diff keeps keys of two or more characters, so A.I., I/O, Dr., and AT&T keep their local correction. A single-letter term without symbols (for example C) is kept because lostSymbols is false. The test change in apps/server/src/speech/SpeechService.test.ts adds C# with the spoken text vitamin c and plan c, and it expects those words to remain unchanged. The cloud alias-only path is not changed. The PR author reports a passing vp test run src/speech/ run and direct corrector checks. I did not run these checks. The source diff supports the fix.
Full details: Out of Scope Changes check
Explanation
The whole-PR diff against the merge base cc97303 contains two changed files. apps/server/src/speech/customWords.ts contains the fuzzy-key fix for #35686. apps/server/src/speech/SpeechService.test.ts contains the regression test for the same defect. Both changes implement the linked issue. The PR description lists typecheck errors in WorkspaceSearchIndex files and existing over-matches (us, .NET) as pre-existing and not changed here. The reviewed diff shows no changes to those files, so they do not count as out-of-scope changes.
Full details: Description check
Explanation
The description is thorough and follows the template's intent. It gives a clear problem statement (C# and C++ turn standalone "c" into the one-letter key, rewriting unrelated letters), a change description (narrowing the skip condition to key.length < 2 && lostSymbols), and a scope tie to the linked issue #35686. Verification is detailed: focused test runs, direct corrector checks, regression checks for A.I., I/O, Dr., AT&T and R&D, and a real-app before/after scenario with evidence. It also states what was not checked. The template's title guidance is not shown in the description, but the scope and verification sections are substantive. Minor gap: the Scope section does not restate the bug-fix framing in the template's terms, and the agent attribution is present at the end as the template requests.
Finishing Touches
Generate unit tests (beta)
Commit to this branch
Create a new PR
Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
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
size:S10-29 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.
1 participant
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.
Local speech correction strips symbols from dictionary terms to build fuzzy-match keys, so C# (and C++) became the one-letter key "c", and every standalone "c" equalled that key and was rewritten. The review item was correct: the previous head dropped any key under 4 characters that lost a symbol, which also stopped A.I., I/O, Dr. and AT&T from correcting their spoken forms, a regression from main. This rework narrows the condition to
key.length < 2 && lostSymbols, so only terms that collapse to a single letter are left out of fuzzy matching. Typing the term itself ("C#") is still handled by the unchanged exact-spelling pass, and cloud transcription is unaffected. The only regex involved is the existing /\s+/gu over dictionary terms (50 characters at most each), which runs in linear time.Fixes #35686
Reproduction on main
On origin/main cc97303 I added a { term: "C#", aliases: [] } row and the sentence "vitamin c and plan c." to the existing dictionary test in apps/server/src/speech/SpeechService.test.ts. The test passes text through SpeechService.transcribe and startStream().finish; the model returns that text unchanged and has no vocabulary support, so local correction runs. Running
vp test run src/speech/SpeechService.test.ts -t "honors exact aliases"returned "... vitamin C# and plan C#." where "... vitamin c and plan c." was expected. I also loaded customWords.ts directly withnode --experimental-strip-types, as the reporter's script does. makeSpeechTextCorrector(['C#'], [{term:'C#',aliases:[]}]) turned "vitamin c and plan c" into "vitamin C# and plan C#" and "I write c sharp" into "I write C# sharp". applySpeechAliases, the cloud path, left both unchanged. I did not run a full audio transcription with a downloaded local model; the defect is in deterministic text cleanup after the model.Evidence
In the real web app, select local Parakeet transcription, add
C#without aliases to the Voice dictionary, and use Test model with the same synthetic recording: “vitamin c and plan c. I write c sharp.” Finish recording and inspect the transcript. Both runs used fresh state, Chromium, a 1280×800 viewport, and light theme. Recognition was real; only microphone audio was synthetic.scenarios:proveexited 0: the scenario failed on base and passed on head.Scenario driver · Audio generator · Identical input audio
Set VOICE_EVIDENCE_AUDIO to the generated WAV when running the scenario. The recognizer varied in the final phrase, but the unrelated C letters gained # only on base.
Not shown:
Tests
vp test run src/speech/in apps/server: 9 files, 82 tests passed, including the repro test from factory/repro-35686. A direct node run of makeSpeechTextCorrector([term], [{term, aliases: []}]): with C#, "vitamin c and plan c" and "I write c sharp" stay unchanged and "C#" is kept; with C++, "plan c" stays unchanged. A.I. turns "a i and ai" into "A.I. and A.I.", I/O turns "the i o layer and io" into "the I/O layer and I/O", Dr. turns "dr smith" into "Dr. smith", AT&T turns "at t" into "AT&T", and R&D turns "r and d" into "R&D", all the same as main. The pre-commit hook passed vp fmt and vp lint.Not in this change
Who did the work
Run by the t3code factory in T3 Code.