feat: migrate AskBar input to Lexical to fix WKWebView caret drift#202
Merged
Conversation
Signed-off-by: Logan Nguyen <lg.131.dev@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.
Overview
Replaces the AskBar's transparent-textarea + highlight-mirror input with a single Lexical-backed
contentEditable. The two-layer overlay drifted the caret off the rendered glyphs in WKWebView once content wrapped or scrolled, because the textarea's wrapping geometry and the mirror's never stayed in perfect lockstep. A single editable element puts the caret in the same element as the text, so it can never drift.What changed
view/askbar/LexicalAskBarInput.tsx— the editor.ValueSyncPluginis a two-way bridge between the host's canonicalquerystring and the editor;BehaviorPluginwires popover key interception (Enter / Arrows / Tab / Escape) and image paste; the contentEditable is plain-text only.view/askbar/CommandNode.ts— aTextNodesubclass that renders a recognized slash-command trigger (e.g./search) in violet inline, modeled on the canonical@lexical/hashtagnode.view/askbar/commandMatch.ts— pure, word-boundary-aware matcher that finds the earliest known command trigger in a string. Drives the inline highlight viaregisterLexicalTextEntity.view/AskBarView.tsx— swaps the textarea forLexicalAskBarInput; slash-popover filtering and selection state are unchanged.App.tsx/App.css—inputRefis now adiv; the manual textarea height resets are gone (the contentEditable auto-sizes via CSS, capped at 144px); window-drag handling now treats any[contenteditable="true"]region as interactive so drag-select works inside the input.How it works
The host owns the canonical
query. After any editor-originated edit the host value already equals the editor text, so the down-sync is a no-op and the caret stays put; it only rewrites the editor for genuine external changes (command completion, clear-on-submit). Those external rewrites commit withdiscrete: trueand are guarded by a flag so they never echo back as a spurious change, which keeps the editor in lockstep with the host during rapid clear→restore cycles (submit-then-cancel).Slash-command triggers highlight inline as a Lexical text entity: matched text is swapped into a
CommandNodeas you type and back to plain text when it stops matching, so the violet token tracks the native caret with no overlay layer.Testing
bun run test:coverage— 1549 tests pass, 100% line/branch/function/statement coverage, including dedicated suites forLexicalAskBarInput,CommandNode, andcommandMatch.bun run typecheck,bun run lint:frontend,bun run format:check— clean.bun run build:frontend— succeeds.Note: the input is plain-text only with no
innerHTMLpath, so the migration adds no new markup-injection surface; paste remains capped by image size and count.