18 KiB
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
Added
Semantic-frontend protocol scaffolding (M11.1)
First milestone of the M11 semantic-frontend arc (see
docs/semantic-frontend-protocol.md). Wire-format scaffolding only —
no producer or consumer yet; mechanically identical to the M10.5 CRDT
wire declaration.
PROTOCOL_VERSIONbumped 2 → 3;SUPPORTED_PROTOCOL_VERSIONSnow[1, 2, 3]. The slice-membership handshake keeps v0.1/v1.0 binaries connecting to v1.1 binaries unchanged.- New
semantic_rendercapability bit onFrontendCapabilities,InstanceCapabilities, andNegotiatedCapabilities(#[serde(default)]; instance defaultfalseuntil the M11.2 projection seam lands).negotiate_capabilitiesAND-combines it and enforces thesemantic_render ⇒ crdt_replicadependency (a semantic session is also a text replica) — rejected as aCapabilityMismatch, never silently degraded. InstanceMessagegains theSemanticFramefamily:StyleSpans,Decorations,InlineAdornments,BlockAdornments,FoldState,ResourceOffer;FrontendEventgainsViewport. All anchored in byte offsets — the instance never learns a pixel.PMACS_INSTANCE_SEMANTIC_RENDERenv override mirrors the existing per-capability test overrides.
Semantic projection seam (M11.2)
The first real producer. The instance now projects syntax styling to
semantic_render sessions without rasterizing to a cell grid.
- New
SemanticRenderState(src/semantic_render.rs), sibling ofinstance_render::RenderState: reads the sameEditorStatebut emitsInstanceMessage::StyleSpans— tree-sitter spans mapped through the activeTheme, scoped and clipped to the byte range the frontend declared viaFrontendEvent::Viewport. Emits nothing until a viewport is declared; suppresses byte-identical frames (true span-granularity diffing is M11.4). StyleSpans.generationis anchored toCrdtState::version_scalar()— the oplog version vector summed to one monotonic non-decreasing scalar, letting a frontend discard styling that predates an edit it already applied optimistically.- Dispatcher selects the projection per session: a semantic
session gets a
SemanticRenderStateand neverCellDelta/gridCursor(it lays out locally) but still receivesCursorByte,BufferSnapshot,CrdtOp, and presence. A grid and a semantic frontend can attach to the same buffer simultaneously.FrontendEvent::Viewportis consumed (routed by authenticated source, likeCrdtOp). InstanceCapabilitiesdefaultsemantic_renderflipped tocfg!(feature = "crdt")— the "M11.2 enables semantic" moment, analogous to the M10.8 Day-4multi_frontend/crdt_replicaflip.
Decorations projection (M11.3)
SemanticRenderState now also projects InstanceMessage::Decorations
from the editor state pmacs actually has instance-side:
- Selection — per-window (per-frontend) byte-native state, scoped
to the session's active window for the declared buffer and clipped
to the viewport.
DecorationKind::Selection. - Diagnostics — the shared
DiagnosticStore, keyed by the file URI the LSP glue opened the document under (lsp::path_to_file_uriis nowpub(crate), byte-identical to the Luafile_uri_for). LSP(line, col)is converted to a byte range against the buffer source; severity maps toDiagnosticError/Warning/Info/Hint. StyleSpansandDecorationssuppress unchanged frames independently — a selection move does not force a styling re-send.Decorations::SearchMatch/SearchMatchActive/CurrentLineare not emitted: pmacs has no instance-side search-hit store, and current-line is a pure cursor derivation the frontend already owns viaCursorByte(emitting it would breach the contract boundary).InlineAdornments/BlockAdornments/FoldStateremain unproduced by design — pmacs has no inlay-hint/blame/lens/fold/diff source yet. The wire variants exist (M11.1); their producers are wired when those features land. Honest stubs, not fabricated data.
Segment diffing (M11.4)
Coarse whole-payload re-sends replaced with a CellDelta-style diff,
lifted from positional cells to byte-anchored ranges.
StyleSpans/Decorationsrefined:{ buffer_id, generation, full: bool, segments: Vec<…Segment> }.full = trueis a resync (frontend discards prior state for the buffer);full = falseships only the dirty byte regions, eachStyleSegment/DecorationSegmentreplacing styling within its range. Bytes in no segment keep prior state; an unchanged span overlapping a dirty range is faithfully reconstructed (segments carry all current items intersecting the range, not only changed ones).Decorationsalso gainsgenerationfor parity withStyleSpans.- The diff: symmetric difference of the previous/current ordered
item sets, changed ranges coalesced into maximal disjoint dirty
intervals. First frame and any viewport-region change force
full; an unchanged frame ships nothing. Byte offsets cascade on edits, so an incremental frame after an edit dirties[edit, viewport_end)— bounded; no-edit frames (cursor/scroll/selection) stay free. ResourceOfferremains an honest stub (no resource-bearing adornment producer exists yet) — same discipline as M11.3.
Semantic frontend↔instance glue (M11.5)
The arc's consumer side and end-to-end coverage. pmacs has no GUI toolkit, so — per the design note's testability strategy — the deliverable is the bounded testable glue, not a GPU renderer.
- New headless
SemanticClient(src/semantic_client.rs,crdt- gated): composes theBufferMirrorrope replica (M10.10) with a tile-basedSemanticModelthat reconstructs styling/decorations from thefull+ dirty-segment deltas (M11.4). Self-contained: no terminal, no pixels. EmitsFrontendEvent::Viewport; exposes read-back accessors (text,effective_style_at,decoration_kinds_at, tile ranges). The M11.4 contract (segments carry every current item intersecting their range) makes a tile self-contained, so incremental application is a clean per-tile replacement with edge-clipping, not cross-span surgery. tests/m11_5_semantic_acceptance.rs: (a) reconstruction- equivalence — an incrementally-driven client is asserted byte-for- byte identical to a fresh full projection across a scripted viewport/edit/selection sequence including a viewport jump (the golden discipline without a snapshot crate); (b) end-to-end — a real daemon routesStyleSpans/Decorationsto a semantic session (after it declares aViewport) and never to a grid session, andCellDeltavice versa, validating the M11.2 per-session projection through the socket.
This completes the M11 semantic-frontend arc (M11.1–M11.5): wire +
capability scaffolding, the instance-side projection seam,
decorations, segment diffing, and the consumer-side glue with
end-to-end coverage. InlineAdornments/BlockAdornments/FoldState/
ResourceOffer remain honest stubs pending their source features.
1.0.0 --- 2026-05-18
First stable release. Builds on the 0.1.0 preview (M1–M6) with the M7–M10 arc: third-party packages, the universality proof across three shape-distinct workloads, MCP, and multi-frontend CRDT collaboration. Solo development through 1.0; public contributions open from this release.
Added
Third-party package system (M7)
- Package install at user-config and project scope
(
pmacs.packages.install/install_project) withversion/branch/commitpins and a lockfile. - Per-package environment with
exports;requireresolution scoped so packages can declare module-level state without cross-package collision. - Edit intercepts: packages observe and transform buffer mutations; multiple intercepts thread in attach order.
- Audit-lint rule set (the package-trust surface): error/warning/info patterns across environment-escape, fs-mutation, process-spawn, and reach-around classes; runnable locally and in CI.
Filesystem API and bundled packages (M8)
pmacs.fs.*worker-dispatched surface:read_dir,stat,rename,chmod,removewith M3supersedechaining on read ops; UTF-8 path constraint surfaced as a structured failure.- Three shape-distinct bundled packages proving the buffer universality claim: dired (directory listing), magit (section view), outline / outline-aggregate (derived structure views).
- Dev-loop APIs:
pmacs.packages.install_local,reload,on_unload.
MCP for package authors (M9)
- Model Context Protocol surface so package authors can expose
editor capability to AI assistance over the same protocol-uniform
path as the rest of the system (see
docs/mcp-for-package-authors.md).
Multi-frontend CRDT collaboration (M10)
- CRDT layer (loro-backed, feature-gated) with an optimistic-apply keystroke path; daemon owns the authoritative replica, frontends mirror it.
- Two laptops attach to one daemon instance, both edit one file, edits converge within a frame; per-frontend undo; presence and per-frontend cursor rendering.
- Convergence verified under simulated network jitter
(
PMACS_INSTANCE_LATENCY_JITTER_MS); automated synthetic-frontend and doubled-PTY acceptance plus a two-laptop manual checklist.
Pulled-forward v0.2-prerequisite APIs
Planned promotions from V0.2-PREREQUISITES.md, implemented and
shipped in 1.0 (the public-API ceiling was raised by operator
decision to absorb them):
pmacs.buffer.from_file,pmacs.buffer.on_removed(with an idempotent:remove()handle; buffer-local keymaps pruned on removal).bypass_interceptoption onbuffer.insert/delete/replace(skips the intercept chain only; undo/dirty/view/CRDT bookkeeping preserved).pmacs.fs.watch(polling watcher;:cancel()/:is_cancelled()).pmacs.async.yield_to_next_tick(worker-free next-tick yield).pmacs.editor.move_to_line(0-based, clamps out of range).pmacs.outline.query(cross-package outline structure query).- Audit-lint rule 16
reach-around-require-field(info).
Changed
- SSH remote attach now carries the wire protocol over the SSH
stderr channel by default (previously stdout). On at least one
tested environment (
OpenSSH_10.3p1), a non-PTY SSH session does not forward a long-lived remote process's stdout until that process exits, while stderr streams in real time; the--daemon-attachbridge is exactly such a process, so a stdout-carried protocol hung indefinitely (the daemon and bridge were correct throughout). The default flip resolves this with no user action required. Override per invocation withPMACS_ATTACH_SSH_PROTOCOL=stdout|stderr(the legacyPMACS_ATTACH_SSH_PROTOCOL_STDERR=0/1is still honored). Breadth is currently a single environment; the default is a one-line internal switch so it can be revisited without downstream impact.
Fixed
- SSH remote attach no longer fails with a host-side
send Hello failed: Broken pipe: the daemon-liveness probe in the--daemon-attachauto-start path was a disposable connect that could consume the daemon's server-speaks-firstHello; the established connection is now reused (regression-tested).
Known limitations
- Per-frontend undo history does not persist across reattach.
After a frontend disconnects and reconnects, it is issued a fresh
collaboration identity; its pre-disconnect edits remain in the
converged document (CRDT state is fully restored) but are no longer
reachable by that frontend's undo command — undo only reaches
edits made since reconnecting. Workaround: remove unwanted
pre-disconnect content by editing it out. Preserving undo history
across reconnect is tracked for v0.2
(
V0.2-PREREQUISITES.md, Finding 4). - macOS REPL ctrl-c / exit-marker timing. Two M6.5 REPL
acceptance tests (
m6_5_ctrl_c_sends_sigint,m6_5_exit_marker_uses_basename_with_leading_newline) time out on macOS CI runners. The core daemon, CRDT, and remote-attach paths — including the Mac-host collaboration scenario — build and pass on macOS (the lib suite is green there); the limitation is confined to bundled-REPL signal/exit-marker timing. Tracked for v0.2.
Project posture
forbid(unsafe_code)maintained across the crate; syscalls that need unsafe are bridged via/bin/sh/ coreutil / binary trampolines or safenixwrappers.- Rust toolchain pinned (
rust-toolchain.toml, 1.95.0) so local and CI build the same validated compiler; bumped deliberately. - Cross-flavor CI: every test runs under both
luajitandlua54; clippy-D warningsclean on both flavors and thecrdtlane; release-only perf-gate jobs. - Library suite ~1223 tests (non-crdt) / ~1377 (crdt), zero failing on the pinned toolchain, plus per-milestone integration suites through M10.
0.1.0 --- 2026-05-03
First public preview. Solo development through 1.0; public contributions deferred until then.
Added
Editor core (M1)
- Persistent rope with O(log N) insert / delete / slice / snapshot.
- Buffers with chained
Viewtrait subscribers and intercept-edit hooks. - Undo / redo history with edit grouping.
- Atomic file load / save.
- Crossterm-driven TUI; cell-grid renderer abstracted for future GUI.
- Single-buffer text editing as the M1 deliverable: open a file, navigate, edit, undo, save.
Lua and command system (M2)
- Embedded Lua VM (LuaJIT default; Lua 5.4 supported via feature flag; cross-flavor test gates on both).
pmacs.command.define/invoke/exists; named introspectable commands for every M1 primitive.pmacs.keymap.bindwith global / mode / buffer scopes; multi-key sequences (e.g.,C-x C-s); buffer-scope wins over mode wins over global.pmacs.hookwith typed kinds:all-must-succeed,first-non-nil,last-write-wins. Built-in hook surface (buffer.before-save,process.after-tick, etc.).- Minibuffer implemented as a buffer (validates the universality of the buffer abstraction early).
describe-key/describe-commandfor self-introspection.
Async, workers, message bus (M3)
- Worker pool with stealable queues; coroutine-based
pmacs.asyncsurface for Lua. - Typed message bus; cancellation tokens that propagate through
awaiting coroutines;
supersedekeys for "newer query cancels older." - Acceptance test: parallel grep across a directory tree with cancel on new query, p99 cancel under 50 ms.
Language services (M4)
- Tree-sitter highlighting as a worker-pool service; per-buffer parse caches; incremental reparse on edit.
- LSP client (initialize / didOpen / didChange / didSave / completion / hover / definition / formatting / signature / diagnostics) with per-server async coroutines.
- Project indexing service; symbol search across 1M+ symbols completes under a second.
Frontend partition and remote attach (M5)
- Daemon mode (
pmacs --daemon) owning buffers, processes, language services as a long-lived instance. - Typed protocol over framed Unix-socket transport;
FrontendCapabilities,Hello,AttachRequest,FrontendEvent,InstanceMessage(CellDelta / Cursor / ModeLine / Bell). - Cell-delta diffing on the instance side; only changed cells go on the wire.
- SSH transport variant for remote attach; reconnect-on-drop modeled
on
mosh. Buffers, cursors, and process state preserved across laptop suspend / network drop / SSH server restart. - Keystroke-to-render p99 ≤ 10 ms over loopback
LocalSocket(M5.9c perf gate).
REPL package and ANSI (M6)
- ECMA-48 ANSI parser: SGR (8/16/256/24-bit color, bold, italic,
underline variants, strikethrough, reverse), intra-line cursor
motion, line-level erase, OSC title-setting, bracketed paste,
alt-screen suppression. Recovery rule: skip to end of CSI/OSC
structure, drop to ground at next
ESCor after 1 KiB, never crash/hang/corrupt. - PTY-aware process supervisor: raw / canonical line-discipline modes, SIGWINCH delivery on resize, child-exit cleanup, per-generation bounded byte channel (1 MiB / 128 slots) with per-tick coalescing.
- REPL package (
builtin/runtime/repl.lua, 691 lines): three-region buffer (history / prompt / input) with read-only enforcement on history; PTY-spawned shells (bash, zsh, fish, lua); buffer-scoped RET (submit) / C-c (SIGINT) / C-d (close stdin or delete-char- forward); exit markers with basenames and symbolic signal names; multi-REPL coexistence with per-handle parser/scrollback/binding state. - Output backpressure perf gates: sustained ingest ≥ 100 MB/s over 10 s; RSS delta ≤ 200 MB during the run sampled at 100 Hz; cancel response p99 ≤ 100 ms over 100 trials.
- Scrollback management: configurable retention (10000 lines / 16 MiB defaults); single-pass tick-driven truncation removing oldest complete command-output blocks; navigation latency p99 ≤ 16 ms across 10000-line scrollback; search p99 ≤ 100 ms; truncation correctness across 50 events.
- Multi-REPL: three concurrent REPL buffers render and respond independently; supervisor handles multiple PTY children without resource leaks (verified across 10 spawn-close cycles).
- API audit (the v0.1 ship gate): REPL package source 691 lines
(46% of 1500-line ceiling); 15 external functions, all through
documented
pmacs.X.Ynamespaces; zero direct Rust core access; zero reach-around patterns. Seven findings classified; one promoted-and-fixed in-audit (supervisor-record leak across spawn-close cycles, closed by restructuring the close path so_on_exitis the single point of teardown includingpmacs.process.forget).
Project posture
forbid(unsafe_code)across the entire crate. Syscalls that need unsafe (e.g.,sysconf) are bridged via/bin/sh/ coreutil / binary trampolines.- Cross-flavor CI: every test runs under both
luajitandlua54. Perf gates run release-only under dedicated CI jobs (m4-perf-gates,m5-perf-gates,m6-perf-gates). - 914 lib tests + per-milestone integration suites (m1-acceptance through m6_8, m6_perf, m4-acceptance, m5_5..m5_8, m6_4..m6_7).