Addresses the four post-merge findings against PR #102 (merged as
2d157d8). Stacked on the kill-ring branch (PR #103): the trigger
redesign rides its command-boundary substrate.
- BLOCKING delta without the capability: pull_semantic_tokens_quiet (and
the pre-existing manual pmacs.lsp.semantic_tokens(), same bug) used
any stored resultId to request /full/delta while only checking that a
provider exists. A resultId does not imply delta support --- servers
may return one from /full regardless --- and a conforming full-only
server rejects the delta request; the pull path swallows the error, so
styling stayed silently stale after the first edit. Both sites now
require semanticTokensProvider.full.delta == true. The fake's default
mode truthfully advertises { "full": { "delta": true } } (it
implements delta); a new `fullonly` mode advertises "full": true,
REJECTS /full/delta, and bumps its resultId per /full response so the
test can observe WHICH pull refreshed the store. Verified the test
bites: with the capability check reverted, the post-edit rid stays
rid-1 (stale) and the test fails.
- HIGH false-positive trigger + cross-frontend misclassification: the
cursor-delta heuristic ("same buffer, cursor +1") fired on any
one-byte edit --- including a one-byte paste of "(" once PR #103 made
paste fire buffer.after-edit --- and its singleton last_typed was
shared across frontends. Replaced with the input-origin signal from
the #103 substrate: inside after-edit,
pmacs.editor.this_command() == "buffer.self-insert" names an edit
produced by typing, per frontend, with nothing inferred from cursor
deltas. New ed.this_command() binding; handle_remote_crdt_op now
classifies a single-codepoint optimistic insert as buffer.self-insert
(rotation, not just break --- kill-chain semantics identical since
self-insert is not a kill, and GPU typing now carries the same origin
signal as TUI typing). Paste/pointer/undo/unbound leave this_command
as something else and can never trigger.
- MEDIUM first-trigger-ignored: the origin signal needs no prior-edit
snapshot, so the very first "(" typed in a buffer triggers. The test
that had encoded the warm-up keystroke as "correct" now types a single
"(" as the first character.
- MEDIUM non-ASCII trigger characters: char_before read one byte and
rejected multi-byte strings; LSP trigger characters are strings. Now
codepoint-aware (read up to 4 bytes back, take the suffix from the
last non-continuation byte). The sighelp fake declares a two-byte
trigger ("«") and a test types it.
Tests (m4_acceptance 94 -> 97 after +4/-1 rework):
arc1c_full_only_server_repulls_via_full_not_delta (bites --- verified),
arc1d_signature_help_auto_triggers_on_trigger_char (now first-char),
arc1d_signature_help_triggers_on_non_ascii_trigger_char,
arc1d_signature_help_ignores_non_typed_edits (movement-stamped
programmatic "(" insert + manual after-edit must not trigger --- the
case cursor-delta inference cannot distinguish). Daemon unit test
updated for the insert classification (break-then-classify: `this` =
buffer.self-insert, `last` = None, chain still dead).
Note: completion.lua still uses the Q#C9 cursor-delta heuristic and
inherits its weaknesses; migrating it to this_command is a named
follow-up, out of scope here.
Gates: fmt + workspace clippy clean; lib 1500; crdt 1672; m4 97;
killring 28; completion 9; GPU 58; git diff --check clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>