31 Commits
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
84b1620e7e
|
feat(release): binaries on tag — Distribution Stage 1
.github/workflows/ had exactly one workflow and it was test-only: no
release job, no artifact upload, no tags-to-binaries path. Installing
pmacs meant `git clone` plus knowing the feature-flag matrix.
COHERENCE.md §17 grades this "missing — zero release machinery exists";
this moves it to Partial and completes journey step 1.
Scope is one stage: binaries when a `v*` tag is pushed, attached to a
GitHub Release. Channels, rollback, update-in-place, signing, RHEL 9 and
Intel macOS are out of scope and named in the framing's §5.
WHAT SHIPS: pmacs and pmacs-gpu, both at 1.1.0, CRDT-enabled, co-located
in one archive, with SHA256SUMS. pmacs-protocol stays at 1.0.0 — it is
the wire crate and versions on its own schedule.
THE VERSION BUMP EXPOSED A REAL DEFECT, and it is the reason this PR
touches src/ at all. `InstanceIdentity::for_running_process` is defined
in pmacs-protocol and expanded `env!("CARGO_PKG_VERSION")` THERE. `env!`
expands in the crate being compiled, so the field documented as "Pmacs
version string" carried the PROTOCOL crate's version. That identity
reaches Lua as `pmacs.instance.identity()` and goes on the wire in
`Hello`, so a 1.1.0 release would have told every attached frontend it
was 1.0.0.
Nothing could have caught it earlier. Three tests assert
`id.pmacs_version == env!("CARGO_PKG_VERSION")` evaluated in the pmacs
crate — the correct assertion — but while both crates read 1.0.0 they
compared the same number reached by two different paths and COULD NOT
FAIL. Deciding to hold pmacs-protocol at 1.0.0 while moving pmacs is
what made them discriminating; all three failed on the bump. The version
is now a parameter so `env!` expands in the caller's crate. A test can
be correct and still prove nothing when the two things it compares are
equal for a reason unrelated to the code under test.
TWO LAYERS OF BINARY EXCLUSION, and layer 2 is load-bearing —
demonstrated, not argued. Cargo auto-discovers src/bin/*.rs, so a
release build can produce five binaries and three must never ship
(pmacs-audit is a contributor tool; pmacs_fake_lsp and pmacs_fake_mcp
are test fixtures). Layer 1 names explicit --bin targets. Layer 2 stages
an explicit asset list, and building this branch produced exactly the
case it guards: after building ONLY --bin pmacs and -p pmacs-gpu,
target/release still held all three forbidden binaries, left by an
earlier `cargo test --release`. Swatinem/rust-cache restores that kind
of directory in CI. An implementation trusting layer 1 and archiving the
directory would have published a fake language server in the first
release.
The three archive assertions are bite-verified: a smuggled
pmacs_fake_lsp, a missing pmacs-gpu, and a cleared executable bit are
each caught, with the honest archive passing.
THE GLIBC FLOOR IS ASSERTED, NOT TRUSTED. Pinning ubuntu-22.04 sets the
floor at 2.35 (Ubuntu 22.04, Debian 12 — NOT RHEL 9 at 2.34, which needs
a container or cross-build and is parked). But a pinned runner proves
nothing about the artifact, and the failure surfaces as a bare
`GLIBC_2.39 not found` on a user's machine with no clue which commit
caused it. The build reads versioned-symbol requirements out of the
binary and fails above the floor, so switching to ubuntu-latest fails in
CI instead of shipping. Bite-verified both directions on a glibc 2.44
host. Both runners are pinned; macos-latest would drift the minimum
supported macOS with no commit to point at.
Preflight runs before any build: the tag must match the root crate
version (stripping a prerelease suffix, so v1.1.0-rc.1 and v1.1.0 both
match 1.1.0), and the tagged commit must be an ancestor of main. Both
catch mistakes that are cheap now and expensive once a public URL
exists. The suite is not re-run — CI already tested the commit — but
nothing otherwise enforced that a tag points at a tested one.
Verified: fmt, diff-check, clippy with and without crdt, --lib 1896,
--lib --features crdt 2081, pmacs-protocol 19, m4 149, required GPU 221,
and the full serialized crdt sweep at 3,715 passed / 0 failed / 30
ignored — identical to the pre-change baseline, so the protocol
signature change broke nothing. Archive staging, contents, executable
bits and both --version outputs were exercised against a real release
build locally.
No release is cut by this PR. Per the framing's §7 the RC is tagged
after merge, from the merge SHA.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
|
|
b27df705bb |
feat(process): make the signal diagnostic discriminating (Bets 2-4)
Framing acceptances 2, 3, 4, 5, 7 and 8. Evidence collection only: no tolerance rule, no change to which process is signalled, no disposition change. Three distinct failures previously rendered as one string. **The PTY fallback is now named.** When a PTY's foreground-group lookup yields no group, the target falls back to the leader — and until now that rendered "leader-pid", identical to a pipe child that never had a terminal. `portable-pty::MasterPty::process_group_leader` collapses every failure into `None` before pmacs can see it, so the errno was gone too. pmacs now performs the query itself and reports four distinct outcomes: no master fd, a failed duplicate with its errno, a failed `tcgetpgrp` with its errno, and a non-positive answer. Doing that without `unsafe` is the interesting part. `nix::unistd:: tcgetpgrp` needs `AsFd`; `MasterPty` exposes only `Option<RawFd>`; and every std route between them is `unsafe`, which this crate forbids. `filedescriptor::OwnedHandle::dup` takes any `AsRawFd` through a safe blanket impl and returns an owned handle that IS `AsFd`, so a lifetime-tied view implementing one safe trait is the whole bridge. The borrow is what makes it sound: the view cannot outlive the master, so the descriptor cannot close underneath it. **The report names the signal.** A failed SIGUSR1 and a failed SIGTERM were the same text. Note this is a reporting gap only — every failed `kill` returns before the fatal-signal branch, so failed signals are disposition-identical whatever they are. A separate control pins that the fatal/non-fatal difference is real for calls that SUCCEED, which is what gives the first test its meaning. **`measured_group` is a real observation.** `expected_group` is `-leader_pid`, and on the spawn-group path the target is `-leader_pid` too, so the report printed the same number three times and their agreement was arithmetic rather than evidence. `getpgid` supplies the one field that can disagree. It establishes no identity — it is read inside the same read-then-act window, and no portable mechanism closes that for a group. Bites, each by an actual revert, all observed to fail: - collapsing the PTY fallback back into a bare "leader-pid"; - dropping `signal=` from the report; - making the measured group restate the pid it was handed; - replacing the job-control fixture with a plain `sleep`, as a positive control on the divergence fixture itself. All four exact-string sites were updated individually, never by a blanket rewrite: a wholesale rewrite of expected strings is how a format regression hides. `:2501`'s first-call disposition pin is retained and updated for the new format rather than replaced. `nix`'s `process` feature is now declared explicitly. It already arrived transitively — nix's own `signal` feature depends on it — which is stable but invisible, and a real requirement resting on another feature's internals is one refactor away from vanishing. `filedescriptor` is declared directly for the same reason: pmacs now calls its API. The reap ledger's comment claiming "EPERM cannot happen for our own children" is corrected. Its bounded-growth policy is unchanged, but the justification was wrong: the probe targets a group, and owning the spawned child says nothing about a group unless the child is still a member — which nothing measures. The handoff records this together with the limit of the evidence: the occurrence does NOT establish that the child itself received EPERM. |
|
|
|
f3e065dc78 |
Merge canonical main (8c86d34) into the inline-math slice
The lane was 28 commits behind. Merged rather than rebased, per the #135/#137 precedent: the PR is awaiting review rounds and a rebase would break every review anchor. The only conflict was docs/active-work.md, where both sides add lanes. Kept both: main's lanes verbatim, with this lane leading since it is the one in flight. The conflict was pre-existing rather than introduced by the dired or Lean 4 ledger commits -- it already conflicted against main at |
|
|
|
6ea8d2756e |
feat(syntax): bundle the Lean 4 grammar (Arc 8 Stage 1, Q#LN1-3)
Adds `arborium-lean` 2.18 and one `BUILTIN_LANGUAGES` entry, closing the framing's open verification obligation on the crate choice. Why this crate and not `tree-sitter-lean4` (Q#LN1): the latter depends on `tree-sitter = "0.25"` directly rather than the shared `tree-sitter-language` ABI crate, and `^0.25` excludes our 0.26, so it would fork the graph exactly as the dead `tree-sitter-dockerfile` does. It also exports only `pub fn language()` while its README advertises a `LANGUAGE` const that does not exist, and its package `include` omits `queries/` so it ships no highlights at all. `arborium-lean` uses `tree-sitter-language 0.1` as its sole runtime dep, ships a pre-generated ABI-15 parser plus scanner, and exports real query constants. `cargo tree -d` reports no duplicate `tree-sitter`. The entry is named `lean4`, not `lean` (Q#LN2): `ensure_server` passes `LanguageEntry.name` through as the `didOpen` language_id, and the Lean ecosystem's id is `lean4` -- `lean` is Lean 3, which is end-of-life. It claims `.lean` only; `.olean` is a compiled binary and `.ilean` is JSON metadata (Q#LN3). Four tests. The load-bearing one is `lean4_grammar_loads_and_parses`, which discharges the half of Q#LN1 that could not be settled by reading: `arborium-lean` exports `const fn language() -> LanguageFn` rather than the `LANGUAGE` const every other entry uses, and its README demonstrates usage against a patched tree-sitter core. Neither is supposed to matter, but "supposed to" is not evidence. The fixture parses without error, and -- the part that actually guards a misbuild -- its Unicode operators produce structure rather than degrading silently: the grammar must see a `(arrow)` for the arrow, a `(forall)` for the universal quantifier, and a `(comparison)` for the inequality. The error-free claim is deliberately scoped to the committed fixture. Lean's syntax is user-extensible via macros, so a static grammar mis-parses some legal input by construction; the framing scores that as bet 3 rather than the doc overselling it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
|
|
|
320bcce276 |
feat(math): bundle Latin Modern Math and read its MATH constants
Font, licence, and the metrics half of Tier 3. The bundled font is Latin Modern Math under the GUST Font License, added as fonts/GUST-FONT-LICENSE.txt — deliberately a separate file from fonts/OFL.txt, which covers JetBrains Mono only. GFL is LPPL-derived, not the SIL OFL; the framing's F6 corrected that error and this is the discharge. At 733,736 bytes the font is now the largest embedded asset in the repository. ttf-parser is declared with default-features = false and only "opentype-layout". Verified differentially: the ttf-parser feature set from `cargo tree -e features` is byte-identical with and without this dependency line, so the declaration widens nothing and forces no rebuild of the font chain. That check also corrected acceptance 17, which asserted `std` would be absent. It is not — fontdb already enables it via `std = ["ttf-parser/std"]`, upstream and independent of us. As written the criterion would have failed a correct implementation, so it is now stated as the differential property that actually matters. MathConstants reads only what the Q#MS2 subset needs — axis height, script scale percent, the two script shifts, and fraction rule thickness. Reading more would be speculative: constants for deferred constructs have no consumer to validate them, which is the Q#LX5 discipline applied to metrics. A font with no MATH table is a typed error rather than plausible-looking zeros, so a bundled-font regression cannot be silent (Q#MS7). math_italic implements TeX's convention as the framing's table states it: ASCII letters and lowercase Greek italic, uppercase Greek upright, digits and operators unchanged, with U+210E for `h` because the 1D4xx run has a hole there and arithmetic would land on a reserved codepoint. Five tests, all against the real embedded bytes rather than fixtures, since B5 is the bet that would sink Tier 3 if false. One goes beyond the framing: every italic mapping must resolve to a glyph the bundled font actually has, because a mapping that produced tofu would be worse than the roman fallback it replaces. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
|
|
|
394d39942c |
feat(web): bundle HTML + CSS grammars + HTML injections
Register `html` (.html/.htm/.xhtml) and `css` (.css) entries in BUILTIN_LANGUAGES, backed by the official tree-sitter-html 0.23 and tree-sitter-css 0.25 grammars over the tree-sitter-language shim (ABI-fine, no overlay — both export their query constants). The single `extensions` field wires detection ahead of the LSP filetype map. HTML's crate-exported INJECTIONS_QUERY lights up <script> -> javascript (already registered) and <style> -> css (registered here) via the #122 injection engine — the north-star injection consumer. The only capture reconciliation (Q#WEB4): the two web captures both grammars' queries use that pmacs did not recognize — ("tag", fg(5)) and ("attribute", fg(3)) — added to highlight.rs's table; @tag.error prefix-walks to tag, and everything else already maps. Tests: table guards; load-and-parse smokes (roots document/stylesheet); highlights-resolve (node-name compat gate, asserts @tag present); extension resolution; a tag+attribute paint test (the attribute assertion is load-bearing); and the injection payoff — an HTML buffer with embedded <style>/<script> paints a CSS property and a JS keyword INSIDE the injected regions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|
|
|
f09b0a1142
|
Merge pull request #144 from levineuwirth/latex-grammar
feat(latex): Stage 1 — LaTeX/TeX grammar highlighting (inline-math substrate) |
|
|
|
f11d625fb0 |
feat(latex): bundle LaTeX grammar + reconcile highlights + tests
Register a `latex` entry in BUILTIN_LANGUAGES (.tex/.latex/.sty/.cls) backed by
codebook-tree-sitter-latex 0.6.1 — the linkable republish of latex-lsp's grammar
over the tree-sitter-language shim (the squatted `tree-sitter-latex` 0.1.0 ships
no scanner.c and cannot link). The single `extensions` field wires the whole
detection chain ahead of the LSP filetype map, so no Lua edit is needed.
The crate exports no query constants, so highlighting is driven by the in-repo
overlay builtin/queries/latex/highlights.scm — the first such overlay,
include_str!'d as LATEX_HIGHLIGHTS (the audit-rules.scm precedent). This commit
reconciles the vendored nvim-treesitter query (previous commit) onto pmacs'
recognized capture set:
* strip @spell/@nospell — meaningless to pmacs, and clobber-risk on a
multi-capture node;
* remove the 8 #eq?/#any-of?/#lua-match? patterns — pmacs evaluates only
`#is? local`, so unevaluated they would over-match every generic command
(as conditional/emphasis) and every line comment (as a magic directive);
* remap fall-through captures: @module->keyword, @label->type,
@markup.heading*->keyword.control, @markup.link*->constant,
@markup.math->string;
* fix node-name drift: this grammar cut uses curly_group_label(_list) for the
label commands where newer latex-lsp unified them onto curly_group_text.
Tests (framing acceptance): table guard; load-and-parse including a verbatim
environment (exercises the external scanner the broken crate lacked);
highlights-resolve (doubles as the grammar/query node-name compatibility gate);
and extension resolution.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
|
|
82355ca529 |
Address GPU invocation review findings
Buffer attach events until winit state exists, keep spawned daemon ownership until the reaper handoff, and detach daemon stderr from the launcher terminal. Tighten direct GPU CLI guidance and sibling discovery. Strengthen managed connector unit and process acceptance coverage for transient retries, timeout reporting, hermetic paths, and deterministic loser reaping. |
|
|
|
3c4d969aba |
Merge canonical main into vterm stage 3
Integrates canonical `main` @ |
|
|
|
9f7bc77f44 |
feat(render): unify tab-width projection
Share one fixed eight-column tab-stop contract across core and GPU renderers. Consolidate byte-to-display-column accounting, expand GPU code tabs with source provenance, align caret/hit/decoration geometry, and refresh minimap projection on edits. |
|
|
|
bdf2b6e4b4 |
feat(vterm): protocol v19 terminal frames and a native GPU terminal
Vterm Stage 3 — the final vterm stage. A semantic frontend can now host a terminal: the daemon ships complete validated cell grids, and pmacs-gpu renders them with fixed-cell geometry, its own input path, and no document projection at all. Protocol v19 appends three variants after their enums' final v18 members: InstanceMessage::TerminalFrame (daemon-gated), and FrontendEvent:: TerminalResize / TerminalPointer (frontend-gated). It is the first bump to gate in both directions, so criterion 28 pins each filter independently and byte pins on StatuslineSegments and MenuPointer guard the placements. pmacs-protocol gains src/terminal.rs: the shared row/column/visible-cell/ grapheme/metadata bounds, TerminalProcessState, TerminalSelectionSpan, and TerminalFrame::validate — the ONE structural policy the daemon runs before emission and the frontend runs after decode. src/terminal/* re-exports them so no duplicate type exists, and unicode-width becomes a workspace dependency so the screen and the validator measure glyph columns with one table. A new 8 MiB aggregate glyph bound keeps the largest legal frame (measured: 13,437,863 bytes) under the unchanged 16 MiB transport cap rather than widening every connection's allocation ceiling. The semantic producer suppresses the whole document family for a terminal buffer while keeping the status band, theme, font, statusline, menu, and minibuffer, and compares the complete ordered payload rather than screen_generation — scroll, selection, and process state all change without advancing it. Two things the framing did not spell out, both found by the real-daemon acceptance: The Viewport gate keys on the authenticated source's ACTIVE buffer, not the buffer the message names. Viewport also aligns the window to what it declares, so a stale document viewport in flight when a command opened a terminal dragged the frontend straight back off it: the window oscillated, every terminal declaration was refused, and no frame ever arrived, with nothing logged anywhere. The producer clears terminal mode on every exit path. The daemon uses that flag to suppress CursorByte and the presence sweep, so an early return that left it set kept both suppressed after the frontend returned to a document. pmacs-gpu/src/terminal.rs is a pure cell-space paint planner, unit-testable without a GPU. The renderer builds one shaped buffer per text run, so a wide or cluster glyph's advance can never choose the next column's origin. Criterion 37 needed a seam rather than a fixture: pmacs-gpu depends only on pmacs-protocol, so attach::connect's reader sink was generalized and a --headless-probe mode added. The acceptance drives a real daemon, a real /bin/sh child, the real attach client, and real composited pixels in one path — which is how both defects above were found. Gates: fmt; strict workspace clippy; 1,757 default + 1,933 CRDT library tests; vterm Stage 1 9/10, Stage 2 4/4, Stage 3 4/5 acceptance (default/CRDT); statusline 7/8; M4 120; required GPU 127; workspace sweep 2,919 across 83 suites; diff check clean. |
|
|
|
4b65b9e1e5 |
feat(statusline): add composable modeline segments at protocol v18
Add the strict pmacs.statusline provider registry, deterministic borrow-released per-window evaluation, context-scoped failure latches, and a pure built-in LSP provider. Preserve the legacy TUI modeline while composing faced custom runs, and append authoritative complete StatuslineSegments replacements for semantic frontends. Expand dynamic ThemeFacts, reset producer/frontend baselines symmetrically, and gate all provider work off protocol v18. Teach the GPU to atomically validate, resolve, shape, clip, and cache custom modeline runs without displacing the protected status suffix. Document the public Lua lifecycle, wire ownership, snapshot semantics, and the fully gated Arc 4 stage-3 delivery state. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
|
|
|
9ce6f1abf3 |
feat(json-yaml): JSON + YAML grammars and language servers
Add tree-sitter-json (0.24) and tree-sitter-yaml (0.7) to BUILTIN_LANGUAGES (both ABI-current via tree-sitter-language, verified compiling under tree-sitter 0.26), each self-contained highlights, no injections of their own. Extensions json=.json, yaml=.yaml/.yml; root kinds json `document`, yaml `stream`. The payoff from the #122 injection engine is free: the markdown block injection query already sets injection.language "yaml" for `---` frontmatter (minus_metadata) and "toml" for `+++` (plus_metadata), so registering yaml lights up YAML frontmatter highlighting with no extra wiring, and ```json / ```yaml / ```yml fences resolve through the engine (yml->yaml alias already present). Two acceptance tests pin this synergy. LSP (builtin/runtime/lsp.lua): pmacs.lsp.config.json uses the maintained extracted-bundle binary `vscode-json-language-server --stdio` (NOT the stale standalone vscode-json-languageserver); MIT, no telemetry, remote $schema fetch left enabled (no handledSchemaProtocols). pmacs.lsp.config .yaml uses `yaml-language-server --stdio` with Red Hat telemetry disabled by default. Both ship the exact workspace/configuration sections each server pulls (json+http; yaml+http+redhat.telemetry) present-not-null so the servers get defaults rather than erroring — the CMake #117 lesson. Sections derived from server source/docs (neither binary installed on this build machine to observe live; verify where present). Filetype fallback entries added. JSON is the standing prerequisite for the Jupyter .ipynb arc; handoff §6 updated. Nine acceptance tests (grammar ABI, highlights compile, detection, grammar<->LSP-key alignment, the two frontmatter/fence synergy proofs, and the pinned LSP-config sections). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJ9FQ832QwftJXCD9LeFan |
|
|
|
e9bafdacd6 |
feat(font): GPU sanitized font-database assembly (Q#F6)
assemble() replaces FontSystem::new() + post-hoc load_font_data
with an explicit fontdb::Database built in today's order -- system
fonts first, the bundled JetBrains Mono second (its fontdb::ID
retained) -- then the parameterized same-family collision filter
(sanitize_font_database removes every NON-monospace face
advertising the default family; the bundled face survives by
construction), cosmic-text's generic-family defaults, and only
then FontSystem::new_with_locale_and_db (sys-locale with the
"en-US" fallback, the same resolution cosmic-text's own
constructor performs; new pmacs-gpu dependency) -- so the internal
monospace-ID set is computed over the final database, bundle
included. FontDefaults { default_family, bundled_id } lands on
State as the total-fallback anchor for the resolution work in the
next commit; query_normal_face is the shared normal-style query
(the same fontdb::Query the base Attrs imply). Debug assertions
pin both anchors present and monospaced at assembly. All 69 GPU
tests pass unchanged -- assembly preserves today's pixels.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VoiEyuPjoBhvwACf8HAnLB
|
|
|
|
bcec61e020 |
feat(highlight): grammars for python/go/typescript/javascript/toml/zig
These five languages already had LSP configs (basedpyright, gopls, tsserver, taplo, zls) but shipped no tree-sitter grammar, so they rendered with no lexical color. Fill the gap — 8 BUILTIN_LANGUAGES entries across 6 crates: - python (`tree-sitter-python`, root `module`), go (`tree-sitter-go`), toml (`tree-sitter-toml-ng`), zig (`tree-sitter-zig`, +`.zon`) — each a single self-contained highlights query. - JavaScript/TypeScript family: `tree-sitter-javascript` parses both `.js` and `.jsx`; `tree-sitter-typescript` ships two grammars (LANGUAGE_TYPESCRIPT, LANGUAGE_TSX). The four entries — javascript, javascriptreact, typescript, typescriptreact — mirror the LSP filetype map so tsserver enables the JSX parser. Highlights inherit: the TS query is a ~5-capture delta over JavaScript and JSX is a further delta, so the entries compose base-first (js → jsx → ts), the same pattern as `cuda` over C/C++ (typescript resolves ~22 capture classes, typescriptreact ~24). Each grammar's name equals its existing `pmacs.lsp.config.<name>` key, so grammar detection (which wins over the filetype map) resolves the id the server keys off — the file now gets BOTH highlighting and the right server. No lsp.lua change needed. All crates ride `tree-sitter-language 0.1` with tree-sitter dev-only — no second core in the graph. Bite-verified acceptance: - gap_grammars_load_and_parse — each grammar's ABI accepted by the 0.26 core; a snippet parses without error at its root (covers both TS grammars, incl. JSX). - typescript_highlights_compose_the_javascript_base — the compiled typescript/typescriptreact queries resolve >= 15 captures, not just the ~5-capture TS delta (the JS base is really composed in). - builtin_languages_include_gap_grammars / gap_grammar_extensions_resolve — entry presence + extension detection across all 8 ids. - m4_gap_grammars_align_with_lsp_configs — through the loaded runtime, each path's grammar id matches an existing LSP config. Bite-verified against pre-feature src/syntax.rs. Ripple: two #116 shebang tests used python as their "has-LSP-but-no- grammar" example, which this PR invalidates. Updated both — the .py + `#!/bin/sh` precedence test now asserts a python grammar tree (not "no tree"), and the grammarless-language-is-silent gate test switches to `ruby` (genuinely grammarless) via a test-local shebang mapping. Gates: fmt; clippy -D warnings; test --lib; --features crdt; m4_acceptance --skip basedpyright; GPU; full workspace sweep; git diff --check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJ9FQ832QwftJXCD9LeFan |
|
|
|
7646dda583 |
feat(highlight): filename detection + Dockerfile/Make/CMake grammars
Files identified by their whole basename — Dockerfile, Makefile, CMakeLists.txt, rc dotfiles — had no detection path (extension-, then shebang-keyed). Add a filename layer and the three grammars behind it. - **Grammars** (BUILTIN_LANGUAGES): dockerfile via `tree-sitter-containerfile` (the ABI-current grammar; the old `tree-sitter-dockerfile` pins `tree-sitter ^0.20` and would fork the graph — containerfile rides `tree-sitter-language 0.1`, tree-sitter dev-only, like the others), make via `tree-sitter-make`, cmake via `tree-sitter-cmake`. All ship self-contained highlights (single fragment). Extensions: `.dockerfile`/`.containerfile`, `.mk`/`.make`, `.cmake`. - **Filename layer**: `pmacs.parse.language_from_filename(name)` backed by an extensible `pmacs.parse.filenames` map, wired into the precedence chain in both syntax.lua (grammar) and lsp.lua (LSP): grammar-ext → filetype map → filename → shebang. A recognized extension still wins; the basename map only fires when the extension misses. Seeds the three filenames plus shell rc dotfiles (`.bashrc`/`.zshrc`/`PKGBUILD`/… → bash) — highlighting them against the grammar shipped in #115. - **LSP**: `config.dockerfile` (docker-langserver --stdio) and `config.cmake` (cmake-language-server). Make has no server, so no `config.make` — grammar highlight only. Extension filetype fallbacks added for id stability. Bite-verified acceptance: - filename_grammars_load_and_parse — each grammar's ABI is accepted by the tree-sitter 0.26 core and parses a representative snippet without error (dockerfile/cmake root at source_file, make at makefile). - builtin_languages_include_dockerfile_make_cmake / language_for_path_resolves_dockerfile_make_cmake_extensions — entry presence and extension detection. - m4_filename_map_resolves_special_files — the basename map (incl. path form and dotfiles→bash), config.dockerfile/cmake commands, and no config.make. Bite-verified against pre-feature syntax.lua. - m4_filename_extensionless_dockerfile_highlights — an extensionless `Dockerfile` resolves to dockerfile for LSP and gets a dockerfile parse tree; reachable only via the filename map. Bite-verified. Gates: fmt; clippy -D warnings; test --lib; --features crdt; m4_acceptance --skip basedpyright; GPU; full workspace sweep; git diff --check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJ9FQ832QwftJXCD9LeFan |
|
|
|
6fd7db81fe |
feat(highlight): shell/bash tree-sitter grammar for the shell family
Shell scripts already had LSP (bash-language-server + shellcheck/shfmt, wired in builtin/runtime/lsp.lua), but no tree-sitter grammar, so their text rendered without lexical color. Fill in the missing half. - Bundle tree-sitter-bash (0.25) as a BUILTIN_LANGUAGES entry. Unlike cuda, bash's highlights.scm is self-contained (no `; inherits:` delta), so a single fragment suffices. The crate exports LANGUAGE/HIGHLIGHT_QUERY over tree-sitter-language 0.1 — shared ABI crate, no second tree-sitter in the graph. - Extension set is wider than the `.sh`/`.bash` the LSP filetype map covered: `.zsh`/`.ksh`/`.ash` are close-enough dialects and `.bats` is bash. The grammar's language name is `bash`, matching the `pmacs.lsp.config.bash` key, so opening any of these also auto-attaches bash-language-server (shellcheck declines zsh, so `.zsh` diagnostics may be sparse; highlighting is unaffected). lsp.lua's filetype map is extended to the same set as the belt-and-suspenders fallback. - Extensionless shebang scripts (`#!/bin/sh`) and rc dotfiles (`.bashrc`) are intentionally NOT covered: detection is extension-keyed and shebang/filename sniffing is a separate, deferred feature. Bite-verified acceptance: - bash_grammar_loads_and_parses_script — the 0.25 grammar's ABI is accepted by the 0.26 core (set_language succeeds at runtime) and a representative script (shebang, set, parameter expansion, function, if) parses without error, rooting at `program`. - builtin_languages_include_bash / language_for_path_resolves_bash_ extensions — entry presence and detection across the wider set. - bash_highlights_compile_with_captures — the self-contained query compiles against the grammar with real capture classes. - m4_12_default_bundle_wires_bash — through the loaded runtime, config.bash targets bash-language-server and both grammar detection and the filetype fallback resolve the new extensions to `bash`. Gates green: fmt; clippy -D warnings; test --lib; --features crdt; m4_acceptance --skip basedpyright; GPU; full workspace sweep; git diff --check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJ9FQ832QwftJXCD9LeFan |
|
|
|
11075914f3 |
feat(lsp): CUDA support — clangd + bundled tree-sitter grammar
Opening a .cu/.cuh file previously resolved to no language, so no server attached and there was no highlighting. Wire CUDA end to end, mirroring the existing C/C++ path: - Bundle tree-sitter-cuda (0.21) as a new BUILTIN_LANGUAGES entry claiming .cu/.cuh, with its own HIGHLIGHTS_QUERY. A dedicated grammar rather than reusing cpp: the C++ grammar errors on the <<<grid, block>>> kernel-launch syntax. The crate rides tree-sitter-language 0.1 (its tree-sitter dep is dev-only), so it shares the ABI crate with the other grammars — no second tree-sitter in the graph. - pmacs.lsp.config.cuda targets clangd (the same binary that serves C/C++; language_id "cuda" so clangd enters its CUDA parse mode), and .cu/.cuh filetype fallbacks map to "cuda" to keep the LSP id stable if the grammar is ever dropped. LspStyleView layers clangd's CUDA semantic tokens on top, exactly as for C/C++. Bite-verified acceptance: - cuda_grammar_loads_and_parses_kernel_launch — proves the 0.21 grammar's ABI is accepted by the 0.26 core (set_language succeeds at runtime, which the compile step cannot confirm) and that the entry wired the CUDA grammar, not a cpp fallback: the <<<...>>> launch parses without error, whereas the cpp grammar reports an error on the same source (verified out of band). - builtin_languages_include_cuda / language_for_path_resolves_cuda_ extensions — entry presence and .cu/.cuh detection. - m4_12_default_bundle_wires_cuda — config.cuda targets clangd and the filetype + grammar detection resolve to "cuda" through the loaded runtime. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJ9FQ832QwftJXCD9LeFan |
|
|
|
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 |
|
|
|
5c34b3c37a |
regex-search: smart-case multi-line find_all_regex (Q#RX1/RX2)
The regex sibling of find_all, on regex::bytes::Regex over the whole buffer. Returns Option<Vec<ByteRange>>: Some for a valid pattern (possibly empty), None when it fails to compile — so the caller can tell an invalid pattern (show [invalid]) from a valid zero-match search. Smart-case mirrors the literal path: case-insensitive via a (?i) prefix unless the pattern carries an uppercase letter. Multi-line is free — the regex runs over the whole byte slice, so an explicit \n (or (?s).) spans lines while `.` keeps its default. Zero-width matches (a*, ^, $) are filtered. The regex crate (already transitive in the lockfile) is promoted to a direct dependency; its linear-time engine makes a pathological pattern slow at worst, never catastrophic. Tests: pattern match, smart-case both ways, \n-spanning + dotall + default-no-cross, invalid→None vs valid-zero→Some(empty), zero-width filter. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
|
|
|
62cee9118c |
session 3 commit 2/2: attach mode + loro rope reconstruction
pmacs-gpu now has two run modes:
- no args: hello-world (session-2 behavior preserved)
- --attach <socket>: connect to a pmacs daemon, negotiate
semantic_render + crdt_replica, import BufferSnapshot into a local
loro replica, render the rope text. Live CrdtOp updates apply as
they arrive.
Architecture:
- pmacs-gpu/src/attach.rs (new): UnixStream connect + Hello /
AttachRequest handshake on the main thread; spawns a reader thread
that pumps decoded InstanceMessage frames through the winit
EventLoopProxy as AppEvent::Attach(AttachEvent::Message). Clean EOF
or transport errors surface as AttachEvent::Disconnected. Reader
thread holds the read half of the stream; AttachClient retains the
write half (unused yet — session 4 wires FrontendEvents back).
- pmacs-gpu/src/main.rs: ApplicationHandler<AppEvent> with a
user_event handler that dispatches Message variants. BufferSnapshot
builds a fresh LoroDoc, imports the snapshot bytes, extracts text
via doc.get_text('body').to_string(), and re-shapes the glyphon
buffer. CrdtOp passes the op bytes through doc.import (loro
accepts both shapes), re-extracts text, re-shapes. Other
InstanceMessage variants are intentionally ignored at session 3.
- Font size dropped from 48pt to 16pt now that we may render full
files (the hello-world 48pt was fine for one line, awful for code).
- Initial text is '(connecting...)' in attach mode, 'hello, pmacs' in
hello-world; attach failure falls back to '(attach failed; see
stderr)' so the window still opens.
One small finding logged in attach.rs's connect() doc: AttachRequest's
initial_size field is a CellSize (rows × cols), nominally
TUI-shaped. Sent as a placeholder (24×80) — a structural answer
('what does initial size mean for a pixel frontend?') belongs in its
own protocol thread, not session 3. Classified under rule (iii) as
deferred.
Container id for the loro text container ('body') hardcoded to match
pmacs::crdt::CrdtState — second finding worth pre-recording: the
container name is a wire-adjacent convention that isn't carried on
the wire itself. Both ends have to agree out-of-band. Not blocking
for session 3 but a structural smell for the producer arc. Logged
as deferred (rule iii structural; the answer is probably 'thread the
container id through BufferSnapshot' but it's not session-3 scope).
Gates: cargo fmt, cargo clippy --all-targets -D warnings (whole
workspace) clean; lib 1303 + pmacs-protocol 11 = 1314 unchanged;
m4_acceptance 83; m11_5_semantic_acceptance --features crdt 2.
Manual validation pending — agent environment is headless. User
walks through: start a pmacs daemon, run pmacs-gpu --attach <socket>,
confirm the window renders the daemon's file contents.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
|
|
a706864e04 |
session 2: pmacs-gpu workspace + wgpu/winit/glyphon hello-world
Adds the pmacs-gpu binary crate to the workspace. wgpu 29.0 + winit 0.30 + glyphon 0.11 (cosmic-text 0.18 via re-export) + pollster + env_logger; pmacs-protocol in the dep graph but not consumed yet (session 3 wires the attach loop). The binary opens an 800x200 window titled 'pmacs-gpu hello-world', sets up wgpu against its surface, configures glyphon with the bundled JetBrains Mono Regular, and renders 'hello, pmacs' once per redraw. Close button or Escape exits. Resize re-configures the surface and glyphon viewport. Surface acquisition matches wgpu 29's CurrentSurfaceTexture enum (success/suboptimal render through; lost/ outdated re-configure; timeout/occluded skip the frame). Bundled assets: pmacs-gpu/fonts/JetBrainsMono-Regular.ttf (268 KB) and pmacs-gpu/fonts/OFL.txt. Font shipped as required by the SIL Open Font License 1.1. One finding surfaced during the move and absorbed under rule (iii) of the framing pass (small / no structural change): the design doc recorded JetBrains Mono as Apache 2.0; the actual license has been OFL since the family's open-source release. Doc corrected in docs/pmacs-gpu-design.md. Gates: cargo fmt + cargo clippy --all-targets -D warnings clean for the whole workspace; cargo test --lib still 1314 (pmacs main crate untouched); m4_acceptance 83; m11_5_semantic_acceptance --features crdt 2. Visual confirmation pending — agent environment is headless, so 'window opens, text renders' is user-side validation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|
|
|
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> |
|
|
|
0935efd454 |
M_B3: tree-sitter-cpp/c + dual-authority TUI styling
Drops the policy-A exclusivity that left grammar-backed languages without LSP semantic refinement. Adds tree-sitter-c (.c/.h) and tree-sitter-cpp (.cpp/.cc/.cxx/.hpp/...) to the bundle so the grid TUI gets lexical highlighting (keywords / strings / operators) on first open. The Lua attach in builtin/runtime/lsp.lua now pushes LspStyleView whenever an LSP server is up, regardless of grammar presence; with both views attached the cell-painter pipeline runs SyntaxHighlightView first (lexical) then LspStyleView (semantic) and their styles compose through crate::overlay::merge_styles. The result is the VSCode / Zed "TextMate + LSP semantic tokens" model on a terminal grid: keywords colored by tree-sitter, identifiers refined by clangd's semantic tokens. `.h` is ambiguous C / C++; the `c` BUILTIN_LANGUAGES entry claims it to match the LSP filetype map's default. Users who want `.h` parsed as C++ can override via Lua (extension → language map). Note the tree-sitter-c / -cpp crates expose `HIGHLIGHT_QUERY` (singular), matching tree-sitter-md's `HIGHLIGHT_QUERY_BLOCK` convention; tree-sitter-rust / -lua use `HIGHLIGHTS_QUERY` (plural). Same bundled highlights.scm either way. Regression guard: builtin_languages_include_c_and_cpp asserts the language entries exist and claim their canonical extensions. The LspStyleView module doc rewritten to reflect dual-authority composition; the existing headline test's comment updated (the test fixture still attaches only LspStyleView directly, so its asserted cells reflect the LSP authority alone — Lua-level attach_buffer is what exercises composition end-to-end). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|
|
|
6113c53381 |
v1.0.0 release prep: CHANGELOG, version bump, README, MSRV
Roadmap steps #3–#6 (post-CI-green doc/version work; no source
change — the CI-validated tree at
|
|
|
|
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>
|
|
|
|
587a2a15de |
M9 ship gate
Land the Model Context Protocol (MCP) integration as a transport binding, not a built-in feature. Six Lua functions plus userdata methods expose the substance of three MCP feature areas (resources, tools, prompts), a notification dispatcher, and a non-trivial AI-assistance example package that meets the architectural ship gate (spec/pmacs-spec.tex:1572): zero direct calls into the Rust core, zero special-cased MCP handling outside the public API, source under 2000 lines of Lua. The M9.5 -> M9.6 -> M9.7 -> M9.8 layered composition validates the claim "AI is a transport binding, not a feature" -- pmacs-mcp-ai composes with pmacs-mcp-prompts.render and inherits notification handling transitively through M9.7's package, demonstrating that the AI domain is a layer above MCP, not a thread woven through the core. Subtask shape: M9.1 stdio transport + initialize handshake + restart policy M9.2 resources with in-flight + settled cache and per-uri invalidation M9.3 tools with isError-vs-JSON-RPC-error semantics + cancellation M9.4 prompts with required-argument validation M9.5 notification dispatcher (on_notification, off_notification) M9.6 tools-as-commands fixture package + 12 audit findings disposed M9.7 prompts-as-result-buffers fixture package + tree-sitter-md grammar M9.8 AI-assistance fixture package (363+ LoC; 17/17 acceptance tests) M9.9 formal package audit -- PASS on all three criteria M9.10 release: TRANSITION-M9.md + MCP-for-package-authors guide Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|
|
|
3a35d0b0f8 | M8 ship gate | |
|
|
c8d0d67615 | Fix PTY final-output drain race | |
|
|
4da4b09d5d | Initial commit: v0.1.0 |