Vterm Stage 3 — the final vterm stage. A semantic frontend can now host a
terminal: the daemon ships complete validated cell grids, and pmacs-gpu
renders them with fixed-cell geometry, its own input path, and no document
projection at all.
Protocol v19 appends three variants after their enums' final v18 members:
InstanceMessage::TerminalFrame (daemon-gated), and FrontendEvent::
TerminalResize / TerminalPointer (frontend-gated). It is the first bump to
gate in both directions, so criterion 28 pins each filter independently and
byte pins on StatuslineSegments and MenuPointer guard the placements.
pmacs-protocol gains src/terminal.rs: the shared row/column/visible-cell/
grapheme/metadata bounds, TerminalProcessState, TerminalSelectionSpan, and
TerminalFrame::validate — the ONE structural policy the daemon runs before
emission and the frontend runs after decode. src/terminal/* re-exports them
so no duplicate type exists, and unicode-width becomes a workspace dependency
so the screen and the validator measure glyph columns with one table. A new
8 MiB aggregate glyph bound keeps the largest legal frame (measured:
13,437,863 bytes) under the unchanged 16 MiB transport cap rather than
widening every connection's allocation ceiling.
The semantic producer suppresses the whole document family for a terminal
buffer while keeping the status band, theme, font, statusline, menu, and
minibuffer, and compares the complete ordered payload rather than
screen_generation — scroll, selection, and process state all change without
advancing it.
Two things the framing did not spell out, both found by the real-daemon
acceptance:
The Viewport gate keys on the authenticated source's ACTIVE buffer, not the
buffer the message names. Viewport also aligns the window to what it
declares, so a stale document viewport in flight when a command opened a
terminal dragged the frontend straight back off it: the window oscillated,
every terminal declaration was refused, and no frame ever arrived, with
nothing logged anywhere.
The producer clears terminal mode on every exit path. The daemon uses that
flag to suppress CursorByte and the presence sweep, so an early return that
left it set kept both suppressed after the frontend returned to a document.
pmacs-gpu/src/terminal.rs is a pure cell-space paint planner, unit-testable
without a GPU. The renderer builds one shaped buffer per text run, so a wide
or cluster glyph's advance can never choose the next column's origin.
Criterion 37 needed a seam rather than a fixture: pmacs-gpu depends only on
pmacs-protocol, so attach::connect's reader sink was generalized and a
--headless-probe mode added. The acceptance drives a real daemon, a real
/bin/sh child, the real attach client, and real composited pixels in one
path — which is how both defects above were found.
Gates: fmt; strict workspace clippy; 1,757 default + 1,933 CRDT library
tests; vterm Stage 1 9/10, Stage 2 4/4, Stage 3 4/5 acceptance
(default/CRDT); statusline 7/8; M4 120; required GPU 127; workspace sweep
2,919 across 83 suites; diff check clean.
Add cross-surface Lua, shared-view, clipboard, authenticated routing,
BEL, resize, and real-host PTY acceptance. Ensure terminal-local keymaps
run before raw child transport and document the criterion-to-test map.
Co-Authored-By: Claude <noreply@anthropic.com>
Typing an opener inserts the closer with the cursor between; typing a
closer over its twin steps over it. Q#AP1: the nine built-in pair
chars leave both optimistic classifiers (shared charset in
pmacs-protocol) and round-trip through dispatch, so the opener and the
hook's closer are adjacent daemon-peer undo units, dispatch CUA
type-over applies, and skip never paints a transient duplicate.
Q#AP9: exact one-shot typed-edit provenance. EditorCore's
apply_active_edit now returns the effective Edit; the dispatch
fallback arms a per-frontend record (codepoint + requested vs
effective ranges + post-cursor + clean verdict) that insert primitives
complete and the daemon's optimistic CRDT arm builds directly. The
record is takeable exactly once via pmacs.editor.take_typed_edit()
during the one after-edit fan-out, then cleared — paste, programmatic
edits, manual hook runs, nested re-runs, rejected edits, and stale
this_command all observe nil, and transformed / relocated /
context-switched source self-inserts fail closed with a status.
pair.lua (loaded BEFORE lsp.lua — ordering contract in editor.rs):
per-language pmacs.pair.sets with a conservative default (no ' or `),
EOL/whitespace/closer insertion predicate, reactive skip-over-close,
rejected/transformed intercept outcomes with context-guarded
translate-and-clamp cursor repair.
Acceptance: 32 dispatch-driven cases (predicate, skip, per-language
sets, non-typed provenance incl. production-shaped paste, type-over,
undo/redo grain, intercept outcomes on both the source and reaction
edits, context-switch probe, record lifecycle, frontend isolation) +
first-didChange ordering against the fake LSP's sighelp mode via a
new PMACS_FAKE_LSP_CHANGE_SINK replay file. Six two-replica CRDT
cases pin dispatch-route convergence with cursor-between, undo/redo
walking the pair on both replicas, both mixed-history undo models as
named substrate limits, and the optimistic custom-char route
(closer-broadcast-before-opener convergence, degraded cross-peer
undo). TestDaemon gains spawn_with_config for init.lua-extended pair
sets.
Framing: docs/auto-pairing-framing.md (revision 3).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VoiEyuPjoBhvwACf8HAnLB
First milestone of the M11 semantic-frontend arc
(docs/semantic-frontend-protocol.md). Wire-format scaffolding only —
no producer or consumer; mechanically identical to the M10.5 CRDT
wire declaration, and non-breaking by the same slice-membership +
per-session-filter argument.
- PROTOCOL_VERSION 2 -> 3; SUPPORTED_PROTOCOL_VERSIONS [1,2,3]. v0.1
and v1.0 binaries keep connecting unchanged (membership, not
strict equality).
- semantic_render capability bit on FrontendCapabilities,
InstanceCapabilities, NegotiatedCapabilities (#[serde(default)];
instance default false until the M11.2 projection seam).
negotiate_capabilities AND-combines it and enforces the
semantic_render => crdt_replica dependency as a CapabilityMismatch
(a semantic session is also a text replica), never a silent
degrade. PMACS_INSTANCE_SEMANTIC_RENDER env override added.
- InstanceMessage SemanticFrame family: StyleSpans, Decorations,
InlineAdornments, BlockAdornments, FoldState, ResourceOffer.
FrontendEvent::Viewport. Supporting types: ByteRange, StyleSpan,
Decoration/DecorationKind, InlineAdornment/AdornmentPlacement/
AdornmentContent, BlockAdornment, ResourceBody. All byte-anchored;
no pixels cross the contract boundary.
- Grid TUI (frontend.rs) and daemon apply_event drop the new family
silently — the "declared, not yet wired" posture CrdtOp held
between M10.5 and M10.8. Stale v1.0 version-pin tests updated to
the v1.1 truth; negotiation matrix + postcard round-trips added.
Lib + integration suites green on both the default and crdt feature
flavors; clippy -D warnings clean on both.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Post-ship-gate completion of M10.10 (optimistic local edit
application, Path β). The milestone core landed in 45be65b
"M10.10 ship gate"; this commit completes the frontend keystroke
path and absorbs Day-5 corrections.
Completes:
- optimistic::frontend_event_for_keystroke — keystroke orchestrator
(classify_key predicate → mirror-ready check → CrdtOp or Key
fallback per Refinement 4 graceful degradation).
- BufferMirror cursor tracking (active_buffer, cursor_byte_pos via
CursorByte) + char-boundary-aware delete helpers (prev/next_char_len)
so multibyte backspace/delete don't trip loro's mid-codepoint
rejection.
- buffer.rs: crdt_state accessor (was test-only) now production —
daemon's BufferSnapshot export path uses it.
Day-5 corrections:
- packages/manifest.rs: fix pre-existing M8-era proptest generator
that produced ".."-containing entry paths the parser correctly
rejects (segment-structured regex; stale regression seed removed).
Out of M10.10 scope; absorbed so future milestone sweeps see clean
output instead of a known-failing test requiring prose.
- tests: extract inline PTY/daemon helpers to shared tests/common/
module (m5_5, m5_8 now import; no coverage change — m5_5 retains
19 m10_10 tests). tests/common/ added (required for compilation).
Audit history (M10.10-AUDIT.md is gitignored internal-only; this
message is the sole version-controlled record):
M10.10 PASSES within Path β scope (end-of-line optimistic visual
paint; mid-line/delete-forward round-trip; full CRDT-op exchange
across the text-input scope). The initial audit verdict was WRONG —
optimistic-apply was structurally unreachable in the production
binary (build_capabilities advertised crdt_replica: false). Six
post-audit review rounds surfaced 28 findings (F5–F32) beyond the
framing pass's original 4. Six M10-era discipline additions emerged,
each empirically grounded: end-to-end-exercise check (bidirectional
scope), composition-consistency check, verification-milestone premise
check, library-API verification check, forward-pointer-comment
hygiene, methodology-composition check.
Scorecard adopts Option C dual methodology: layer (a) framing-pass
accuracy is 7/8 milestones-not-findings AND 2/8 findings-as-failures
— the 5/8 spread is the density diagnostic (M10.10's defining
characteristic; neither number alone is honest). Layer (c): 7/8 and
6/8 (M10.8 inherited-gap cluster). Budget honesty: 5-day
pre-authorization covered anticipated implementation surprises (K1,
Risk #6 a); Finding 3 was a third surprise absorbed via compression,
not structural slack; the six post-audit rounds were entirely
unbudgeted and are the milestone's dominant cost. M10.10's density
is partly forecastable — it is the only M10 milestone with all three
of: architectural reversal, multi-milestone integration, and
verification depending on incomplete cross-milestone wiring.
Ship-gate clean on clean checkout (cargo clean + rebuild): luajit+crdt
1364/1364, luajit 1211/1211, lua54+crdt 1364/1364, lua54 1211/1211,
m5_5 daemon-e2e 36/36, perf 1MB=1.1ms vs 10ms gate, clippy 0 across
feature combos, fmt clean. One transient flake observed
(async_runtime::supersede_cancels_in_flight_job_within_50ms — timing
test starved under concurrent compile load, non-reproducible in
isolation, known infra pattern, not an M10.10 regression).
Next: M10.11 (two-laptop acceptance) inherits all six discipline
additions; v1.0 ships after M10.11.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>