Stop the layout-cue pass deleting words from ordinary speech - #8
Open
ziyaad-mallick wants to merge 1 commit into
Open
ziyaad-mallick wants to merge 1 commit into
ziyaad-mallick wants to merge 1 commit into
Conversation
`pre_normalize_layout` rewrites "new line", "next line", "line break" and
"new paragraph" into break markers before the model sees them. It does that
unconditionally, on a whole-word match, and it swallows the following space —
so dictating
the next line of code is broken
produced
the
of code is broken
Two words gone and a line break the speaker never asked for. Same for "the
new line item on the invoice", "read the next line to me", "a line break
down of the costs". LAYOUT_CUES_POST applies the same table again to the
model's output, so the second pass repeats the damage on anything the first
one missed.
The system prompt already handles this correctly — rule 5 says to leave a
mark name as a word "if a mark name is clearly being talked about" — but the
pre-pass destroys the evidence before the model can apply that judgement.
So the pre-pass now declines the cases it cannot judge: a cue immediately
preceded by a determiner, demonstrative or possessive ("the", "a", "every",
"my", ...), or immediately followed by "of", is copied through as ordinary
words and the model decides.
Two details that matter more than they look:
- On a suppressed cue the scan resumes AFTER the whole phrase, not one
character later. Otherwise an overlapping cue fires inside the same noun
phrase: "the next line break here" suppresses "next line", steps forward
one char, then matches "line break" — and loses the words anyway.
- The word list is short on purpose, and it is biased. Some entries ("that",
"her", "same") are also clause-final words, so a real command will
occasionally be suppressed — "scratch that new line let's start over"
keeps "new line" as words. That is the cheaper mistake: a suppressed cue
still reaches a model that is instructed to turn it into a newline, while
a wrongly fired cue deletes what the speaker said and nothing can put it
back. "item" and "number" were left OUT of the after-list for the same
reason — they are what people say right after a genuine break when
dictating a list ("new line number two is the deadline").
Also: post_process only restored the exact markers, so a model that echoed
back "[[ NL ]]" or "[[nl]]" had it pasted at the user's cursor as literal
text. No gate caught that — normalize_tok strips the brackets, leaving "nl",
which looks like a word that was already in the transcript. Near-miss
markers are now restored too; anything else in double brackets is left
alone, since it may be the speaker's own words.
Five tests, including every destructive phrase above, the overlapping-cue
case, and two that the command cases still break the line.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Stop the layout-cue pass deleting words from ordinary speech
The bug
Dictating "the next line of code is broken" pastes "the", a line break, then "of
code is broken" — two words deleted and a break nobody asked for. "The new line
item on the invoice" breaks the same way.
Why it happens
replace_cuesrewrites "new line", "next line", "line break" and "new paragraph"into break sentinels on a whole-word match, unconditionally, and swallows the
following space (
crates/whimpr-core/src/cleanup/mod.rs:244-248, tables at:185-201). Nothing distinguishes a cue talked about from one asked for, andpost_processreruns the table on the model's output (:171), repeating thedamage.
The system prompt already says to leave a mark name as a word when it is clearly
being talked about (
cleanup/prompts.rs:30-31), but the pre-pass destroys theevidence first.
The fix
A cue immediately preceded by a determiner, demonstrative or possessive, or
immediately followed by "of", is copied through as ordinary words and left to the
model (lists at
mod.rs:247-258, guard at:305). The scan then resumes past thewhole phrase, so an overlapping cue cannot fire inside the same noun phrase. The
list is short and biased toward suppressing, because a suppressed cue still reaches
a model told to make it a newline while a wrongly fired one deletes speech.
post_processalso restored only the exact sentinels, so a model echoing"[[ NL ]]" or "[[nl]]" pasted it literally; near-miss markers are now restored
(
:334), and other bracketed text is untouched.Reproduce / verify
cargo test -p whimpr-core cleanup, or dictate "the next line of code is broken".Tests
Five added (
cleanup/mod.rshad eight): the destructive phrases, theoverlapping-cue case, the sentinel restore, and two confirming genuine commands
still break the line. Not run here — no Rust toolchain on this machine.
Branched from cb3617b;
merge-treereports no conflicts against current main.