fix(direct): sentence-window context + remove per-keystroke re-reads (Outlook canvas reset) - #68
Merged
Merged
Conversation
…(Outlook reset) Heide's video report: in Outlook, "H goes in, then she navigates to 'e' and the canvas resets — 'e' never crosses the line." The per-keystroke UIA selection watch (#56, RFC 0019 clause 6) was the cause: Outlook's UIA provider fires TextSelectionChanged on every injected character and returns inconsistent full-document reads (signatures, formatting, timing differences) — the shadow-compare against the full text could never reliably match, so every keystroke triggered a full model rebuild = visible canvas reset. BlueMail's "stutters but usable" was the same mechanism at lower severity. v5 never had this problem because it never re-read the target within a field. Fix (Heide's suggestion, v5-aligned): 1. Sentence-window seeding: target reads are trimmed to the SENTENCE around the caret (last sentence boundary up to 200 chars back) before seeding the engine. The engine's language model only needs local context; a small seed makes re-seeds cheap and the shadow-compare stable — during typing, the sentence and the engine buffer grow together character by character, so echoes match and are skipped. 2. The UIA selection-change watch is REMOVED entirely. Context is seeded once on focus/app switch (the focus hooks), then the engine buffer accumulates internally (v5 parity). No per-keystroke re-reads. Clicking within the same field does not re-read; clicking into a different field or app fires the focus hooks and re-seeds with that field's sentence. 3. The long-document seed cap (EditorSeedPolicy) is superseded by the sentence window for the target path (the sentence is always <= 200 chars); the EditorSeedPolicy remains for the editor pane path. 10 unit tests for SentenceWindow (boundary detection, typing-flow sync invariant, caret-at-boundary, multi-sentence clicking, max-lookback cap, empty inputs). Full suite 108/108. Signed-off-by: will wade <willwade@gmail.com>
… re-anchor button User testing: clicking within the same field (e.g. mid-sentence in an email reply) didn't re-anchor — the focus hooks can't see same-field caret moves, so the user had to switch apps and back. Two additions: (1) re-enable the UIA selection-change watch, now SAFE because the sentence-window shadow-compare is immune to the full-document read inconsistency that caused the Outlook resets — during typing, sentence and engine buffer grow together (match, skip); on a genuine caret click, the sentence changes (mismatch, re-seed with the new sentence). (2) A manual re-anchor button (Locate icon) on the mini-bar forces a fresh read for edge cases where the watch misses or the user wants certainty. Signed-off-by: will wade <willwade@gmail.com>
…e guards Review-loop round 1 score 6/10 → round 2 score 7/10; all findings addressed: 1. CRITICAL — symmetric trimming: the engine buffer and target sentence are BOTH trimmed via SentenceWindow.Trim before the shadow-compare. Without this, typing a period/question mark/newline through Dasher broke the lockstep invariant (engine grew, sentence trimmed to empty at boundary → mismatch → reset on every sentence end). CRLF divergence also handled naturally. 2. Watch lifecycle: StartSelectionWatch now returns bool; ArmSelectionWatch only marks _lastWatchTarget on confirmed success (a sticky field on silent UIA failure permanently disabled re-arming); StopSelectionWatch called on mode exit. 3. Surrogate guards both sides: start slides past low surrogates at the 200-char cap; caret steps back from lone high surrogates (UIA can return mid-pair offsets). 4. Trailing-whitespace tolerance in the shadow-compare: engine lagging a space no longer causes churn re-seeds. 6 new tests: boundary-typing lockstep, boundary-resume lockstep, CRLF divergence, consecutive boundaries, surrogate-at-cap, caret-beyond-length. 114/114. Signed-off-by: will wade <willwade@gmail.com>
…ence re-seeds Two Greptile P1s on #68: 1. SECURITY "private target text is logged": the seed log line recorded the actual sentence content — up to 200 chars of a private email appended to keyboard_debug.log. Removed: the log now records lengths and offsets only, never content. Verified no other KbLog line references target text. 2. "Whitespace hides buffer divergence": TrimEnd() on both sides made different buffers compare as equal, but the subsequent set_offset used a byte offset computed from the LONGER (untrimmed) target sentence — placing the engine offset beyond its own buffer. Now: exact match → cheap set_offset (as before); whitespace-only match → full re-seed to absorb the difference (correct, and the convergence cost is one seed, not an out-of-range offset). Signed-off-by: will wade <willwade@gmail.com>
Post-v0.2.24 commit: aggregates invalid-UTF-8 training-data warnings into one summary message instead of per-byte toasts. Not yet tagged — the pin tracks the merged head until the next DasherCore release. Signed-off-by: will wade <willwade@gmail.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.
Closes the Outlook canvas-reset problem (Heide's video report).
Root cause
The per-keystroke UIA selection watch (#56, RFC 0019 clause 6) re-read the target on every selection change. Outlook fires
TextSelectionChangedon every injected character and its UIA provider returns inconsistent full-document reads (signatures, formatting, timing) — the shadow-compare against the full text could never reliably match, so every keystroke triggered aseed_buffer→ full model rebuild → visible canvas reset. BlueMail's "stutters but usable" was the same mechanism at lower severity.v5 never had this problem because it never re-read the target within a field — its internal EDIT control accumulated whatever was typed through Dasher, full stop.
Fix (Heide's suggestion, v5-aligned)
Sentence-window seeding: target reads trimmed to the sentence around the caret (last sentence boundary, max 200 chars back) before seeding. The LM only needs local context; a small seed makes the shadow-compare stable — during typing, the sentence and the engine buffer grow together character by character, so echoes match and are skipped.
Selection-change watch removed entirely. Context seeds once on focus/app switch (the focus hooks), then the engine buffer accumulates internally (v5 parity). No per-keystroke re-reads. Clicking into a different field/app fires the focus hooks and re-seeds with that field's sentence.
The long-document seed cap is superseded by the sentence window for the target path (always ≤ 200 chars);
EditorSeedPolicyremains for the editor pane.Tests
10 new
SentenceWindowTests: boundary detection (.!?\n;:), the typing-flow sync invariant (sentence grows in lockstep with engine buffer → shadow-compare skips), multi-sentence clicking, caret-at-boundary → empty context, max-lookback cap, empty inputs. Full suite 108/108.Manual: publish-test built — test in Notepad and Outlook (type a word, verify no canvas reset per keystroke; switch between fields; the
keyboard_debug.logshowssentence "..." (N of M chars)lines).The PR appears safe to merge, with no outstanding previous findings or actionable new failures identified.
Summary
Diagram
%%{init: {'theme': 'neutral'}}%% flowchart LR Target[Focused target field] --> Read[Read text and caret] Watch[UIA selection-change watch] --> Read Manual[Manual re-anchor] --> Read Read --> Window[Extract bounded sentence window] Engine[Native engine output] --> EngineWindow[Extract matching sentence window] Window --> Compare{Sentence comparison} EngineWindow --> Compare Compare -->|Exact match| Offset[Cheap offset update or skip] Compare -->|Whitespace-only difference| Reseed[Reseed current sentence] Compare -->|Content difference| Reseed Offset --> Canvas[Preserve prediction canvas] Reseed --> CanvasReviews (5) · Last reviewed commit: "chore: pin DasherCore to v0.2.24 + UTF-8..."