diff --git a/COHERENCE.md b/COHERENCE.md index 95761b2..9fe85f0 100644 --- a/COHERENCE.md +++ b/COHERENCE.md @@ -1272,9 +1272,16 @@ its asks are already practiced.** exclusive by construction; every other per-frontend-kind pair in the dispatcher remains two adjacent `if`s that a reader must notice are alternatives. -- The GPU frontend exceeds the TUI (minimap, squiggles, typography) - without the TUI losing the model — the "no privileged frontend" rule - is holding under real divergence pressure. +- The GPU frontend exceeds the TUI (minimap, squiggles, typography, + and since #158 rendered inline math) without the TUI losing the + model — the "no privileged frontend" rule is holding under real + divergence pressure. Inline math is the sharpest case so far: the + GPU shapes `$…$` spans through a bundled MATH-table font while the + TUI shows the LaTeX source unchanged, and the TUI's distinct-face + fallback is a **named deferral rather than an oversight**. What + keeps it inside the rule is that the slice reserves no protocol + version and adds no wire surface — the divergence is presentational + only, and the semantic model both frontends read is identical. Remaining, honestly small relative to the section's ambition: capability negotiation is per-bit rather than a first-class declared capability diff --git a/docs/active-work.md b/docs/active-work.md index 5284b94..aeac490 100644 --- a/docs/active-work.md +++ b/docs/active-work.md @@ -14,11 +14,11 @@ 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` @ `8c86d34` (the dired framing #164 atop find-file - #162, COHERENCE.md #163, Lean 4 Stage 1 #160, the minimap blank-slab fix - #159, bottom-panel Stage 1 #155, the inline-math re-scout #154, the vterm - PTY-flake fix #153, and the GPU initial-target doc refresh #152; protocol - v20). + `githubsucks/main` @ `d152120` (the bottom-panel landed-doc refresh #156 + atop the inline-math slice #158, dired Stage 1 #165, the GPU terminal + input fix #166, Lean 4 Stage 2 #161, the dired framing #164, + COHERENCE.md #163, find-file #162, Lean 4 Stage 1 #160, and the minimap + blank-slab fix #159; protocol v20). - 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 @@ -52,137 +52,9 @@ git worktree list git status --short --branch ``` -The `git log` command must expose `8c86d34` or a newer intentional main. +The `git log` command must expose `d152120` or a newer intentional main. If it does not, stop and repair the remote/fetch configuration. -## Inline-math slice lane — PR #158 OPEN, main integrated - -- Portable branch: `githubsucks/inline-math-slice`; worktree - `../pmacs-math-slice`. **PR #158**, base `main`. -- **Canonical `main` merged into the lane three times on 2026-07-25**, - every time merged rather than rebased, per the #135/#137 precedent: the - PR is awaiting review rounds and a rebase would break every review - anchor. - - First at `8c86d34` (28 commits behind). The conflict was - **pre-existing**, not introduced by the dired (#164) or Lean 4 - ledger commits; it already conflicted against `main` @ `e745068`. - - Then at `46a1b8f`, after Lean 4 Stage 2 (#161) landed while this - branch's CI was still running. Same single conflict, same shape, - same resolution. - - Then at `b889873`, after the GPU terminal input fix (#166) landed. - **No conflict at all this time** — and that is exactly why it still - needed a real integration, see below. -- **The first two conflicts were this ledger and nothing else** — both - sides' lanes kept verbatim each time. That is the standing cost of a - long-lived PR here: every merge to `main` edits this file, so a branch - awaiting review re-conflicts on it and only on it. It is a docs - collision, never a code one, and it says nothing about integration - risk — do not read a `CONFLICTING` badge on this PR as a code signal - without checking which file `git merge-tree` names. -- **The inverse trap matters more, and #166 is the case in point: a - CLEAN `git merge-tree` is not a reason to skip integrating.** #166 - landed 41 lines in `pmacs-gpu/src/main.rs`, the same heavily-rewritten - file as the first integration, and git merged it without a murmur - because the two edits sit in different regions (#166 is entirely in the - headless probe — `PMACS_GPU_PROBE_OBSERVE_MS`, `PROBE_INPUT_CHAR`, - `input_echo_observed` — while this lane rewrites the render path). - Merging the PR on the strength of that clean auto-merge would have - shipped a combination no gate had ever run. **Decide whether to - integrate from the shared-FILE set, not from whether git complained.** -- **First integration's surface** (derived from `git diff - ..main`, not from another PR's file list): - `pmacs-gpu/src/main.rs` gained 72 lines on main from `e547a90` — the - minimap all-blank-slab divide-by-zero fix — and this lane rewrites - large parts of the same file. Git auto-merged it **textually**; a - clean auto-merge is not evidence the tree compiles (the folding-arc - lesson), so the full gate suite below is what actually discharges it. -- **Second integration's surface is code-disjoint.** #161 touched - `COHERENCE.md`, `builtin/runtime/lsp.lua`, `src/lua_bindings/mod.rs`, - and a new `tests/lsp_multi_root_acceptance.rs`; intersecting that - against this lane's own changed-file set leaves exactly one entry, - `docs/active-work.md`. No source file is touched by both sides, so - this one carries none of the first integration's semantic risk. -- **CI ran on this branch for the first time on 2026-07-25 and passed - all twelve** (Format, both Lints, GPU Render headless, all four Test - matrix jobs, M1/M4/M5/M6 gates) at `8b457de` — the first-integration - tip. Before that there were zero workflow runs since the PR opened on - 2026-07-24, while every other open PR had a full run; not a fork and - not a trigger-config issue (the workflow fires on all `pull_request` - events), cause never identified. So the green run **validates the - first integration, including the `pmacs-gpu/src/main.rs` auto-merge, - on macOS and Linux both** — the platforms local gating could not - cover. The second and third integrations get their own CI run on the - push that carries them. -- Framing: `docs/inline-math-slice-framing.md` rev 3, approved after two - review rounds; parent arc framing merged as #154. -- State: parser, font bundle (GUST licence), MATH-table layout with the - measured height budget, currency-guarded detection, and the - `ChunkSource::MathBox` spacer substrate are implemented and - round-3-reviewed (review fixes at `cbf7782`: the exclusive-`end` - mapping bug its own test had pinned, script-marker whitespace, fallible - layout via `UncoverableGlyph`, real fraction gap-min constants — - flagship scale 0.867, fallback depth 5). -- Caret-driven suppression (the Q#MS5 gate over the effective caret and - Q#MS11 selection endpoints, chunk substitution before tab expansion, - the line-reuse predicate's third input, and the CursorByte / - optimistic-edit / Decorations refresh triggers), the draw pass - (per-glyph mini-buffers positioned by each shaped line's real - baseline, fraction-rule quads over the washes, the F8b family pin), - the Q#MS11 whole-rectangle wash widening, and the pixel acceptance - battery (criteria 5–11, 14–16; 17 discharged by the differential - `cargo tree -e features` check — byte-identical with and without the - dependency line) are implemented on the branch tip. -- Clippy is CLEAN on the whole workspace at `-D warnings` — the draw - pass consumed every formerly-dead item. -- Verification **pre-integration** (at `14c1c01`, against the old base): - 199 `pmacs-gpu` tests under `PMACS_REQUIRE_GPU=1`; 1,815 default + - 1,992 CRDT library tests; M4 121; full workspace sweep green (isolated - `XDG_CONFIG_HOME`). Superseded by the post-integration run below — - those numbers describe a tree 28 commits behind. -- Verification after the **first** integration: `cargo fmt --check` - clean; strict workspace Clippy clean; 1,826 default + 2,003 CRDT - library tests; **202 `pmacs-gpu` tests under `PMACS_REQUIRE_GPU=1`**; - M4 121; isolated-`XDG_CONFIG_HOME` `--no-fail-fast` workspace sweep - 3,208 across 91 suites, zero failures; `git diff --check` clean. -- Verification after the **third** integration (this is the set that - describes what the PR now proposes): fmt clean; `git diff --check` - clean; strict workspace Clippy clean; **1,829 default + 2,006 CRDT** - library tests; **202 `pmacs-gpu`** under `PMACS_REQUIRE_GPU=1`; M4 121; - **isolated-`XDG_CONFIG_HOME` `--no-fail-fast` sweep 3,224 across 92 - suites, zero failures**. -- **Test-count reconciliation is the integration proof, not the pass.** - Run it against what the other side actually added, per merge: - - First: GPU 199 → **202**, and `e547a90` added exactly **3** - `pmacs-gpu` tests — the whole delta on main since the merge base. - Structurally spot-checked too: main's fix survives as - `(count > 0).then(|| MinimapLineShape {` (the deferred closure, - **not** the eager `then_some`) with its regression test. - - Third: #166 adds **3** library tests, **2** to - `vterm_stage3_acceptance`, and **0** to `pmacs-gpu`. Predicted lib - 1,826 → 1,829, CRDT 2,003 → 2,006, GPU unchanged at 202 — and that - is exactly what ran. Suite count 91 → **92** is #161's new - `tests/lsp_multi_root_acceptance.rs` binary. All three sides' - markers confirmed live in `pmacs-gpu/src/main.rs`: #166's probe - symbols, this lane's `math_plan_for_line` / `math_gates_match` / - `cached_math_subs_for_slice` / `widen_over_math_chunks` / 21 - `MathBox` references, and the first integration's minimap fix. -- **Ops trap, cost hours: `m4_5_basedpyright_initializes_and_negotiates_ - encoding` does not time out — it hangs forever.** A `--workspace` - sweep parks on `m4_acceptance` with a live - `basedpyright/langserver.index.js` child and never advances (observed - stuck at 38 of 92 suites for 2h26m). The per-suite M4 gate already - carries `-- --skip basedpyright`; **the workspace sweep needs the same - flag** — `cargo test --workspace --no-fail-fast -- --skip - basedpyright` (libtest filters apply to every binary; verify it bit by - checking the run reports exactly 1 filtered out). Do not read a - long-running sweep as "slow": check whether the suite count is - advancing. -- Remaining: the user's review pass. - Named v0 approximations: the peer-caret half of acceptance 14 is - pinned at the mapping level (unit tests), not pixels; a soft-wrapped - spacer draws its box whole at the first run's origin; the fit budget - reads the bundled code face even under a custom `set_font` family - (the draw anchors to the real shaped baseline either way). ## Lean 4 lane (Arc 8) — Stage 1 MERGED; Stage 2 IN REVIEW (PR #161) - Stage 1 **merged as #160** (`main` @ `0827dd1`, 2026-07-25, one review @@ -645,6 +517,46 @@ git worktree add --track \ ## Closed since the last snapshot +- **Inline-math slice — MERGED as #158** (`main` @ `5aa9044`, + 2026-07-25). Detect → parse → layout → draw for `$…$`, entirely inside + `pmacs-gpu`, no protocol change. Verified by the user's manual pass on + a real paper after the landing. What is worth carrying forward: + - **The v0 subset is 34 Greek symbols, sub/superscript, and `\frac`.** + An unsupported command fails the **whole span** back to source, so on + a real document most inline spans still show LaTeX. Widening the + symbol map is the highest-value next increment — ahead of display + math, which is also deferred. + - **A stale frontend binary is invisible from the source tree.** The + slice lives only in `pmacs-gpu`, so after it merged the feature was + absent until `cargo build --release -p pmacs-gpu` and a client + restart; the daemon needs neither. Diagnose with `strings` on the + binary (`Latin Modern Math`, `MathBox`) rather than by re-reading the + checkout, which was already current. + - **Main was integrated three times in one day** (`8c86d34`, + `46a1b8f`, `b889873`), merged not rebased to preserve review anchors. + Two conflicts, both this ledger and nothing else. **The dangerous + case was the one that did NOT conflict**: #166 auto-merged into + `pmacs-gpu/src/main.rs`, the file this lane rewrites, because the two + edits sat in different regions of it. Decide whether to integrate + from the shared-**file** set, never from whether git complained. + - **Integration was proved by test-count reconciliation**, not by a + green run: predict what the other side adds, then check the deltas. + GPU 199→202 matched `e547a90`'s 3; later lib 1,826→1,829 and CRDT + 2,003→2,006 matched #166's 3, with GPU unchanged because #166 adds + none. Suites 91→92 was #161's new binary. + - **Why the branch had no CI for a day**: a conflicting PR builds no + merge ref, so no `pull_request` run is created. The ledger previously + recorded this cause as unidentified; it is not. Check `mergeable` and + confirm a run exists for the current head SHA. + - **`m4_5_basedpyright` has no timeout and hangs forever**, parking a + `--workspace` sweep (observed 2h26m at 38 of 92 suites). It is + **intermittent**, so an earlier clean sweep proves nothing. Sweep with + `cargo test --workspace --no-fail-fast -- --skip basedpyright` and + judge progress by whether the suite count advances. + - Named v0 approximations: the peer-caret half of acceptance 14 is + pinned at the mapping level, not pixels; a soft-wrapped spacer draws + its box whole at the first run's origin; the fit budget reads the + bundled code face even under a custom `set_font` family. - **Bottom panel Stage 1 — MERGED as #155** (`main` @ `e745068`, 2026-07-24, after two review rounds). Window placement, window parameters, TUI side windows, the divider, and the adopter `display` diff --git a/docs/agent-handoff.md b/docs/agent-handoff.md index 21352e9..d873063 100644 --- a/docs/agent-handoff.md +++ b/docs/agent-handoff.md @@ -1,7 +1,8 @@ # Agent handoff — cross-machine continuity -**Last updated: 2026-07-25, after find-file (#162) landed — the dired -arc's Stage 0 — following COHERENCE.md (#163), Lean 4 Stage 1 (#160), the +**Last updated: 2026-07-25, after the inline-math slice (#158) landed — +the first mathematical typesetting in pmacs — following find-file (#162), +the dired arc's Stage 0, and COHERENCE.md (#163), Lean 4 Stage 1 (#160), the minimap blank-slab fix (#159), bottom-panel Stage 1 (#155), the inline-math re-scout (#154), the vterm PTY-flake fix (#153), and the GPU initial-target doc refresh (#152); and before that GPU @@ -26,11 +27,13 @@ commands, read `docs/active-work.md` immediately after this file. ## 1. Where the project stands (2026-07-25) -- `main` @ `2af1ab3` (find-file #162 atop COHERENCE.md #163, Lean 4 Stage 1 - #160, minimap blank-slab #159, bottom-panel Stage 1 #155, inline-math - re-scout #154, vterm PTY-flake #153, and doc refresh #152). Protocol - unchanged at **v20**. The bullets below describe the arcs in their own - terms; this line is the head-of-`main` anchor. +- `main` @ `d152120` (the bottom-panel landed-doc refresh #156 atop the + inline-math slice #158, dired Stage 1 #165, the GPU terminal input fix + #166, Lean 4 Stage 2 #161, the dired framing #164, COHERENCE.md #163, + find-file #162, Lean 4 Stage 1 #160, minimap blank-slab #159, + bottom-panel Stage 1 #155). Protocol unchanged at **v20**. The bullets + below describe the arcs in their own terms; this line is the + head-of-`main` anchor. - **`COHERENCE.md` is now required reading and a required framing input — #163.** It carries the product-coherence thesis, an audited scorecard, per-concern gaps, and §20's priority order, and it is the @@ -39,6 +42,32 @@ commands, read `docs/active-work.md` immediately after this file. interaction islands added, config-registry adoption, background-work attribution. Its §2 grades the golden journey **broken at step 3** (`pmacs .` exits 1). +- **Inline math LANDED — #158** (`docs/inline-math-slice-framing.md` rev 3; + merge `5aa9044`). pmacs renders `$…$` as typeset mathematics in the GPU + frontend. **No protocol change (still v20); the whole slice lives in + `pmacs-gpu`**, because `pmacs-gpu` depends only on `pmacs-protocol` and + never on `pmacs` — a core-crate parser would have been unreachable from + where rendering happens. + - `math_parse.rs` → `math_layout.rs` → a `ChunkSource::MathBox` spacer + chunk → per-glyph mini-buffers drawn at the shaped line's real + baseline, with fraction rules as quads. Font is bundled Latin Modern + Math (~717 KiB) under the **GUST Font License** — not OFL. + - **The v0 subset is narrow and deliberately so**: Greek (34 entries), + sub/superscript, and `\frac`. Everything else — including relations + like `\geq`, fences, big operators, and all display math (`$$…$$`, + `\[…\]`) — is a named deferral, and an unsupported command degrades + the **whole span** back to source rather than rendering partially. + In a real paper most inline spans still show source; that is the + designed behaviour, not a defect. + - **Math is suppressed while the caret is inside its span**, so editing + always sees source. That gate reads the effective caret plus + selection endpoints and is fed by three separate refresh triggers; + it is the most delicate part of the slice. + - Selection and search washes cover the whole box rectangle, not + sub-ranges (sub-range washes are deferred). + - TUI shows the LaTeX source unchanged. That divergence is recorded + against `COHERENCE.md` §16, which audits the "no privileged + frontend" rule. - **find-file LANDED — #162** (`docs/dired-framing.md` §10, Q#DR11; merge `2af1ab3`; one review round). `C-x C-f` is the dired arc's **Stage 0**: pmacs previously had no discoverable way to open a file by path — no diff --git a/docs/inline-math-framing.md b/docs/inline-math-framing.md index 0b6bfb4..a2ce9c8 100644 --- a/docs/inline-math-framing.md +++ b/docs/inline-math-framing.md @@ -237,7 +237,7 @@ candidates, ranked: | Font | Table quality | License | Notes | |------|--------------|---------|-------| -| **Latin Modern Math** | Full | OFL (GUST) | Reference, ships with TeX Live, most widely tested | +| **Latin Modern Math** | Full | **GUST Font License** (not OFL) | Reference, ships with TeX Live, most widely tested; ~717 KiB. Bundled by #158 as `pmacs-gpu/fonts/latinmodern-math.otf` | | **STIX Two Math** | Full | OFL | Broader Unicode coverage | | **Cambria Math** | Full | Proprietary | Ships with Office; unavailable on Linux | | **Libertinus Math** | Full | OFL | Derivative of Latin Modern, wider | diff --git a/src/buffer.rs b/src/buffer.rs index 304918b..7f048d5 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -2790,6 +2790,88 @@ mod tests { } } + /// Deterministic reduction of a `rope_matches_crdt_projection_ + /// after_arbitrary_edits` failure found by raising the case + /// count (`PROPTEST_CASES=2000`) on `main` @ `e745068`. + /// + /// **What happens.** Replacing a byte range with *identical* + /// bytes is a textual no-op but a real CRDT operation (a delete + /// plus an insert). Undoing it therefore advances the CRDT + /// version while leaving the materialized text unchanged, so + /// `undo_crdt_mode` derives an EMPTY replacement edit — and + /// still attaches the `crdt_op` that `crdt.undo()` produced. + /// That trips the proptest's `crdt_op` shape invariant, "a + /// no-op edit must have `crdt_op = None`". + /// + /// **What was verified about the consequences**, so the next + /// reader does not have to redo it: + /// + /// * content stays correct — rope and CRDT projection agree + /// before and after (asserted below); + /// * replicas stay converged — both `crdt_op` consumers + /// (`EditorCore::queue_daemon_origin_crdt_op` and the remote-op + /// path) read `edit.crdt_op` unconditionally and do **not** + /// short-circuit on an empty range, so the op is broadcast; + /// * the cursor does not jump — `EditorCore::undo` only clamps + /// to buffer length and never seeks `edit.range.start`. + /// + /// **The open question** is therefore whether the *invariant* is + /// simply mis-scoped rather than the behavior being wrong. It + /// was written for the FORWARD `apply_edit` short-circuit, which + /// returns before ever producing an op; CRDT-mode undo/redo + /// never reach that path. One artifact is genuinely arbitrary + /// either way: `derive_replacement_edit` reports the empty range + /// at the buffer END rather than at the edit site. + /// + /// Ignored, not deleted: it documents a real, reproducible + /// asymmetry that nothing else on `main` records, and un-ignoring + /// it is the first step of whichever resolution wins. + #[test] + #[ignore = "known pre-existing main behavior; see the doc comment \ + for the verified consequences and the open question"] + fn crdt_undo_of_an_identity_replace_reports_a_no_op_edit_carrying_an_op() { + let mut buffer = + Buffer::new_with_crdt(BufferId::next(), "*identity-undo*", 1).expect("crdt"); + buffer + .apply_edit(EditOp::Insert { + pos: 0, + bytes: b"hello", + }) + .expect("seed insert"); + + // Replace one byte with the SAME byte. + let forward = buffer + .apply_edit(EditOp::Replace { + range: Range::new(1, 2), + bytes: b"e", + }) + .expect("identity replace"); + assert_eq!(forward.range, Range::new(1, 2)); + assert_eq!(forward.inserted_len, 1); + + let undone = buffer.undo().expect("undo"); + assert!( + undone.range.is_empty() && undone.inserted_len == 0, + "the undo produced no textual change: {:?}/{}", + undone.range, + undone.inserted_len + ); + assert!( + undone.crdt_op.is_some(), + "…yet it carries a version-advancing CRDT op — the invariant \ + the proptest trips on" + ); + + // Content is unharmed in both projections. + let mut bytes = vec![0u8; buffer.len() as usize]; + buffer.snapshot_rope().slice(0, buffer.len(), &mut bytes); + assert_eq!(String::from_utf8(bytes).expect("utf8"), "hello"); + assert_eq!( + buffer.crdt_state().expect("crdt").materialize_string(), + "hello" + ); + } + proptest! { // Smaller proptest case count than the default (64) to keep // CI overhead modest; the per-op invariant check is the