Skip to content

Stop the layout-cue pass deleting words from ordinary speech - #8

Open
ziyaad-mallick wants to merge 1 commit into
Blueturboguy07:mainfrom
ziyaad-mallick:fix/layout-cues-eat-words
Open

ziyaad-mallick wants to merge 1 commit into
Blueturboguy07:mainfrom
ziyaad-mallick:fix/layout-cues-eat-words

Conversation

@ziyaad-mallick

Copy link
Copy Markdown

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_cues rewrites "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, and
post_process reruns the table on the model's output (:171), repeating the
damage.

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 the
evidence 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 the
whole 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_process also 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.rs had eight): the destructive phrases, the
overlapping-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-tree reports no conflicts against current main.

`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>
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.

1 participant