Share one fixed eight-column tab-stop contract across core and GPU renderers. Consolidate byte-to-display-column accounting, expand GPU code tabs with source provenance, align caret/hit/decoration geometry, and refresh minimap projection on edits.
InstanceMessage::CompletionPopup {buffer_id, anchor: Option<u64>,
prefix_len, rows: Vec<CompletionPopupRow{label, kind, detail}>,
selected, total} -- the first byte-anchored popup on the wire: the
frontend maps byte -> glyph rect locally (the caret precedent), so the
instance never learns a pixel. Rows are display-only; accept resolves
daemon-side via dispatch_completion_key, so insert text never ships.
PROTOCOL_VERSION 14 -> 15, SUPPORTED extended; postcard round-trip
(open + closed shapes) and version-pin/ladder tests updated.
Producer: semantic_render::completion_popup_msg, the family pattern
(per-buffer cached-compare, active-buffer only, first-sight-closed
stays silent) with one new rule -- the session is WINDOW-stamped and
this state is per-frontend, so only the frontend whose own window
owns the session sees it open: a popup opened by TUI typing never
renders in an attached GPU and vice versa. Windowed rows share the
TUI overlay's POPUP_MAX_ROWS. Daemon-gated >= 15 (a v14 peer still
completes via the key round-trip, it just gets no GPU dropdown).
GPU consumption follows in this branch.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Five findings from the manual validation pass, all in-branch:
1. LSP-only words never queried the server: the auto-open path fired
request_completion only when the sync providers already produced
rows. An empty sweep now leaves a pending session and the request
always fires; isIncomplete responses re-request on further typing.
Corollary: attachment_for_request now flushes-if-attached but NEVER
attaches -- the first cut wrapped attached_for_active, which spawns
servers on demand, i.e. per-keystroke spawn attempts in unattached
buffers (wedged the parallel m4 suite; serial ran 3x slower).
Attachment stays buffer-open policy.
2. Cross-buffer LSP leak: the built-in provider's no-uri fallback was
the legacy global store drain, so scratch/unattached buffers could
show another file's cached completions. Strict now: no uri, no rows.
3. Pending prefixes own the keyboard: Action::Pending (C-x ...)
dismisses the popup and the popup shadow is guarded on an empty
dispatcher prefix, so the sequence's continuation and its C-g abort
reach the dispatcher instead of the popup.
4. Window-scoped sessions: CompletionPopupState.window_id (stamped by
completion_popup_open; Lua never sees it). Only the owning window's
overlay paints -- same-buffer splits each carry a persistent
overlay -- and a focus change invalidates the session.
5. Flaky worker test: the /proc thread-count probe and the idempotence
check both build EditorStates and could run concurrently, polluting
the baseline; merged into one test (non-Linux keeps a portable
idempotence variant).
Regression tests for 1-4; framing doc gains the as-built notes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Q#C2: CompletionPopupState (buffer + byte anchor + prefix + candidates
+ selection) behind SharedCompletionPopup on EditorCore — the
completion twin of SharedMenu. Q#C4: CompletionView reworked from the
dormant M4.7 store-keyed full-viewport painter into a self-positioning
overlay (MenuView model): windows candidates around the selection,
maps the byte anchor to a screen cell via the diag-view walk, places
below the anchor row (flips above when nothing fits), self-suppresses
when closed or on a foreign buffer. Q#C3: dispatch_key gains a PARTIAL
shadow — only TAB/RET/C-n/C-p/Up/Down/Esc/C-g intercept while the
popup is open; everything else falls through so typing keeps
self-inserting, with post-dispatch validation (active buffer, cursor
at/after anchor, word bytes between) closing broken sessions after the
after-edit hook has had its chance to refresh. Q#C7: accept
re-validates at the moment of accept and applies a single Replace
(one undo step; empty-prefix trigger sessions degrade to Insert),
firing buffer.after-edit through the existing revision check.
Framing: docs/in-buffer-completion-framing.md. Lua driver + bindings
follow in this branch.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Root cause of the CI Lint regression: commit 6113c53 bumped
rust-version 1.85 -> 1.95. clippy::collapsible_if is MSRV-gated —
collapsing `if let { if let }` needs let-chains, stabilized in Rust
1.95. At MSRV 1.85 clippy suppressed these; at 1.95 it emits them.
The patterns were pre-existing; the MSRV bump surfaced 47 of them
and turned `Lint (luajit)` / `Lint (lua54)` red at HEAD (was green
through PR #7; red from PR #8 = the release-prep MSRV bump).
Resolution (operator-chosen: autofix into let-chains): applied
`cargo clippy --fix` across the luajit, lua54, and crdt lanes
(--all-targets). The fix only applied with the lint at warn level;
`-- -D warnings` turns it into an error and blocks --fix.
Verified on the pinned 1.95.0, all three lanes:
clippy --all-targets -D warnings clean (luajit / lua54 / crdt);
fmt 0 diffs; lib tests 1223/0.
Note: the prior #6 "quiescent audit, clippy clean" was inaccurate —
clippy was not actually re-run there (build/version/fmt only), so
this MSRV-gated regression went uncaught until the live attach-debug
investigation surfaced it. This commit restores genuine clippy
cleanliness at MSRV 1.95.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>