pmacs/CHANGELOG.md

388 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [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_VERSION` bumped 2 → 3; `SUPPORTED_PROTOCOL_VERSIONS` now
`[1, 2, 3]`. The slice-membership handshake keeps v0.1/v1.0 binaries
connecting to v1.1 binaries unchanged.
- New `semantic_render` capability bit on `FrontendCapabilities`,
`InstanceCapabilities`, and `NegotiatedCapabilities`
(`#[serde(default)]`; instance default `false` until the M11.2
projection seam lands). `negotiate_capabilities` AND-combines it and
enforces the `semantic_render ⇒ crdt_replica` dependency (a semantic
session is also a text replica) — rejected as a
`CapabilityMismatch`, never silently degraded.
- `InstanceMessage` gains the `SemanticFrame` family: `StyleSpans`,
`Decorations`, `InlineAdornments`, `BlockAdornments`, `FoldState`,
`ResourceOffer`; `FrontendEvent` gains `Viewport`. All anchored in
byte offsets — the instance never learns a pixel.
- `PMACS_INSTANCE_SEMANTIC_RENDER` env 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 of
`instance_render::RenderState`: reads the same `EditorState` but
emits `InstanceMessage::StyleSpans` — tree-sitter spans mapped
through the active `Theme`, scoped and clipped to the byte range the
frontend declared via `FrontendEvent::Viewport`. Emits nothing until
a viewport is declared; suppresses byte-identical frames (true
span-granularity diffing is M11.4).
- `StyleSpans.generation` is anchored to `CrdtState::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 `SemanticRenderState` and never `CellDelta`/grid
`Cursor` (it lays out locally) but still receives `CursorByte`,
`BufferSnapshot`, `CrdtOp`, and presence. A grid and a semantic
frontend can attach to the same buffer simultaneously.
`FrontendEvent::Viewport` is consumed (routed by authenticated
source, like `CrdtOp`).
- `InstanceCapabilities` default `semantic_render` flipped to
`cfg!(feature = "crdt")` — the "M11.2 enables semantic" moment,
analogous to the M10.8 Day-4 `multi_frontend`/`crdt_replica` flip.
#### 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_uri`
is now `pub(crate)`, byte-identical to the Lua `file_uri_for`).
LSP `(line, col)` is converted to a byte range against the buffer
source; severity maps to `DiagnosticError`/`Warning`/`Info`/`Hint`.
- `StyleSpans` and `Decorations` suppress unchanged frames
independently — a selection move does not force a styling re-send.
- `Decorations::SearchMatch`/`SearchMatchActive`/`CurrentLine` are not
emitted: pmacs has no instance-side search-hit store, and
current-line is a pure cursor derivation the frontend already owns
via `CursorByte` (emitting it would breach the contract boundary).
- `InlineAdornments`/`BlockAdornments`/`FoldState` remain 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`/`Decorations` refined: `{ buffer_id, generation,
full: bool, segments: Vec<…Segment> }`. `full = true` is a resync
(frontend discards prior state for the buffer); `full = false`
ships only the dirty byte regions, each `StyleSegment` /
`DecorationSegment` replacing 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). `Decorations`
also gains `generation` for parity with `StyleSpans`.
- 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.
- `ResourceOffer` remains 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 the `BufferMirror` rope replica (M10.10) with a
tile-based `SemanticModel` that reconstructs styling/decorations
from the `full` + dirty-segment deltas (M11.4). Self-contained:
no terminal, no pixels. Emits `FrontendEvent::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 routes `StyleSpans`/`Decorations` to a semantic
session (after it declares a `Viewport`) and never to a grid
session, and `CellDelta` vice versa, validating the M11.2
per-session projection through the socket.
This completes the M11 semantic-frontend arc (M11.1M11.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 (M1M6) with the
M7M10 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`) with `version` /
`branch` / `commit` pins and a lockfile.
- Per-package environment with `exports`; `require` resolution 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`, `remove` with M3 `supersede` chaining 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
These planned v0.2 promotions were implemented and shipped in 1.0
after the public-API ceiling was raised by operator decision:
- `pmacs.buffer.from_file`, `pmacs.buffer.on_removed` (with an
idempotent `:remove()` handle; buffer-local keymaps pruned on
removal).
- `bypass_intercept` option on `buffer.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-attach`
bridge 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 with `PMACS_ATTACH_SSH_PROTOCOL=stdout|stderr` (the
legacy `PMACS_ATTACH_SSH_PROTOCOL_STDERR=0/1` is 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-attach` auto-start path was a disposable connect that
could consume the daemon's server-speaks-first `Hello`; 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 remains a v0.2 follow-up.
- **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 safe `nix` wrappers.
- 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 `luajit` and `lua54`;
clippy `-D warnings` clean on both flavors and the `crdt` lane;
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 `View` trait 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.bind` with global / mode / buffer scopes; multi-key
sequences (e.g., `C-x C-s`); buffer-scope wins over mode wins over
global.
- `pmacs.hook` with 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-command` for self-introspection.
#### Async, workers, message bus (M3)
- Worker pool with stealable queues; coroutine-based `pmacs.async`
surface for Lua.
- Typed message bus; cancellation tokens that propagate through
awaiting coroutines; `supersede` keys 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 `ESC` or 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.Y` namespaces; 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_exit` is the single point of teardown including
`pmacs.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 `luajit` and `lua54`.
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).
[1.0.0]: https://example.invalid/pmacs/releases/tag/v1.0.0
[0.1.0]: https://example.invalid/pmacs/releases/tag/v0.1.0