diff --git a/Cargo.lock b/Cargo.lock index ba0880f..489a87d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2583,6 +2583,7 @@ dependencies = [ "pmacs-protocol", "pollster", "sys-locale", + "unicode-width", "wgpu", "winit", ] diff --git a/docs/active-work.md b/docs/active-work.md index 1673ca2..ab47d8a 100644 --- a/docs/active-work.md +++ b/docs/active-work.md @@ -14,8 +14,8 @@ backlog. machine-local: `origin` may name this canonical URL, a release mirror, or something else, and therefore has no authority by name alone. - Canonical base at this snapshot: - `githubsucks/main` @ `1dd47fc` (modeline detection #132 merged atop Vterm - Stage 2 #130; protocol v18). + `githubsucks/main` @ `2625ec7` (tab-width parity #137 merged atop + locals-query #134 and modeline detection #132; protocol v18 on `main`). - On the transfer source, `origin/main` named a release mirror at `d3fa632` and lagged badly. On the current destination, `origin` names the canonical URL. This difference is why all recovery begins by @@ -49,16 +49,17 @@ git worktree list git status --short --branch ``` -The first command must expose `1dd47fc` or a newer intentional main. +The first command must expose `2625ec7` or a newer intentional main. If it does not, stop and repair the remote/fetch configuration. ## Vterm Stage 3 implementation lane - Portable branch: `githubsucks/vterm-gpu` - Framing carried as the first commit; implementation follows it. -- Base: canonical `main` @ `1dd47fc` (modeline detection #132 atop Vterm - Stage 2 #130). Cut from `main`, NOT stacked on `vterm-stage3-framing`, - per the framing's §8. +- Base: cut from canonical `main` @ `1dd47fc`, NOT stacked on + `vterm-stage3-framing`, per the framing's §8. Canonical `main` @ + `2625ec7` (tab-width parity #137, locals-query #134, modeline handoff + #133/#136) is MERGED IN — see the integration entry below. - PR: #135, , open against canonical `main`. Never merge without explicit authorization. - State: criteria 28-37 implemented. Protocol v19 (`SUPPORTED=[6..=19]`): @@ -99,11 +100,31 @@ If it does not, stop and repair the remote/fetch configuration. grid-missing press no longer arms a drag; roadmap/handoff Arc 5 lines corrected. Named deferral: terminal wheel gestures discard scroll magnitude. -- Post-round-2 gates: 1,758 default + 1,934 CRDT library tests; required - GPU 129; workspace sweep 2,923 across 83 suites; Stage 3 acceptance 5 - default / 7 CRDT; M4 120; fmt, clippy, diff check clean. +- Post-round-2 gates (pre-integration): 1,758 default + 1,934 CRDT library + tests; required GPU 129; workspace sweep 2,923 across 83 suites; Stage 3 + acceptance 5 default / 7 CRDT; M4 120; fmt, clippy, diff check clean. +- **Post-integration gates (canonical `main` @ `2625ec7` merged in):** + `cargo fmt --check`; strict workspace Clippy; `pmacs-protocol` 17; + `cargo test --lib` 1,768; `--features crdt` 1,944 (3 ignored each); + vterm Stage 1 9/10, Stage 2 4/4, Stage 3 5/7, statusline 7/8, tab-width + 2/2 (default/CRDT); M4 121 passed (3 ignored, 1 filtered); required GPU + 139; workspace sweep 2,946 passed across 84 suites (19 ignored), one + invocation; `git diff --check` clean. - Closed caveat: the once-seen required-GPU failure did not reproduce in eight author runs plus five reviewer runs. Treated as environmental. +- Canonical-main integration after #137 landed: the agreed order was + #137 first (it was approved and FROZEN at `5b23e11`, so it could not + absorb a rebase without breaking its freeze), then this lane second. + Integrated by MERGING canonical main into the branch, matching repo + precedent (`Merge canonical main into vterm-tui`, `… into modeline + detection`) rather than a rebase, which would have force-pushed away + the review anchors on #135. Main had also moved past this lane's base + by #133/#134/#136, so the integration surface was wider than the + #135/#137 overlap: `src/semantic_render.rs` was a fourth overlapping + code file and auto-merged, as did `pmacs-protocol/src/lib.rs`. The one + code conflict was the `pmacs_protocol` import list in + `pmacs-gpu/src/main.rs` (`TAB_STOP_COLUMNS` against the terminal + types) — resolved as a union. - Next: further user review rounds on the PR. Recovery worktree: @@ -115,37 +136,33 @@ git worktree add --track \ githubsucks/vterm-gpu ``` -## Cross-PR coordination: #135 and #137 overlap -Two PRs are open against canonical `main` at once and they touch the same -files. **Neither lane copies from or merges the other.** PR #137 -(`tab-width-parity`, "feat(render): unify tab-width projection") is -APPROVED and FROZEN at `5b23e11`; this lane must not cherry-pick, merge, -or otherwise absorb it. +## Cross-PR coordination: #135 and #137 (resolved) -**Whichever PR lands SECOND rebases onto the canonical resulting `main` -and reruns the COMPLETE gate suite** — not a subset, and not the -pre-rebase results. The overlap is in the renderer and the protocol -crate's export surface, so a clean textual merge does not imply a -correct one. +**Resolved 2026-07-22: #137 merged first, #135 integrated second.** Kept +as the worked example, because the deciding argument is reusable. -Overlapping paths, and why each one collides: +#137 was APPROVED and FROZEN at `5b23e11`. "Frozen" and "rebase onto the +resulting main" are mutually exclusive, so the frozen PR had to land +first — the alternative would have broken its freeze and voided its +approval. Three arguments pointed the same way: the approved PR should +not wait on an unapproved one; the PR carrying the protocol byte pins +should be the one that integrates, because its own suite is what detects +a disturbed discriminant; and the larger, more invasive change should +pay the integration cost, since its author has the context to verify the +merged result. -| Path | #135 (vterm stage 3) | #137 (tab width) | -| --- | --- | --- | -| `pmacs-gpu/src/main.rs` | terminal mode: `State` fields, render-batch swaps, input branches, headless probe | tab-width projection in the same renderer | -| `pmacs-protocol/src/lib.rs` | `pub mod terminal` + the terminal re-export block | its own added export | -| `Cargo.lock` | `unicode-width` promoted to a workspace dep for `pmacs-protocol` | a `pmacs-gpu` dependency change | -| `docs/active-work.md`, `docs/agent-handoff.md` | this lane's entries and §1 snapshot | its own entries | +The named overlap (`pmacs-gpu/src/main.rs`, `pmacs-protocol/src/lib.rs`, +`Cargo.lock`, both ledger docs) was accurate but incomplete — main had +also moved by #133/#134/#136, making `src/semantic_render.rs` a fourth +overlapping code file. **Lesson: derive the integration surface from +`git diff ..main`, not from the other PR's file list.** -`Cargo.lock` and the docs are mechanical. The two source files are not: -both PRs edit the GPU renderer's measurement/paint path, and both widen -`pmacs-protocol`'s public surface in the same `pub use` region. After the -rebase, re-read the merged region rather than trusting a conflict-free -apply — in particular that terminal cell geometry (`cell_viewport`, -`hit_test_cell`, `terminal_run_rect`) still uses the monospace advance -and is not routed through any new tab-expansion path, since terminal -columns come from the child and never from document projection. +The feared semantic collision did not occur. Terminal cell geometry +still uses the monospace advance and is not routed through +`TAB_STOP_COLUMNS`: terminal columns come from the child, and tab +expansion is a DOCUMENT projection concern. That is verified rather than +assumed — see the integration gates on the lane above. ## Vterm Stage 3 framing lane (superseded) @@ -190,6 +207,16 @@ git worktree add --track \ ## Closed since the last snapshot +- **Tab-width rendering parity — MERGED as #137** (`main` @ `2625ec7`, + 2026-07-22). One fixed 8-column `TAB_STOP_COLUMNS` in `pmacs-protocol` + now drives core/TUI columns, GPU code projection, and minimap width; + source bytes and protocol ranges are unchanged. Its lane and worktree + are done. This closes the long-standing "tab width is a rendering-parity + bug, NOT a config gap" deferral recorded in `docs/agent-handoff.md` §5. +- **Locals-query processing — MERGED as #134** (with handoff #136), and + **modeline detection handoff #133**. Both landed between this lane's + base and its canonical-main integration. + - **Config registry — MERGED as #127** (`main` @ `2e37c04`). Its lane (`config-registry`, worktree `../pmacs-config-registry`) is done; the branch is kept but carries nothing unmerged. Durable substrate facts diff --git a/docs/agent-handoff.md b/docs/agent-handoff.md index a4a857f..bf384ce 100644 --- a/docs/agent-handoff.md +++ b/docs/agent-handoff.md @@ -1,11 +1,12 @@ # Agent handoff — cross-machine continuity -**Last updated: 2026-07-22, after Vterm Stage 3 (protocol v19, GPU -terminal) was implemented on `vterm-gpu`. Vterm Stage 2 landed as PR #130 and -modeline detection landed as #132. Mode system wiring (#129), config registry -(#127), Vterm Stage 1 terminal core (#126), and completed Themes Arc 4 -(#120/#124/#125) are also on `main`. Stage 3 is implemented on `vterm-gpu` -and awaits review; see `docs/active-work.md`.** +**Last updated: 2026-07-22, after tab-width rendering parity (#137) and +locals-query processing (#134) landed on `main`, and canonical `main` was +merged into the Vterm Stage 3 lane. Vterm Stage 3 (protocol v19, GPU +terminal) is implemented on `vterm-gpu` and in review as PR #135; see +`docs/active-work.md`. Vterm Stage 2 (#130), modeline detection (#132), +mode system wiring (#129), config registry (#127), Vterm Stage 1 (#126), +and completed Themes Arc 4 (#120/#124/#125) are on `main`.** This file is the bridge between development machines. If you are an agent reading this on a fresh clone: this document plus the `docs/*-framing.md` @@ -19,7 +20,7 @@ commands, read `docs/active-work.md` immediately after this file. ## 1. Where the project stands (2026-07-22) -- `main` @ `1dd47fc` (modeline detection #132 atop Vterm Stage 2 #130), +- `main` @ `2625ec7` (tab-width parity #137 atop locals-query #134), protocol **v18** on `main`, **v19** on `vterm-gpu` (`SUPPORTED=[6..=19]`; v16 = `ThemeFacts`, v17 = `FontFacts`, v18 = `StatuslineSegments`). @@ -83,6 +84,21 @@ commands, read `docs/active-work.md` immediately after this file. - The built-in `mode` statusline provider reads `ctx.buffer`, so passive splits render their own mode. Real-daemon acceptance covers all ten framing criteria across both Lua backends and Linux/macOS CI. +- **Modeline language detection LANDED — #132** + (`docs/modeline-detection-framing.md` rev 2; merge `1dd47fc`). Fresh loads + scan bounded Emacs `-*- mode: ... -*-` and Vim/Vi `ft=` / `filetype=` + modelines without evaluating file content, normalize common editor aliases, + and give explicit modelines precedence over inferred language. + - `builtin/runtime/syntax.lua` owns one per-buffer fresh-load decision: + modeline → bundled grammar extension → LSP filetype extension → exact + filename → shebang. Syntax, initial major mode, pairing, comments, and LSP + all reuse that pin; LSP retains its independent backing-path guard. + - Editing a modeline or shebang does not switch an attached parser or make + language-aware consumers diverge. Close/reopen re-evaluates changed file + metadata. Explicit post-load major-mode overrides remain independent. + - Bounded valid unknown names remain passive major modes without starting an + unavailable parser or server. All thirteen framing criteria are covered on + LuaJIT and Lua 5.4. No Rust or protocol surface changed; protocol stays v18. - **Syntax-highlight / language-detection side-quest (#114–#118) LANDED** — a one-shot arc built in sibling worktrees off main while the user's themes lane (`theme-faces`) ran concurrently in the shared @@ -92,25 +108,29 @@ commands, read `docs/active-work.md` immediately after this file. the ABI-current `tree-sitter-containerfile`, NOT the dead `tree-sitter-dockerfile` which pins `tree-sitter ^0.20`), make, cmake, python, go, javascript (+jsx), typescript (+tsx), toml, zig. - - **Detection chain** (`resolve_active_language` in syntax.lua / - `buffer_language` in lsp.lua): extension → LSP filetype map → - filename → shebang. New user-extensible Lua surfaces - `pmacs.parse.shebangs` / `.filenames` and - `pmacs.parse.language_from_shebang` / `language_from_filename`. - Grammar name MUST equal the `pmacs.lsp.config.` key (grammar - detection wins over the filetype map, so it fixes the LSP id too). - A buffer keeps its first-attached grammar across edits/switches (no - re-sniff of a since-edited shebang). + - **Detection chain and pin** (`builtin/runtime/syntax.lua`): modeline → + grammar extension → LSP filetype map → filename → shebang. User-extensible + Lua surfaces include `pmacs.parse.modeline_aliases`, `.shebangs`, + `.filenames`, `language_from_modeline`, `language_from_shebang`, + `language_from_filename`, and the pinned `buffer_language`. + `builtin/runtime/lsp.lua` delegates to that shared decision after enforcing + its backing-path requirement. Grammar name MUST equal the + `pmacs.lsp.config.` key. A buffer keeps its pinned language across + edits/switches; close/reopen performs a fresh bounded inference. - **LSP configs added**: dockerfile (`docker-langserver --stdio`), cmake (`cmake-language-server`, config via `init_options.buildDirectory="build"` — it does NOT pull `workspace/configuration`). Make has no server. - - **Substrate**: `LanguageEntry.highlights_query` is now - `&[&'static str]` — fragments joined base-first, for grammars whose - bundled highlights are a `; inherits:` delta (cuda over c/cpp; ts - over js/jsx). `compute_highlight_spans` FAILS CLOSED on the - `#is?`/`#is-not? local` property predicate (no locals processing) — - drops those captures so shadowed builtins aren't mis-styled. + - **Substrate**: `LanguageEntry.highlights_query` and `.locals_query` are + `&[&'static str]` fragments joined base-first (cuda over c/cpp; ts over + js/jsx). Since locals-query processing #134, settle compiles the + grammar's `LOCALS_QUERY`, resolves Tree-sitter's scope/definition/value/ + reference conventions into sorted `LocalFacts`, and stores them beside + each layer's tree/query. Work runs once per fresh bundle and only when the + highlight query asks about `local`; viewport rendering remains bounded. + Both TUI and semantic/GPU producers evaluate `#is?`/`#is-not? local` + through the shared capture walk. Non-shadowed JS/TS builtins are restored; + shadowed definitions/references keep ordinary variable styling. - **Multi-language injections (#122) LANDED** — the direct continuation of the #114–#118 highlight arc; four review rounds, framing `docs/multi-language-injections-framing.md` (Q#IJ1–IJ11). A buffer can @@ -130,7 +150,8 @@ commands, read `docs/active-work.md` immediately after this file. worker never touches the `Rc` registry or Lua); `ParseTreeBundle.injection_capped` (the 4096-layer backstop, surfaced once/buffer via `pmacs.error` at settle); - `compute_highlight_spans_for(query, tree, source, range)` (per-layer); + `compute_highlight_spans_for(query, tree, source, local_facts, range)` + (per-layer); the wire `flatten_layer_spans` event-sweep → DISJOINT effective spans (deeper / later-sibling / narrower wins, keyed by `(layer_index, capture_order)`); GPU `spans_from_segments` + `source_color_at` fold. @@ -279,9 +300,11 @@ commands, read `docs/active-work.md` immediately after this file. origin. `pmacs-gpu --headless-probe` drives the real attach client without winit (`attach::connect_with_sink`), which is how criterion 37 gets one real daemon + real PTY + real wgpu path. - - **Stage 2 TUI LANDED ON `main` — #130** (`docs/vterm-framing.md` - Revision 7, criteria 15–27). `TerminalViewKey` keys per-frontend/window - projection state over one shared process/screen; logical row anchors retain + - **Stage 2 TUI LANDED ON `main` — #130** (merge `86fc1bc`; + `docs/vterm-framing.md` Revision 7, criteria 15–27). `TerminalViewKey` keys + per-frontend/window projection state over one shared process/screen; logical + row anchors retain + scroll/selection through reflow. One authenticated frontend controls at most one session, with atomic replacement and release on focus/switch/kill/detach. @@ -327,15 +350,28 @@ commands, read `docs/active-work.md` immediately after this file. 8 CRDT; M4 114 passed (3 ignored, 1 filtered); required GPU 109; workspace 2,882 passed across 82 suites (19 ignored, 1 filtered); `git diff --check` clean. - - **Stage 3 framing is Revision 8 on `vterm-stage3-framing`**: additive - protocol v19 complete frames/events, dual viewport bootstrap, common - validation/aggregate limits, authenticated semantic view adapters, and - fixed-cell GPU rendering/input/cache behavior. Criteria 28–37 are mapped. - First review found no architectural defect; `c72dfea` pins maximal style - and cluster-prefix overhead in the measured frame fixture, corrects the - GPU clipboard criterion, and distinguishes Arc 5 stage 2 from its internal - Stages 1–3. No implementation branch or PR exists pending explicit user - approval. +- **Tab-width rendering parity LANDED — #137** (merge `2625ec7`; + `docs/tab-width-parity-framing.md` rev 2). + Source tabs remain one byte while every buffer + renderer follows the shared fixed `pmacs_protocol::TAB_STOP_COLUMNS = 8`. + - `src/display_width.rs` owns allocation-free Unicode/tab-aware byte-to-column + accounting for plain text, syntax, diagnostics, completion anchors, + buffer-style overlays, and search washes. + - The GPU rich-chunk projection expands source/adornment tabs before + cosmic-text shaping and retains first-class source-tab provenance. + Carets, hits, selections, peer washes, and diagnostic geometry share the + same source/projected boundary rules, including a soft wrap inside one + expanded tab. + - GPU minimap widths use the same tab/Unicode rule and refresh in the accepted + text-edit transaction. No config, wire shape, negotiation, or protocol + version changed. Local gates: 1,763 default + 1,939 CRDT + 1,763 Lua 5.4 + library tests; 2 focused acceptance; M4 121; required GPU 119; workspace + 2,911 across 83 suites; strict Clippy and diff check clean. + - This closes the standing "**tab width is a rendering-parity bug, NOT a + config gap**" deferral in §5: one shared constant now drives every + renderer. Terminal cells are deliberately OUTSIDE it — a terminal's + columns come from the child, so `pmacs-gpu`'s terminal geometry uses + the monospace advance and never `TAB_STOP_COLUMNS`. - **PARKED: kill-ring browser + persistence.** Revision 2 framing is preserved on branch `kill-ring-browser`, but its `0efb5cd` scout is stale and must be repeated before implementation. No PR or implementation is @@ -361,6 +397,10 @@ commands, read `docs/active-work.md` immediately after this file. editing/indent/comment items that were config-blocked. - **Mode system wiring COMPLETE (#129)** — major-mode keymaps, introspection, lifecycle initialization, and statusline display shipped. + - **Locals-query processing COMPLETE — #134** — grammar locals metadata, + lexical resolution, settled per-layer facts, shared TUI/GPU + local-predicate filtering, and a registry-wide locals-query invariant + shipped without a protocol change. - Remaining ranked arcs: 6 folding, 7 DAP, 8 GPU splits, plus the `.ipynb` arc (its JSON-grammar prerequisite shipped in #123). @@ -521,19 +561,14 @@ cannot detect a discriminant shift. in per-session baselines; and any daemon-side reset needs its frontend mirror audited in the same round (the GPU snapshot arm missed search/menu/status the first time). -- **Tab width is a rendering-parity bug, NOT a config gap** (scouted at - `7bc0c61` while framing #127; still true). There are FIVE tab-width - sites across TWO crates with TWO different values: `TAB_WIDTH = 8` in - `src/text_view.rs`, `src/highlight.rs`, `src/diag.rs` and - `src/completion.rs`, versus `advance_minimap_col` in - `pmacs-gpu/src/main.rs` expanding to **4** — and the GPU's main text - path expands tabs *not at all* (buffer bytes reach the frontend raw, - so a literal `\t` is shaped by the font). `editor.tab-width` is - therefore the obvious-looking first config adopter and is not one: - defining the setting cannot make the GPU honor it. Doing it properly - needs frontend tab expansion plus a wire-or-frontend-local decision. - Deferred from #127 on exactly these grounds; don't re-plan it as a - config task. +- **Tab width is a rendering semantic, NOT a config gap.** The implementation + on `tab-width-parity` fixes the width at the TUI's established 8 columns, + shares that constant through `pmacs-protocol`, and expands tabs only in each + display projection. Defining `editor.tab-width` could not have fixed the GPU: + source text and semantic spans stay byte-addressed while cosmic-text needs + projected spaces plus an inverse hit/caret map. A future configurable width + would require a buffer-effective frontend fact and cache invalidation; do not + re-plan it as a scalar config-only change. - **A test that never runs passes.** Two #127 review-round tests passed vacuously at first: `pmacs.editor.save()` is the RAW save, while `buffer.before-save` fires inside the `buffer.save` COMMAND @@ -626,10 +661,8 @@ and Vim `ft=`/`filetype=` parsing, explicit-over-inferred precedence, alias normalization, and shared fresh-load language pinning for syntax/highlight/LSP startup. Highlight/detection (from the #114–#118 side-quest + injections #122): -locals-query processing (run each grammar's LOCALS_QUERY so -`#is?`/`#is-not? local` is honored instead of the current fail-closed -drop — restores `.builtin` styling for non-shadowed console/require -etc.); **injection follow-ups now the engine landed (#122)** — +~~locals-query processing~~ **SHIPPED #134**; remaining injection follow-ups +now that the engine landed (#122) — `injection.combined` (many matches → one shared parse; PHP-in-HTML, some comment schemes), child-tree incrementality + range-scoped layer rebuild (child layers cold-reparse on every settle today), injectable @@ -637,6 +670,8 @@ runtime/Lua-registered languages (v1 resolves only against `BUILTIN_LANGUAGES`), and the next injection *consumers* gated on new grammars — HTML/CSS/GraphQL/SQL (`