fix(search): stop lingering highlights on document switch and 1→0 clear - #17
Merged
Merged
Conversation
Two distinct causes of stale search highlights: - reset() (called by loadFile after re-rendering) never resynced its internal query with the input field, so switching documents re-ran the previous term on the freshly rendered content — even when the field was empty. It now reads ui.input.value, the source of truth. - On the CSS Custom Highlight path (WKWebView), removing the last highlight from the registry does not always repaint the glyph, most visibly when the query goes from 1 char to 0. forceHighlightRepaint() nudges an imperceptible container repaint after clearing. Adds regression tests for both, including one through the real input-event + debounce path.
nprudhomme
added a commit
that referenced
this pull request
Aug 13, 2026
* chore(release): v0.11.0 Bump version to 0.11.0 and document the user-facing changes since 0.10.0: native File/View menus with Open Recent (#19), fast document open with in-memory cache, cloud-sync UX and a debug HUD (#18), and a search highlight fix on document switch / 1->0 clear (#17). * fix(ui): stop transparent modal backdrops from locking the window A modal backdrop could be left in `display: flex` while fully transparent, covering the whole window at z-index 2000. It swallowed every click and scroll while staying invisible, and because the close path had already detached its listeners, the user had no way to dismiss it — the app was unusable until restart. Reported on 0.10.0: no clicks on Open Folder, no file selection in the sidebar, no scrolling, while the native menu bar still worked. The backdrops were shown and hidden through deferred callbacks. WebKit suspends both animation frames and timers while a window is occluded, so neither the reveal nor the hide is guaranteed to run — leaving the layer displayed but never marked `visible`. Make interactivity follow opacity in CSS: an overlay without `.visible` is now click-through and hidden from the tab order and the accessibility tree. That is the load-bearing guarantee — it holds whatever the reason the layer got stuck, including a keyboard user reaching a destructive button inside an invisible dialog. Defence in depth on top of it: - reveal synchronously (forced reflow, then class) instead of from a frame callback, so a dialog opened on an occluded window is still visible and therefore dismissable; - track an open generation so a pending hide never acts on a backdrop a newer open has claimed, in both confirm-dialog and Preferences; - focus the trap immediately, since callers now reveal before trapping. Tests cover the two suspension modes that produce the field state: frames never delivered, and timers never delivered. Claude-Session: https://claude.ai/code/session_01NcJuB2iR4WEfuQVLpYqWi4 * docs(changelog): record multi-window support in 0.11.0 PR #22 landed without a changelog entry. Covers the new windows, the native Window menu, the arrangement commands, and the switch from broadcast to frontmost-window menu delivery. Claude-Session: https://claude.ai/code/session_01NcJuB2iR4WEfuQVLpYqWi4 * docs(changelog): set the 0.11.0 release date The heading still carried the date the release branch was prepared. Claude-Session: https://claude.ai/code/session_01NcJuB2iR4WEfuQVLpYqWi4
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.
Problem
Two distinct causes of stale search highlights that survived #13:
Ghost highlights on document switch.
reset()(called byloadFileafter re-rendering the new document) never resynced its internalquerywith the search field. Switching documents re-ran the previous term on the freshly rendered content — even when the field had been emptied — leaving yellow highlights with an empty search box.One glyph lingers on 1 char → 0. On the CSS Custom Highlight path (WKWebView), removing the last highlight from
CSS.highlightsdoes not always repaint the affected glyph. Most visible when the query goes from a single character to empty. The JS state and registry are correctly cleared (proven by a test through the real input-event + debounce path) — this is a WebKit repaint quirk.Fix
reset()now readsui.input.value(the input is the source of truth in the app; the debounce reads it too), so a new document never re-highlights a stale query.forceHighlightRepaint()nudges an imperceptible container opacity toggle + reflow after clearing highlights on the CSS-highlights path, invalidating the stale WebKit paint.Tests
reset() drops a stale query once the field is empty (document switch)clears highlights when a 1-char query is deleted to empty via input events(real event path, CSS-highlight environment)resettest aligned to real usage (input as source of truth)Full suite: 295 passing,
tsc --noEmitclean.🤖 Generated with Claude Code