perf(1346): round 2 -- the statusbar and clipboard costs the first budget could not see - #1394
Merged
Conversation
… not see Re-examining #1346 found a second layer larger than what round 1 fixed. The round-1 budget test stubbed _refresh_title, and _refresh_title was smuggling a full statusbar refresh onto the synchronous typing path. What each keystroke still did after round 1: * The statusbar refreshed TWICE (once dragged in synchronously by the title refresh, once from the key-up handler), and its cells each re-marshalled the buffer to compute themselves: word count (full copy + full text pass), line/column (full copy + scan to caret), progress (full copy), page (full copy + scan). ~9 full-document copies and 2 full scans per character. * The abbreviation expander opened the WINDOWS CLIPBOARD on every keystroke. The clipboard is a shared, single-owner cross-process lock; screen readers and clipboard managers poll it, and clipboard_retry deliberately retries up to 10 x 20 ms on contention -- per character typed. Likely the most direct contributor to the reported NVDA/JAWS lag of anything in either round. * SetTitle and the tab label were re-set to identical strings per keystroke, each firing an MSAA/UIA name-change event. The fixes: * try_expand gains clipboard_provider: the clipboard is read only once an abbreviation has matched AND its expansion references ${clipboard} -- approximately never. The UI passes a lazy memoized fetch through the user library, the Quillin-contributed library, and the fill-in fields path. * _refresh_title splits into the cached _refresh_title_bar (native calls skipped when the strings are unchanged) plus the statusbar half. The typing path calls the title-bar half only; the statusbar catches up in the deferred pass. Caret activity coalesces its refresh (_schedule_statusbar_refresh, 90 ms restarting timer) -- holding an arrow key costs one refresh when the caret stops instead of one per key repeat. * Display-only reads (statusbar cells, the table-transition check) use the new _document_text_for_display(): document.text is the same string GetValue would marshal, already in memory, at zero copies. The #269 dead-widget contract is kept via an insertion-point liveness probe. * Document.set_text compares length before content, making the no-op check O(1) for insertions and deletions. Modelled on the same basis as round 1 (UTF-16 round trip per marshal), the synchronous per-keystroke cost drops a further ~85-155x: 200 KB document ~22 ms -> ~0.3 ms per keystroke; 1 MB ~118 ms -> ~1.3 ms -- before counting the removed clipboard contention, which no model captures. The budget test grows from 10 to 16 assertions: typing a plain letter never touches the clipboard, a non-matching trigger never touches it, a matching expansion without ${clipboard} never touches it, the typing path may not call _refresh_title (title-bar half only, no statusbar inside it), caret activity must coalesce, statusbar stats must come from document.text with zero GetValue calls, and an unchanged title must not be re-set. Test fixtures in test_main_frame_navigation now keep the stub document in sync with the stub editor, as the real app does on every EVT_TEXT -- the old fixtures modelled a desync that cannot occur. Suite: 13089 passed, 31 skipped (full tests/unit + tests/stability). GATE-11: four rebaselines with a dated rationale (_rebaseline_2026_08_13_ 1346_round2). Docs: CHANGELOG (the #1346 entry gains the round-2 half), PRD section 11 (budget extended to clipboard/statusbar/title rules), user guide; artifacts re-rendered, parity gate passes. Co-Authored-By: Claude Opus 5 (1M context) <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.
Re-examining #1346 found a second layer larger than what round 1 fixed. The round-1 budget test stubbed
_refresh_title— and_refresh_titlewas smuggling a full statusbar refresh onto the synchronous typing path.What each keystroke still did after round 1
clipboard_retrydeliberately retries up to 10 × 20 ms on contention — per character typed. Of everything in either round, this is the most direct candidate for the reported NVDA/JAWS lag.SetTitleand the tab label were re-set to identical strings per keystroke, each firing an MSAA/UIA name-change event.The fixes
try_expandgainsclipboard_provider: the clipboard is read only once an abbreviation has matched and its expansion references${clipboard}— approximately never. Lazy memoized fetch threaded through the user library, the Quillin-contributed library, and the fill-in fields path._refresh_titlesplits into the cached_refresh_title_bar(native calls skipped when unchanged) plus the statusbar half. The typing path uses the title-bar half; the statusbar catches up in the deferred pass. Caret activity coalesces its refresh (90 ms restarting timer) — holding an arrow key costs one refresh when the caret stops, not one per repeat._document_text_for_display()—document.textis the same stringGetValuewould marshal, already in memory, zero copies. The bug: quill crashes when running from source, post startup wizard, and when pressing ctrl+f4. #269 dead-widget contract is kept via an insertion-point liveness probe.Document.set_textcompares length before content.Measured (same modelling basis as round 1)
— before counting the removed clipboard contention, which no model captures.
Gates
tests/unit+tests/stability: 13089 passed, 31 skipped, 0 failed._rebaseline_2026_08_13_1346_round2).ruff+ scopedmypyclean. Docs (CHANGELOG, PRD §11, user guide) updated, artifacts re-rendered, parity gate passes.EVT_TEXT— the old fixtures modelled a desync that cannot occur.🤖 Generated with Claude Code