From 16702330571be7af5925091bd730ec7c1728e8d1 Mon Sep 17 00:00:00 2001 From: Levi Neuwirth Date: Tue, 7 Jul 2026 10:33:01 -0400 Subject: [PATCH 1/3] fix(gpu): gutter click classification + fit guard; test v14 wire round-trip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three correctness findings from the sub-arc 3 review: 1. (F1) GPU gutter clicks weren't classified before text hit-testing — the hit path just subtracted `text_left()` and called `buffer.hit()`, so a click in the gutter band fed glyphon a negative x (undefined) and gave future gutter markers no stable seam. Extracted `gutter_aware_rel_x`: a click left of the text origin clamps to `0.0` (the line start), mirroring the TUI's saturate-to-column-0 affordance. The hit path now branches on it — the seam a future marker would hook. 2. (F2) The GPU had no fit guard when the gutter consumed the text width. The TUI drops the gutter for a too-narrow window; the GPU always grew `text_left()` and `text_bounds_right()` floored against `TEXT_LEFT`, so a narrow window or very large file could produce `left >= right` (blank / undefined render). `gutter_width_px` now drops the gutter when it would leave less than `MIN_TEXT_WIDTH_PX` of text past `TEXT_LEFT`. 3. (F3) The v14 `LineNumbers { mode }` shape had no direct postcard round-trip (only the version pin + daemon gate). Added one covering all four `LineNumberMode` variants, so a future enum reorder can't silently shift the wire. Tests: `gutter_aware_rel_x` clamps the band (and passes through with the gutter off); a 60px window drops the gutter while an 800px one keeps it; all four modes round-trip. fmt + clippy --all-targets clean both flavors + gpu; 1447 lib + 12 protocol + 57 pmacs-gpu tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_014TXbAwk27agwhrNNrhLi2U --- pmacs-gpu/src/main.rs | 88 ++++++++++++++++++++++++++++++++++++++++++- src/protocol.rs | 22 +++++++++++ 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/pmacs-gpu/src/main.rs b/pmacs-gpu/src/main.rs index 22b3acf..711cfba 100644 --- a/pmacs-gpu/src/main.rs +++ b/pmacs-gpu/src/main.rs @@ -102,6 +102,11 @@ const GUTTER_MONO_ADVANCE_FALLBACK: f32 = 9.6; /// `W` its width; it spans the full line height. const GUTTER_SIGN_X: f32 = 4.0; const GUTTER_SIGN_W: f32 = 4.0; +/// Minimum text-area width (px) the gutter must leave. If reserving the +/// gutter would crowd the text below this, the gutter is dropped for the +/// frame — the GPU mirror of the TUI's too-narrow-window disable, so a +/// narrow window or a very large file can never force `left >= right`. +const MIN_TEXT_WIDTH_PX: f32 = 48.0; const MINIMAP_BG: [f32; 4] = [0.075, 0.075, 0.105, 0.92]; const MINIMAP_DEFAULT_LINE: [f32; 4] = [0.23, 0.23, 0.29, 0.82]; const MINIMAP_THUMB_FILL: [f32; 4] = [0.82, 0.82, 0.92, 0.18]; @@ -2818,7 +2823,19 @@ impl State { return 0.0; } let lines = self.current_line_starts.len().max(1); - decimal_digits(lines) as f32 * self.mono_advance() + GUTTER_GAP_PX + let want = decimal_digits(lines) as f32 * self.mono_advance() + GUTTER_GAP_PX; + // Fit guard (mirrors the TUI's too-narrow disable): never reserve so + // much gutter that the text area collapses. `text_bounds_right` is + // the text clip edge (minimap-aware); if the gutter would leave less + // than `MIN_TEXT_WIDTH_PX` past `TEXT_LEFT`, drop it this frame + // rather than shift `text_left` to or past the clip and render into + // a degenerate `left >= right` rectangle. + let avail = self.text_bounds_right() as f32 - TEXT_LEFT; + if want + MIN_TEXT_WIDTH_PX > avail { + 0.0 + } else { + want + } } /// The code's left origin in px: `TEXT_LEFT` plus the gutter. Every @@ -2882,6 +2899,22 @@ impl State { /// line) → projected byte → run map → slice byte → + `vstart`. /// `None` when no buffer is attached or the position is outside /// anything hit-testable. + /// Text-relative x for hit testing, classifying the gutter band first + /// (UX gutter, Q#UX6). A click left of the text origin (`raw_x < 0`, + /// i.e. inside the gutter) is not a text hit — it clamps to `0.0`, the + /// line start, rather than feeding glyphon a negative x (undefined). + /// Mirrors the TUI's saturate-to-column-0 affordance and is the stable + /// seam a future gutter marker would branch on instead of relying on + /// glyphon's negative-x edge behavior. + fn gutter_aware_rel_x(&self, x: f64) -> f32 { + let raw_x = x as f32 - self.text_left(); + if self.line_numbers.is_on() && raw_x < 0.0 { + 0.0 + } else { + raw_x + } + } + fn hit_test_source_byte(&mut self, x: f64, y: f64) -> Option { self.current_buffer_id?; if self.hit_map_dirty { @@ -2900,7 +2933,7 @@ impl State { self.projected_line_starts = projected_line_starts; self.hit_map_dirty = false; } - let rel_x = x as f32 - self.text_left(); + let rel_x = self.gutter_aware_rel_x(x); let rel_y = y as f32 - TEXT_TOP; let cursor = self.buffer.hit(rel_x, rel_y)?; let line_start = *self.projected_line_starts.get(cursor.line)?; @@ -7517,4 +7550,55 @@ mod tests { "relative numbering must differ from absolute ({differing} bytes differ)" ); } + + #[test] + fn gutter_aware_rel_x_clamps_the_gutter_band() { + // F1: a click in the gutter band (left of the text origin) clamps + // to the line start (rel_x 0), never a negative x into glyphon. + let text = "alpha\nbeta\ngamma\n"; + let Some(mut s) = headless_or_skip(400, 300, text) else { + return; + }; + s.line_numbers = LineNumberMode::Absolute; + let text_left = f64::from(s.text_left()); + assert!(text_left > f64::from(TEXT_LEFT), "the gutter is present"); + + // Inside the gutter band and at the exact origin → clamped to 0. + assert!(s.gutter_aware_rel_x(text_left - 4.0).abs() < f32::EPSILON); + assert!(s.gutter_aware_rel_x(text_left).abs() < f32::EPSILON); + // Well into the text → a positive text-relative x. + assert!(s.gutter_aware_rel_x(text_left + 40.0) > 0.0); + + // With the gutter off there's no band, so a left-of-origin x passes + // through negative (the pre-gutter behavior is unchanged). + s.line_numbers = LineNumberMode::Off; + assert!(s.gutter_aware_rel_x(f64::from(TEXT_LEFT) - 4.0) < 0.0); + } + + #[test] + fn narrow_window_drops_the_gutter() { + // F2: a window too narrow to fit the gutter + a minimum text area + // drops the gutter for the frame (no `left >= right`), mirroring the + // TUI. A wide window keeps it. + let text = "l1\nl2\nl3\n"; + let Some(mut narrow) = headless_or_skip(60, 200, text) else { + return; + }; + narrow.line_numbers = LineNumberMode::Absolute; + assert!( + narrow.gutter_width_px() < f32::EPSILON, + "a 60px window can't fit gutter + min text → gutter dropped" + ); + assert!( + (narrow.text_left() - TEXT_LEFT).abs() < f32::EPSILON, + "text origin unshifted when the gutter is dropped" + ); + + let mut wide = State::new_headless(800, 200, text).expect("adapter was just available"); + wide.line_numbers = LineNumberMode::Absolute; + assert!( + wide.gutter_width_px() > 0.0, + "a wide window keeps the gutter" + ); + } } diff --git a/src/protocol.rs b/src/protocol.rs index 1ece0e5..eaa0610 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -2769,6 +2769,28 @@ mod tests { } } + #[test] + fn line_numbers_all_modes_round_trip_through_postcard() { + // UX gutter v14: `LineNumbers` swapped `enabled: bool` for a + // `LineNumberMode` enum. Pin every variant's postcard shape so a + // future enum reorder / addition can't silently change the wire. + let bid = crate::buffer::BufferId::next(); + for mode in [ + LineNumberMode::Off, + LineNumberMode::Absolute, + LineNumberMode::Relative, + LineNumberMode::Hybrid, + ] { + let msg = InstanceMessage::LineNumbers { + buffer_id: bid, + mode, + }; + let bytes = postcard::to_allocvec(&msg).expect("encode"); + let decoded: InstanceMessage = postcard::from_bytes(&bytes).expect("decode"); + assert_eq!(msg, decoded); + } + } + #[test] fn frontend_event_viewport_round_trips_through_postcard() { let ev = FrontendEvent::Viewport { From cb459b79ef404e43527e6252f1e6c93abdb9a3d3 Mon Sep 17 00:00:00 2001 From: Levi Neuwirth Date: Tue, 7 Jul 2026 11:00:09 -0400 Subject: [PATCH 2/3] docs(readme): overhaul for the post-1.0 state Rewrites the stale sections: the intro now names both frontends (TUI + pmacs-gpu over protocol v14) and CRDT collaboration; Status reflects the post-1.0 arcs and points at docs/roadmap-2026-07.md; 'What v0.1 ships with' becomes a current Highlights section (LSP surface, gutter modes, search, context menu, packages, MCP); adds a Running section with real daemon/attach/GPU invocations; Layout updated to the three-crate workspace and the lua_bindings/ module dir. Build feature matrix and runtime-requirements sections kept as-is (still accurate). Co-Authored-By: Claude Fable 5 --- README.md | 190 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 140 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 51cc6b1..ecd5694 100644 --- a/README.md +++ b/README.md @@ -10,23 +10,113 @@ discards the single-threaded substrate; workers, message bus, and a coroutine-based async surface are core primitives, not bolt-ons. The editor is partitioned into a long-lived **instance** (the daemon -that owns buffers, processes, and language services) and a thin -**frontend** that attaches over a typed protocol. Frontends can run -locally over a Unix socket or remotely over SSH; reconnect-on-drop -modeled on `mosh` keeps remote sessions alive across laptop suspends. +that owns buffers, processes, and language services) and thin +**frontends** that attach over a typed protocol (currently v14). Two +frontends ship today: -The first-class package is a **REPL package** written entirely against -the public Lua API: PTY-spawned shells (bash, zsh, fish, lua), an -ECMA-48 ANSI parser, multi-REPL coexistence, and scrollback management -with line/byte retention. Successful completion of an audit verifying -the package uses zero direct Rust core access was the v0.1 ship gate. +- a **TUI** (crossterm cell grid), attachable locally over a Unix + socket or remotely over SSH, with reconnect-on-drop modeled on + `mosh`; and +- **pmacs-gpu**, a GPU frontend (wgpu + winit + glyphon) that renders + from a *semantic projection* of editor state --- style spans, + decorations, inlay adornments --- rather than a character grid, and + edits optimistically against a local CRDT replica for + latency-free typing. + +Buffers are optionally CRDT-backed (`loro`, behind `--features crdt`), +so multiple frontends --- TUI and GPU, local and remote --- can edit +the same buffers concurrently with live cursor/selection presence. ## Status -**v1.0.0 --- stable.** The design described above is implemented and -working. Solo development carried the project to 1.0; public -contributions are open from this release. Use, evaluate, file issues, -and send pull requests. +**v1.0.0 --- stable core, active development.** The v1.0 gate (the +instance/frontend partition, the Lua surface, and a REPL package +audited to use zero direct Rust core access) shipped some time ago; +development since has landed the GPU frontend at near input/render +parity with the TUI, the semantic-frontend protocol (v6 → v14), the +LSP feature arc, in-buffer search, the context menu + OS clipboard, +the line-number gutter with diagnostic signs, and the package-manager +hardening pass. Current direction lives in `docs/roadmap-2026-07.md`. +Public contributions are open: use, evaluate, file issues, send pull +requests. + +## Highlights + +**Editing & UI.** CUA-style region editing plus Emacs kill/yank +bindings; linear undo/redo; incremental search, substring and regex +(`C-s` / `C-r` / `C-M-s`); line-number gutter with absolute, relative, +and hybrid modes; diagnostic gutter signs; right-click context menu; +OS clipboard integration (OSC 52 in the TUI, native in the GPU); +minibuffer with completion dropdown and per-bucket persisted history; +buffer-list mode (`C-x C-b`); self-navigable help system +(`describe-command`, `describe-key`); atomic saves (temp + rename + +parent fsync, mode-preserving). + +**Language intelligence.** LSP client with async, never-blocking +requests: diagnostics (severity-colored underlines/squiggles, gutter +signs, statusline counts, `M-g n`/`M-g p` navigation), rename with +`prepareRename`, go-to-definition including cross-file navigation, +hover, signature help, references, document symbols, code actions, +buffer formatting, semantic tokens, and inlay hints (rendered inline +in the GPU frontend). Servers are preconfigured for rust-analyzer, +clangd (C/C++), basedpyright, gopls, typescript-language-server +(TS/TSX/JS/JSX), lua-language-server, bash-language-server, taplo +(TOML), and zls (Zig). Syntax highlighting is dual-authority: +bundled tree-sitter grammars (Rust, Lua, Markdown, C, C++) paint +lexical structure and LSP semantic tokens refine it --- languages +without a bundled grammar still get full semantic coloring. A +persistent project symbol index (`.pmacs/index.json`) rides the same +worker infrastructure. + +**Collaboration & frontends.** With `--features crdt`, buffers are +CRDT-backed and any number of frontends attach to one daemon and edit +concurrently; peers see each other's cursors and selections as +translucent washes. The GPU frontend adds a live minimap (click to +jump, drag to scrub), wavy diagnostic squiggles, a status band with +live diagnostic counts, and optimistic local editing that rebases +in-flight edits through authoritative frames. + +**Extensibility.** ~37 `pmacs.*` Lua namespaces cover buffers, +windows, commands, keymaps (global/mode/buffer scope), hooks, themes +(truecolor-capable syntax palette), tree-sitter, LSP stores, async +workers, and a PTY-aware process supervisor with an ECMA-48 ANSI +parser. A package manager installs from git (`github:owner/repo`, +version/branch/commit pins) with transitive dependency resolution and +a SHA-256 lockfile. Pmacs is also an **MCP client**: packages can +spawn MCP servers and consume their tools, resources, and prompts --- +AI integrations are packages over a transport, not a built-in +feature. The bundled REPL package (PTY shells, ANSI rendering, +multi-REPL, scrollback retention) is written entirely against the +public Lua API. + +## Running + +Single-process TUI: + +```sh +pmacs [FILE] # TUI; -nw reserved for when a GUI default lands +``` + +Daemon + attached frontends (build with `--features crdt` for +multi-frontend editing and the GPU frontend): + +```sh +pmacs --daemon --socket NAME # foreground daemon; bare NAME → + # /pmacs/NAME.sock +pmacs --attach --socket NAME # TUI frontend; F12 detaches +pmacs --attach user@host # remote TUI over SSH +pmacs-gpu --attach /run/user/$UID/pmacs/NAME.sock # GPU frontend +``` + +`pmacs --attach` also understands `ssh:user@host/instance`, +`local:/path.sock`, and bare hostnames (treated as SSH). See +`pmacs --help` for the full matrix. + +User configuration is plain Lua at +`$XDG_CONFIG_HOME/pmacs/init.lua` (default `~/.config/pmacs/init.lua`), +loaded after the builtin runtime so plain assignments override +defaults --- keybindings, `pmacs.lsp.config`, theme overrides, and +package installs all live there. ## Build @@ -34,7 +124,9 @@ Builds on the toolchain pinned in `rust-toolchain.toml` (Rust `1.95.0`, edition 2024); rustup selects it automatically. ```sh -cargo build --release # produce target/release/pmacs (LuaJIT) +cargo build --release # target/release/pmacs (LuaJIT flavor) +cargo build --release --features crdt # + CRDT buffers (daemon use) +cargo build --release -p pmacs-gpu # the GPU frontend binary cargo run --release -- # build and run on a file cargo test --workspace # unit + integration tests (all crates) cargo fmt --check @@ -76,7 +168,11 @@ CI runs the matrix on every push. Release-only perf gates (M5 keystroke-to-render, M6 ingest/RSS/cancel and scrollback navigation/search) are `#[ignore]`'d during normal -test runs and exercised in CI under dedicated jobs. +test runs and exercised in CI under dedicated jobs. The GPU frontend +has headless render tests (offscreen wgpu, pixels read back) that run +in CI under lavapipe and skip gracefully on machines without a Vulkan +adapter (`PMACS_REQUIRE_GPU=1` turns a missing adapter into a hard +failure). ## Runtime requirements @@ -125,59 +221,53 @@ The Lua VM (LuaJIT or Lua 5.4) is statically vendored via `mlua`'s `vendored` feature, so there is no external Lua dependency at runtime. -## What v0.1 ships with - -- **Editor core.** Persistent rope with O(log N) edits and snapshots; - buffers with chained intercept-views; undo/redo; atomic file I/O; - crossterm-driven TUI. -- **Lua surface.** Embedded LuaJIT (or Lua 5.4) with `pmacs.command`, - `pmacs.keymap` (global / mode / buffer scopes), `pmacs.hook` - (typed kinds: all-must-succeed, first-non-nil, last-write-wins), - `pmacs.buffer`, `pmacs.window`, `pmacs.editor`. Minibuffer is itself - a buffer. `describe-key` and `describe-command` for self-introspection. -- **Async runtime.** Worker pool + message bus + coroutine-based Lua - async surface (`pmacs.async`). Cancellation is provably correct - under load. -- **Language services.** Tree-sitter highlighting and LSP integration - ride the worker/message infrastructure. Project indexing as a third - service. Symbol search across 1M+ symbols completes under a second. -- **Frontend partition.** Daemon mode with local Unix-socket transport; - cell-delta diffing on the instance side; SSH transport variant for - remote attach; reconnect-on-drop preserves session state across - laptop suspend / network drop. -- **REPL package.** A 691-line Lua package that wires the M6 ANSI - parser to PTY-spawned shells with raw-mode line discipline. Three- - region buffer (history / prompt / input) with read-only enforcement; - RET / C-c / C-d bindings; multi-REPL coexistence; scrollback - retention with line- and byte-bounded truncation. Published - alongside an audit verifying zero direct Rust core access. +The GPU frontend additionally needs a Vulkan-capable driver stack +(any real GPU driver, or lavapipe for software rendering); its font +(JetBrains Mono, OFL-licensed) is bundled into the binary. ## Layout +The workspace has three first-party crates: + ``` -src/ Rust core +src/ pmacs — the core + TUI + daemon rope.rs persistent byte-sequence backing every buffer buffer.rs buffer + view chain + undo/redo editor_core.rs cursor + commands + edit dispatch + crdt.rs loro-backed CRDT buffer state (feature `crdt`) + daemon.rs instance side of the frontend partition + attach.rs frontend side; transports + reconnect + semantic_render.rs semantic-frame producer (StyleSpans, Decorations, …) + lsp.rs language-server client + diag.rs, highlight.rs diagnostic + syntax/semantic-token rendering + syntax.rs tree-sitter integration + search.rs incremental search (substring + regex) + minibuffer.rs prompt, completion, persisted history + menu.rs context-menu model + file_io.rs atomic saves + external-modification detection async_runtime.rs worker pool + message bus process.rs PTY-aware process supervisor ansi.rs ECMA-48 parser - daemon.rs instance side of the frontend partition - attach.rs frontend side; protocol + reconnect - lsp.rs language-server client - syntax.rs tree-sitter integration - project_index.rs symbol / file indexing + project.rs, project_index.rs project detection + symbol index + packages/ resolver, fetcher, installer, lockfile, loader + mcp.rs MCP client (packages speak to MCP servers) + lua_bindings/ pmacs.* Lua surface installers text_view.rs cell-grid renderer frontend.rs crossterm TUI - lua_bindings.rs pmacs.* Lua surface installers - main.rs entry point (TUI + daemon modes) + main.rs entry point (TUI / daemon / attach modes) + +pmacs-protocol/ wire types + framing codec shared by all frontends +pmacs-gpu/ the GPU frontend (wgpu + winit + glyphon) builtin/ Lua runtime shipped with the binary commands/default.lua named commands for every editor primitive keymaps/default.lua default key bindings hooks/default.lua built-in hook definitions - runtime/ packages (async, lsp, repl, syntax) + menus/default.lua context-menu items + runtime/ async, lsp, syntax, mcp, fs runtimes + packages/repl/ the bundled REPL package +docs/ design notes, framing docs, and the roadmap tests/ integration tests (acceptance gates per milestone) ``` From 642505099aa782710f36b331ba9d20a284a2be60 Mon Sep 17 00:00:00 2001 From: Levi Neuwirth Date: Tue, 7 Jul 2026 11:00:09 -0400 Subject: [PATCH 3/3] =?UTF-8?q?docs:=20July=202026=20roadmap=20=E2=80=94?= =?UTF-8?q?=20ranked=20arcs;=20Arc=201=20(LSP=20utility=20surface)=20activ?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit State assessment from a five-way sweep (core editing/persistence, LSP, GPU parity, extensibility/terminal, deferred-work inventory). Records the decision to push Arc 1 (completion popup, LSP panels, semantic-token auto-pull, signature trigger) with Arc 2 editing table-stakes interleaved, plus the ranked remaining arcs and housekeeping list. Co-Authored-By: Claude Fable 5 --- docs/roadmap-2026-07.md | 135 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 docs/roadmap-2026-07.md diff --git a/docs/roadmap-2026-07.md b/docs/roadmap-2026-07.md new file mode 100644 index 0000000..d48426e --- /dev/null +++ b/docs/roadmap-2026-07.md @@ -0,0 +1,135 @@ +# pmacs roadmap — July 2026 + +Date: 2026-07-07. Produced from a five-way codebase/docs/memory sweep +(core editing + persistence, LSP surface, GPU parity, extensibility + +terminal, deferred-work inventory across all framing docs). + +**Decision (2026-07-07): push Arc 1 (LSP utility surface), with Arc 2 +(editing table stakes) items interleaved between sub-arcs.** + +--- + +## State assessment (as of main @ ccb0ff6, protocol v14) + +**Strong.** CRDT multi-frontend core; both frontends at near input/render +parity (GPU exceeds TUI: minimap, wavy squiggles); LSP data layer — 13 +language ids / ~8 servers, rename + cross-file definition + formatting +fully working, semantic tokens + inlay hints rendering; package manager +(git installs, lockfile, resolver, SHA-256 hardening); MCP client; async +worker + PTY subprocess substrate; bundled line-oriented REPL package; +headless GPU render CI; help system; buffer-list UI; atomic save; +persisted minibuffer history. + +**Dark matter — built but unwired (highest leverage).** + +- Complete completion framework (`src/completion_framework.rs`: + providers for lsp/snippets/project-symbols/dabbrev; store; + `CompletionView` popup in `src/completion.rs`) — **no keybinding, no + typed-char trigger, popup never instantiated, no GPU message.** +- `HoverView` (`src/hover.rs`), `SignatureView` (`src/signature.rs`), + document-highlight store — plumbing only. Hover / signature / + references / document-symbols surface as one-line modeline summaries + (`builtin/runtime/lsp.lua` marks each "future UX work"). +- Code actions apply the **first** action blindly — no picker. +- Semantic-token pull is refresh/manual-only (no pull on attach/edit) — + semantic styling can silently never appear. Arguably a bug. +- Wire-declared but unproduced protocol families: `FoldState`, + `BlockAdornments`, `ResourceOffer` (no fold engine / blame / diff + source). + +**Absent.** Query-replace, kill ring (single slot only), keyboard +macros, rectangles, registers, comment/uncomment, auto-indent, +snippets, auto-pairing; desktop-save/session restore, recentf, +saveplace, autosave, backups, crash recovery; theming beyond syntax +captures (all chrome hardcoded per-frontend; GPU font hardcoded); +terminal emulation (`src/ansi.rs` parses but discards alt-screen / +cursor addressing — no grid/scrollback); compile/grep/shell-command +modes; DAP debugging; GPU splits / multi-buffer / auto-reconnect. + +--- + +## Arcs, ranked by value-per-effort + +### Arc 1 — LSP utility surface: "light up the dark matter" ← ACTIVE + +Data layer is done; only UI is missing. + +- **1a. In-buffer completion popup** (first). Trigger on typing, + TAB/RET accept, both frontends. GPU needs a wire message — mirror the + minibuffer-dropdown pattern (protocol v12). Framework + popup view + already exist. +- **1b. Panels**: hover popup, code-action picker, references list, + document-symbol outline. Generalize the buffer-list UI pattern + (`*buffer-list*` buffer-local bindings) into a reusable list-buffer + idiom. +- **1c. Semantic-token auto-pull fix** (small): pull on attach + on + edit-flush, like inlay hints already do. +- **1d. Signature-help auto-trigger** on `(`. + +### Arc 2 — Editing table stakes (interleave with Arc 1) + +Each small, core-only, frontend-agnostic: query-replace (isearch +exists; `search.rs` has no replace API), real kill ring + `M-y`, +comment/uncomment, auto-indent on newline, auto-pairing. + +### Arc 3 — Persistence/serialization + +Desktop-save (buffer set + layout + cursors → restore), recentf, +saveplace, autosave + crash recovery, optional backups. Generalize the +`$XDG_STATE_HOME/pmacs/` pattern from minibuffer history. Framing +question: what is a "session" in a daemon world; do CRDT snapshots +ride along. + +### Arc 4 — Themes + extensibility surface + +Extend `pmacs.theme` from syntax captures to named UI faces (modeline, +minibuffer, gutter, selection, status band); wire GPU chrome to it — +Q#UX1 lesson applies: rendering is frontend-local but control is +daemon-owned, so a wire channel (`ThemeFacts`-style) is needed. Add +`pmacs.gpu.set_font` (designed, never built) and a Lua +statusline-segment API. + +### Arc 5 — Terminal, staged + +- **Stage 1**: compile-mode / grep-mode / shell-command on the existing + PTY + ANSI + REPL-package substrate (line-oriented output buffer, + error-regex jump-to-file, `M-x compile`). Cheap, transformative. +- **Stage 2 (vterm)**: extend `ansi.rs` into a 2D grid model + (alt-screen, cursor addressing, scrollback — parser already + recognizes and discards these), grid-backed buffer view, GPU + rendering question (grid cells vs text buffer). + +### Arc 6 — Folding (keystone gutter rider) + +Fold engine (tree-sitter fold ranges) unblocks gutter fold markers + +the unproduced `FoldState` wire family + a visible feature. Git gutter +markers similarly just need a diff source. + +### Arc 7 — Debugging (DAP) + +Greenfield but newly unblocked: gutter (breakpoint signs), process +substrate (DAP = JSON-RPC over stdio, same shape as the LSP client), +list-buffer panels from Arc 1b (stack/variables). Frame after Arc 1 +ships — panels are reused here. + +### Arc 8 — GPU structural parity + +Splits / multi-buffer (largest unscoped design problem), auto-reconnect +after daemon restart, cursor blink, font/theme config (overlaps Arc 4). + +--- + +## Housekeeping (do opportunistically) + +- Merge PR #91 (gutter click classification + fit guard). +- Delete or mark-superseded stale docs: `pmacs-gpu-editing-perf-handoff.md` + (freeze fixed by PR #60 arc), `session-5-stale-styling-handover.md` + (fixed, task #25 closed), `python_experiment.md`, `#run.sh#`, + `semantic-frontend-protocol.md.local-bak`. +- `V0.2-PREREQUISITES.md` cited twice by CHANGELOG but missing on disk — + reconstruct or unlink. +- README still leads with "What v0.1 ships with" — refresh. +- F-016 lua_bindings split: ~5–8 tranches left; `editor.rs` (7k lines) + and `pmacs-gpu/main.rs` (7.6k lines) splits are named follow-up arcs. +- Deferred backlogs live in each framing doc's Deferred section; the + consolidated sweep is reflected above.