Skip to content

feat: migrate AskBar input to Lexical to fix WKWebView caret drift#202

Merged
quiet-node merged 1 commit into
mainfrom
worktree-fancy-splashing-honey
Jun 7, 2026
Merged

feat: migrate AskBar input to Lexical to fix WKWebView caret drift#202
quiet-node merged 1 commit into
mainfrom
worktree-fancy-splashing-honey

Conversation

@quiet-node
Copy link
Copy Markdown
Owner

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. ValueSyncPlugin is a two-way bridge between the host's canonical query string and the editor; BehaviorPlugin wires popover key interception (Enter / Arrows / Tab / Escape) and image paste; the contentEditable is plain-text only.
  • view/askbar/CommandNode.ts — a TextNode subclass that renders a recognized slash-command trigger (e.g. /search) in violet inline, modeled on the canonical @lexical/hashtag node.
  • view/askbar/commandMatch.ts — pure, word-boundary-aware matcher that finds the earliest known command trigger in a string. Drives the inline highlight via registerLexicalTextEntity.
  • view/AskBarView.tsx — swaps the textarea for LexicalAskBarInput; slash-popover filtering and selection state are unchanged.
  • App.tsx / App.cssinputRef is now a div; 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 with discrete: true and 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 CommandNode as 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 for LexicalAskBarInput, CommandNode, and commandMatch.
  • 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 innerHTML path, so the migration adds no new markup-injection surface; paste remains capped by image size and count.

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>
@quiet-node quiet-node merged commit adafe47 into main Jun 7, 2026
3 checks passed
@quiet-node quiet-node deleted the worktree-fancy-splashing-honey branch June 7, 2026 06:59
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