Commit Graph

17 Commits

Author SHA1 Message Date
Levi Neuwirth 640b998d6b pmacs context menu: protocol v11 + dispatch + TUI/GPU surfaces (Q#CM1/Q#CM5)
The wiring that makes the menu and OS clipboard work end-to-end. The
protocol bump touches every exhaustive match on the wire enums, so the
daemon / frontend / GPU consumers all land together.

Protocol v11 (additive; SUPPORTED = [6..11]):
- `PointerKind::Context` (right-click), `FrontendEvent::MenuPointer`
  (GPU->daemon navigation, index-only), `InstanceMessage::MenuPrompt` +
  `MenuPromptRow` (daemon->GPU rows + highlight, daemon-gated >= 11).

Dispatch + producer:
- `EditorState`: menu interception in `dispatch_key`/`dispatch_mouse`,
  `MenuKey`, `dispatch_menu_key`/`_mouse`, `open_context_menu` (TUI) /
  `open_menu_at_byte` + `dispatch_menu_pointer` (GPU), `build_menu_rows`
  (calls the Lua resolver), `dispatch_idle` now false while a menu is
  open. `dispatch_pointer` gains the `Context` arm.
- daemon: routes `Context` -> open, `MenuPointer` -> navigate; gates
  `MenuPrompt` >= 11; drains the clipboard publish as
  `InstanceSignal::Clipboard`; honors the previously-dropped
  `FrontendEvent::Paste` (so paste works for the first time).
- `semantic_render`: `MenuPrompt` producer with cached-compare.

Frontends:
- TUI (`frontend.rs`): OSC 52 clipboard write; ignores `MenuPrompt`
  (the cell overlay renders the menu).
- GPU (`pmacs-gpu`): `arboard` dep; clipboard write/read + Ctrl-V inbound
  paste; right-click -> `Context`; `MenuLocal` + `MenuPrompt` handler;
  the popup (a second `TextRenderer` over bg quads) at the click pixel;
  hover/click -> `MenuPointer`; key intercept while open.

Also folds a pre-existing clippy `unnested_or_patterns` nit in a search
test (`Color::Indexed(11 | 3)`) that newer CI clippy surfaced.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014TXbAwk27agwhrNNrhLi2U
2026-06-27 22:19:00 -04:00
Levi Neuwirth 6e47fb4725 regex-search: GUI regex prompt + protocol v10 (Q#RX5/RX6)
Carries regex mode to the GUI status band and lets the GUI start a
regex search.

SearchPrompt gains `regex` + `invalid` (protocol v10; SUPPORTED grows
to [6,7,8,9,10]). The fields changed that variant's encoding, so the
daemon's per-session gate moves from >= 9 to >= 10 — a v9 peer
negotiates v9 and is simply sent no SearchPrompt (the decorations
still highlight) rather than mis-decoding the wider shape. The
producer fills both from the active SearchSession.

GUI: `is_search_entry_chord` also forwards C-M-s / C-M-r (Ctrl+Alt) so
a regex search can start; M-r (the toggle) already round-trips via the
intercept path once a search runs. The status band reads
`Regex I-search:` in regex mode and `[invalid]` when the pattern won't
compile. Multi-line regex matches needed no GUI change —
push_glyph_extent_rects already fans a byte range across lines.

Tests: SearchPrompt postcard round-trip extended to regex/invalid
shapes; protocol version pin 9→10 + ladder grows to v10; GUI entry
chord accepts C-s/C-r and C-M-s/C-M-r. (last_search_prompt's 5-tuple
factored into a SearchPromptFacts alias to satisfy type_complexity.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 14:46:43 -04:00
Levi Neuwirth 5111ae82e7 search: GPU isearch surface (Q#SR5, protocol v9)
Brings incremental search to pmacs-gpu, which has no minibuffer, by
reusing the shared daemon-side search core from the previous commit.

Key routing needs no new mechanism: `dispatch_idle` now also reports
false while a search is running, so the GPU's existing M11.6
optimistic-apply gate round-trips every keystroke to the daemon —
where `dispatch_search_key` extends the query / steps — instead of
self-inserting it. The match highlights were already wired (commit
2's SearchMatch / SearchMatchActive decoration colors), so they
light up live the moment keys round-trip.

The one thing a semantic frontend can't derive locally is the query
text, so a new additive `InstanceMessage::SearchPrompt { buffer_id,
query, active, total }` carries it (protocol v9, SUPPORTED grows to
[6,7,8,9]). The producer emits it cached-compare-suppressed like
StatusFacts — `query: Some` while searching, `None` to clear on
accept/cancel (matches keep highlighting via decorations), and
stays silent on a fresh buffer that never searched. The daemon's
per-session filter keeps the variant off wires negotiated < 9. The
GPU mirrors it into the status band: while searching, the band's
left side shows `I-search: <query> (n/m)` (or `[no match]`) in
place of the buffer name, returning to the name when the search
ends.

Tests: protocol version pin + SearchPrompt postcard round-trip
(active / failing / cleared shapes); producer emit-on-change +
suppress + clear-on-accept + first-sight silence; dispatch_idle
flips false during search (the GPU round-trip contract).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:26:25 -04:00
Levi Neuwirth 44430e8377 StatusFacts (protocol v8): name, modified, exact diag counts
The wire-authoritative half of the status band (Q#S1): an additive
InstanceMessage::StatusFacts { buffer_id, name, modified,
diag_errors, diag_warnings }, emitted by the semantic producer on
change (cached-compare). Counts freeze at their last value while
the diag store is stale — positions go wrong mid-edit but counts
merely lag, and flickering to zero per keystroke would be worse.
The daemon's write loop keeps the variant off wires negotiated
< 8, the DispatchIdle gate shape; SUPPORTED grows to [6, 7, 8].

GPU side: the band's left shows name + modified dot, the right
gains severity-colored E:n/W:n ahead of the local L:C/scroll
readout (rich-text spans, change-detected per side).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 15:17:07 -04:00
Levi Neuwirth a358df8cf2 triple-click selects the line (Q#M4, protocol v7)
PointerKind::TripleDown — the cheap additive bump shape returns:
PROTOCOL_VERSION 7, SUPPORTED [6, 7], the new variant kept off
pre-v7 wires by a frontend send-gate that downgrades it to the
plain Down a third click produced before. The GPU's click history
deepens to a chain count (1 → Down, 2 → DoubleDown, 3 →
TripleDown, then restart). Daemon side, select_line_at_cursor
selects the line including its trailing newline, so consecutive
triple-click lines abut.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 12:24:08 -04:00
Levi Neuwirth 72799f771a protocol v6: per-severity diagnostic underline colors (SGR 58)
M4.6 follow-up piece 2. `Style` gains `underline_color: Color`
(Default = follow the text color) so a diagnostic squiggle can be
red/yellow/cyan/gray without clobbering the syntax color of the
text it underlines — exactly why error_style() left its 'red'
unwired until now.

The wire consequence: Style rides inside Cell / CellDelta /
Snapshot / StyleSpans, so this is the protocol's first
encoding-breaking change. PROTOCOL_VERSION 5 → 6 and
SUPPORTED_PROTOCOL_VERSIONS narrows to [6]: postcard is not
self-describing, so no per-session send gate can keep a v5 peer
decoding v6 cells — a mismatched pair now fails the handshake with
a clean VersionMismatch instead of garbling mid-session. Version
policy tests rewritten to pin the new contract.

Surface wiring:
- diag.rs: per-severity underline_color (indexed 1/3/6/8).
- frontend.rs: kitty-style CSI 4:N for Double/Curly/Dotted/Dashed
  (previously flattened to plain SGR 4) + SGR 58:5/58:2 emission.
- ansi.rs: parse SGR 58/59 with the 38/48 extended-color grammar.
- overlay.rs merge_styles: non-default-wins, like fg/bg/underline.
- lua_bindings.rs: underline_color on Lua style tables.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 19:16:42 -04:00
Levi Neuwirth 9528595c0e session M-1 — Pointer wire + daemon byte-space mouse semantics
Per docs/pmacs-gpu-mouse-framing.md (resolves the deferred Q#B5):
a pixel frontend cannot express the daemon's cell coordinates —
inline adornments shift visual columns invisibly to cell space and
the design contract forbids hit-test round trips — so the frontend
hit-tests locally and ships source-byte gestures.

- protocol v5: FrontendEvent::Pointer { buffer_id, byte, kind, mods }
  with PointerKind { Down, Drag, Up, DoubleDown }. Double-click
  detection is frontend-side (only it knows pixel proximity).
  SUPPORTED_PROTOCOL_VERSIONS gains 5; the send gate runs in the
  frontend (an older instance cannot decode the variant).
- daemon: dispatch_pointer replays the existing mouse gesture
  semantics in byte space against the semantic session's window —
  Down places + anchors, Drag grows, Up collapses an empty click,
  DoubleDown selects the word. Routed by the authenticated source
  (CrdtOp/Viewport trust rule); hit bytes clamp + snap to UTF-8
  boundaries (a hit can race an in-flight edit).
- word_range_at fix (pre-existing CUA bug the new test surfaced):
  double-clicking a word's FIRST character selected the previous
  word too — backward_word from pos sees the non-word char behind
  the hit and crosses over; walk from pos + ch_len instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 13:10:17 -04:00
Levi Neuwirth 7ec314ad78
T M11.6 — DispatchIdle signal closes optimistic-apply blindness (#45)
The attach-mode optimistic-apply layer (M10.10) classifies any
plain-char keystroke as `Insert(c)` and applies it directly to the
local CRDT mirror, bypassing the daemon's keymap dispatcher. The
documented limitation ("the optimistic layer doesn't track keymap-
prefix state") also covered the minibuffer-active case, which
surfaced during session-5 manual validation: characters typed into a
`C-x C-f` prompt were optimistically inserted into the previously-
active document instead of routed to the minibuffer.

The fix is a daemon→frontend wire signal indicating whether the
daemon's *next* key event would be intercepted (minibuffer or pending
prefix) vs would self-insert. The frontend gates the optimistic-apply
path on this; when not idle, every keystroke round-trips as
`FrontendEvent::Key`.

Protocol changes (pmacs-protocol):

- `PROTOCOL_VERSION` 3 → 4; `SUPPORTED_PROTOCOL_VERSIONS` adds 4.
- New `InstanceMessage::DispatchIdle { idle: bool }`.

Daemon (`src/editor.rs`, `src/daemon.rs`):

- `EditorState::dispatch_idle()` — true iff `dispatcher.pending`
  empty AND `minibuffer.is_active() == false`.
- Per-tick emission: `last_dispatch_idle_sent: HashMap<FrontendId,
  bool>` tracks the last-broadcast value per session; emission fires
  on first frame after attach (absent entry) and on transitions.
- Gated on `crdt_replica` AND `negotiated_protocol_version >= 4` so
  older peers don't hard-error on the unknown variant. Same gating
  shape as the M10.5 CrdtOp and M11.1 SemanticFrame bumps.

Frontend (`src/attach.rs`):

- New `dispatch_idle: bool` (cfg `crdt`); default `false`
  (pessimistic — optimistic apply only activates after the daemon
  explicitly says idle).
- DispatchIdle messages consumed in the drain loop; they don't
  participate in `present_messages` batches.
- Optimistic-apply branch gated on `dispatch_idle`. When false, the
  branch returns false (forces fallthrough to the round-trip
  `forward_event` path).

Tests:

- `editor::tests::dispatch_idle_*` — fresh, prefix-pending, prefix-
  resolved, minibuffer-open/cancelled.
- `protocol::tests::dispatch_idle_round_trips_through_postcard` —
  wire encoding both polarities.
- `protocol::tests::protocol_version_is_four_for_dispatch_idle` +
  `supported_protocol_versions_includes_one_through_four` — pin the
  new version constants.

Gates: cargo fmt + clippy (workspace, with/without `crdt`) clean;
lib 1474 (+5 from 1469 baseline) with crdt; 1312 (+4) without;
m4 83; m11_5 (--features crdt) 2.

Acknowledged remaining gap: plain-char Lua bindings (e.g. binding
`q` to a command) still surface optimistic-apply divergence —
optimistic doesn't know "is this char bound to a non-self-insert
command in the current keymap." Rare in practice; revisit if anyone
hits it. Documented at session-5 finding time.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 13:49:48 +00:00
Levi Neuwirth a820e91389 session 1 commit 4/4: message envelopes moved to pmacs-protocol
The big move that completes session 1. Wire types moved from
src/protocol.rs to pmacs-protocol/src/message.rs:

- Input event family: Key, Modifiers, KeyEvent, MouseButton, MouseKind,
  MouseEvent, FrontendEvent (and its variants — Resize, KeyEvent,
  MouseEvent, Resume, Pause, Detach, ResizeAck, CrdtOp, Viewport).
- Instance-side message family: CursorState, InstanceSignal,
  GoodbyeReason, InstanceMessage (Hello/Cursor/CellDelta/CursorByte/
  CrdtOp/BufferSnapshot/Goodbye/PresenceUpdate + the SemanticFrame
  variants).
- SelectionSnapshot.
- SemanticFrame family components: StyleSpan, StyleSegment,
  DecorationKind, Decoration, DecorationSegment, AdornmentPlacement,
  AdornmentContent, InlineAdornment, BlockAdornment, ResourceBody.
- Handshake: PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS,
  is_supported_protocol_version, InstanceIdentity, InstanceCapabilities,
  FrontendCapabilities, NegotiatedCapabilities, negotiate_capabilities,
  Hello, AttachRequest.

What stays in src/protocol.rs:
- AttachTarget / AttachError / AttachTargetParseError /
  AttachTargetValidationError / AttachTargetError / AttachmentHandle
  (CLI / binding internals, not wire).
- crossterm_translate submodule (the crossterm ↔ pmacs-protocol-types
  translation layer; sits at the binding boundary, not on the wire).
- Existing tests (wire-format roundtrip + AttachTarget + crossterm
  translation), unchanged — they reach the moved types through the
  'pub use pmacs_protocol::*' re-export.

Mechanical rewrites inside the moved chunk: crate::buffer::BufferId →
crate::BufferId, crate::rope::Position → crate::Position,
crate::rope::CrdtOp → crate::CrdtOp (the message module is inside
pmacs-protocol; identity types live at the crate root).

Feature re-added on pmacs-protocol: 'crdt' (was removed in commit 3
as I'd thought CrdtOp was the only feature-gated thing — but
InstanceCapabilities::default and FrontendCapabilities::default both
call cfg!(feature = 'crdt') for their multi_frontend / crdt_replica /
semantic_render defaults). Re-added with a doc comment explaining why.
The parent pmacs crate's 'crdt' feature now activates
'pmacs-protocol/crdt' so the cfg!() check evaluates consistently in
both crates.

Full gate green: fmt, clippy --all-targets -D warnings, lib 1314,
m4_acceptance 83, m8_1/m8_9/m8_10 10/26/19, m9_1 18, m5_8 5,
m11_5_semantic_acceptance --features crdt 2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:55:27 -04:00
Levi Neuwirth 14341d958e session 1 commit 1/4: workspace + identity types moved to pmacs-protocol
Workspace skeleton: root Cargo.toml becomes a workspace with members
[".", "pmacs-protocol"]; [workspace.dependencies] pins serde,
postcard, thiserror so both crates use byte-identical versions (the
wire format depends on it). pmacs main package keeps its existing
shape (no file moves); it just gains pmacs-protocol as a path
dependency.

Identity types moved: BufferId (from buffer.rs), FrontendId + ByteRange
(from protocol.rs), Position type alias (from rope.rs). All four are
self-contained — no custom-type dependencies — so the first stage of
the move can land atomically without dragging cell/message types along.

src/buffer.rs / src/protocol.rs / src/rope.rs each gain a 'pub use
pmacs_protocol::...' re-export for the moved names, so existing
internal imports (crate::buffer::BufferId, crate::rope::Position, etc.)
continue to resolve unchanged. New consumers (pmacs-gpu, debug tools)
will depend on pmacs-protocol directly.

One visibility change: BufferId::from_raw was pub(crate); promoted to
pub with a doc note that it's not stable API for external consumers.
The (crate) restriction was advisory only — external deserialization
already worked via the derived Deserialize, so making it pub doesn't
widen the actual surface, just makes it honest.

Lib gate: 1314 passed, no regressions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:11:33 -04:00
Levi Neuwirth c692710321 M1+M2: FileStyleSummary (minimap producer, Q#2 resolved) + doc
New InstanceMessage::FileStyleSummary { buffer_id, generation, lines:
Vec<Style> }: a coarse whole-file styling summary for a Zed/VSCode-
style minimap, resolving the design note's Open Q#2. One dominant
Style per source line (by byte count across the producer's current
spans); the frontend maps minimap rows to one or more lines.

Producer scoped_file_summary reuses scoped_style_spans with a whole-
buffer synthetic viewport, so policy A's authority pick (tree-sitter
for grammar-backed languages, LSP semantic tokens otherwise) is
inherited automatically — no separate styling path. file_style_summary_msg
is keyed on the buffer's CRDT generation: an idle buffer at the same
generation pays nothing (the whole-file summary is the expensive bit
on large files, so re-emit only after edits). First frame for a
buffer always emits; the existing first-frame test updated to expect
3 messages (StyleSpans + Decorations + FileStyleSummary).

Per-line dominant style is the v1 representation. Future refinements
(fixed-N bands; whole-file RLE style runs) are recorded in the design
note as straightforward extensions if a real frontend prefers them.

Structural gating same as the other semantic families: the daemon
only constructs a SemanticRenderState for sessions that negotiated
semantic_render, so non-semantic sessions never receive it. Grid TUI
adds the variant to its ignore list. Round-trip fixture covers it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 19:23:20 -04:00
Levi Neuwirth 80c0679a5b T M4.5: documentSymbol / workspace symbol / documentHighlight
Completes the read-only LSP feature set (everything except edits).
Same request→store→Lua pattern as the nav batch; these three need
new parsing (new response shapes), so they were deliberately split
from the Location-family PR (#16).

- src/symbol.rs: one flat Symbol type for both symbol requests.
  from_lsp_value handles BOTH LSP shapes — hierarchical
  DocumentSymbol[] (flattened with depth + parent chain) and flat
  SymbolInformation[]/WorkspaceSymbol[] (location.uri, range
  optional for WorkspaceSymbol). Scope-keyed (Document(uri) vs
  Workspace(query)) so an outline and a query don't collide.
- src/document_highlight.rs: range + DocumentHighlightKind (absent
  defaults to Text=1 per spec), (server,uri)-keyed.
- lsp.rs: three ResponseRoute variants + absorb arms + request
  methods. documentSymbol/documentHighlight ranges convert via the
  requested-doc codec; workspace/symbol results are cross-file →
  route uri "" → non-destructive passthrough (same rule as
  cross-file definition).
- Lua: raw bindings + pmacs.document_symbol / .workspace_symbol /
  .document_highlight read surfaces (the new LSP Symbol is aliased
  to avoid the pre-existing project_index::Symbol name clash);
  lsp.lua wrappers + an lsp.document-symbols command on C-c o
  (modeline summary; outline buffer is future UX).
- Tests: 6 parser unit tests (hierarchical depth/parent, flat
  SymbolInformation, range-less WorkspaceSymbol, highlight kind
  default, scope non-collision) + an e2e driving all three through
  the async bridge asserting shape correctness.

Also includes a pre-existing rustfmt normalization of the #15
semantic-frontend files (protocol.rs / semantic_client.rs /
semantic_render.rs) — main was not rustfmt-clean there after the #15
merge; bundled here per operator decision so the fmt gate is green.

Gate: fmt clean; clippy --all-targets -D warnings clean; lib
1257/0; m4_acceptance 66/0; m9_1 18/0; m8_1/m8_9/m8_10 green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 22:15:03 -04:00
Levi Neuwirth 071e79ffee M11.4: segment diffing for StyleSpans/Decorations
Replace the M11.2/M11.3 coarse whole-payload suppression with a
CellDelta-style diff lifted from positional cells to byte-anchored
ranges.

- protocol.rs: StyleSpans/Decorations refined to
  { buffer_id, generation, full: bool, segments: Vec<...Segment> }.
  New StyleSegment{range,spans} / DecorationSegment{range,decorations}.
  full=true → frontend discards prior state for the buffer; full=false
  → replace styling only within each segment's range, bytes in no
  segment keep prior state. Decorations gains generation for parity.
  Each segment carries ALL current items intersecting its range
  (clipped), so an unchanged span overlapping a dirty range is
  reconstructed. ResourceOffer stays an honest stub (no producer).
- semantic_render.rs: LastFrame baseline per buffer (viewport region
  + full item set). full on first frame / viewport-region change;
  else symmetric-difference the ordered sets, coalesce changed ranges
  into maximal disjoint dirty intervals, emit one segment per
  interval with current items clipped to it; suppress when no dirty
  interval. Independent baselines for styling vs decorations.

Byte offsets cascade on edits (an insert shifts later spans), so an
incremental post-edit frame dirties [edit, viewport_end) — bounded;
no-edit frames (cursor/scroll/selection-only) still cost nothing.

10 semantic_render tests (full-on-first/viewport-change, incremental
dirty intervals, independent suppression, unchanged-overlapping
reconstruction) + updated protocol round-trip. Lib (1397 crdt / 1242
non-crdt) + integration green both flavors; clippy -D warnings clean
both.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 21:04:49 -04:00
Levi Neuwirth 4ba6fd7bb8 M11.2: semantic projection seam (SemanticRenderState)
The first real producer of the semantic-frontend arc. The instance
projects syntax styling to semantic_render sessions without
rasterizing to a cell grid.

- src/semantic_render.rs: SemanticRenderState, sibling of
  instance_render::RenderState. Reads the same EditorState, emits
  InstanceMessage::StyleSpans (tree-sitter spans via the active
  Theme), scoped + clipped to the FrontendEvent::Viewport byte range.
  Emits nothing until a viewport is declared; suppresses
  byte-identical frames (per-span delta encoding deferred to M11.4).
- CrdtState::version_scalar(): oplog version vector summed to a
  monotonic non-decreasing u64 — the StyleSpans.generation anchor.
- daemon dispatcher: semantic_states map parallel to render_states;
  projection selected per session. Semantic sessions get StyleSpans +
  CursorByte + BufferSnapshot + CrdtOp + presence, never CellDelta /
  grid Cursor. FrontendEvent::Viewport consumed (routed by
  authenticated source). SessionEstablished body extracted to
  handle_session_established (clippy 100-line ceiling). Grid-less
  sessions no longer panic the _ => apply_event arm.
- InstanceCapabilities default semantic_render flipped to
  cfg!(feature = "crdt") — the "M11.2 enables semantic" moment,
  analogous to the M10.8 Day-4 flip. M11.1 negotiation test comment
  updated for the flip (frontend-side default still false keeps M10.7
  outcomes unperturbed).

Lib (1392 crdt / 1237 non-crdt) + integration suites green on both
feature flavors; clippy -D warnings clean on both.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:14:08 -04:00
Levi Neuwirth 1c5169b8f8 M11.1: semantic-frontend protocol scaffolding (wire + capability)
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>
2026-05-18 19:39:07 -04:00
Levi Neuwirth 45be65b026 M10.10 ship gate
Land the optimistic local-edit-application layer on top of the M10 CRDT
foundation: frontend-side rope replica with local edit application,
daemon-authoritative broadcast, and bidirectional cursor reconciliation.
Keystrokes feel instantaneous because the local replica answers next-render
queries before the daemon round-trip completes, while the daemon remains
the single source of truth for conflict resolution and broadcast to remote
replicas.

Architecture beats:
- BufferMirror (src/buffer_mirror.rs) holds a per-frontend rope replica
  with explicit cursor-staleness tracking. Every event that may move the
  active cursor or swap the active buffer marks the mirror stale; the
  next CursorByte from the daemon clears it.
- CrdtOpOrigin {OptimisticReplica(FrontendId), DaemonKey} routes broadcast.
  OptimisticReplica skips re-application on the originating frontend
  (already applied locally); DaemonKey broadcasts to all replicas including
  source -- covers Lua-driven and generated-buffer edits that bypass the
  optimistic path.
- Generated buffers (*help*, *workers*, *pmacs-instance*, *errors*) funnel
  apply_edit output through queue_daemon_origin_crdt_op so post-attach
  CRDT upgrades don't drop their edits.
- forbid(unsafe_code) preserved throughout; loro 1.12 added as the CRDT
  engine.

Audit posture: M10.10 shipped through six post-audit review rounds with
twenty-eight cumulative findings, most categorized as "incomplete
application of a prior round's mechanism." The audit doc records
grep-driven exhaustiveness as the standing countermeasure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 16:28:46 -04:00
Levi Neuwirth 4da4b09d5d Initial commit: v0.1.0 2026-05-03 19:51:06 -04:00