From 5dcc7d70a0d7301752f28c61b6057050993aa1d9 Mon Sep 17 00:00:00 2001 From: Levi Neuwirth Date: Fri, 24 Jul 2026 14:10:46 -0400 Subject: [PATCH] docs(math): re-scout inline-math framing against landed state Revision 1 was written against protocol v18, before LaTeX Stage 1 (#144), web grammars (#146), folding Stages 1-2 (#142/#149) and the GPU initial target (#148) landed. Revision 2 changes no design decision; it corrects the ground truth those merges invalidated and records the staging decision the sibling substrate framing already took. A new section 0 lists every correction so a reader who knows revision 1 can read it alone. Two corrections change implementation choices rather than line numbers: - The MATH-table dependency story was wrong in both directions. Revision 1 said a crate must be added and that "neither is in the tree today"; ttf-parser 0.25.1 already reaches pmacs-gpu non-optionally through fontdb -> cosmic-text -> glyphon, the same fontdb the frontend already calls. And the choice is not "one of ttf-parser or read-fonts": only ttf-parser exposes the MATH table, supplying exactly the constants Tier 3 names. read-fonts 0.37.0 is present but has none, so selecting it would be a dead end. - Tier 2's staging was already decided elsewhere and this note did not say so. The sibling framing's Q#LX5 puts the parser beside its Tier 3 consumer, never ahead of it, because MathNode's shape is only validated by a layout consumer. That makes Tier 2 not independently shippable, which is worth stating explicitly: it is pure and conflict-free, so landing it alone while other lanes hold the render path is exactly the tempting move Q#LX5 refused. Tier 1 is materially de-risked: the LaTeX grammar already exposes math_environment and math_delimiter, and the in-repo query overlay this tier proposed already exists and captures both, so the mechanism is proven rather than speculative. The guessed node name (math_expression) is corrected to the grammar's own. Markdown still needs the overlay treatment. Tier 4 gains a contention note. Revision 1 described the GPU render path as though math were its only claimant; folding Stage 3 and the bottom-panel arc's Stage 2 now converge on it, so whichever lands second re-scouts against the first. Framing only; no implementation, no runtime code. Co-Authored-By: Claude Opus 5 (1M context) --- docs/inline-math-framing.md | 157 +++++++++++++++++++++++++++++++----- 1 file changed, 136 insertions(+), 21 deletions(-) diff --git a/docs/inline-math-framing.md b/docs/inline-math-framing.md index c0a533e..879c205 100644 --- a/docs/inline-math-framing.md +++ b/docs/inline-math-framing.md @@ -1,5 +1,33 @@ # Inline math rendering — framing +**Revision 2 — framing only; no implementation. Ground truth re-scouted +against canonical `main` @ `ddaa80d`, protocol v20, 2026-07-24.** + +Revision 1 was written against protocol v18, before LaTeX Stage 1 (#144), +web grammars (#146), folding Stages 1–2 (#142/#149) and the GPU initial +target (#148) landed. **Revision 2 changes no design decision.** It +corrects the ground truth those merges invalidated, and records the +staging decision the sibling substrate framing already took. A reader who +knows revision 1 can read §0 alone. + +## 0. What the landed-state re-scout corrected + +| # | Revision 1 said | Current state | +| --- | --- | --- | +| C1 | A MATH-table crate must be added; "neither is in the tree today" | **Both are already in `Cargo.lock`.** `ttf-parser` 0.25.1 reaches `pmacs-gpu` non-optionally via `fontdb` → `cosmic-text` → `glyphon`, and `pmacs-gpu` already calls `fontdb` directly in `build_font_system` (`pmacs-gpu/src/main.rs:217`). `read-fonts` 0.37.0, `skrifa`, `swash` and `font-types` are present too. | +| C2 | Use "one of `ttf-parser` or `read-fonts`" | **Not interchangeable.** `ttf-parser` ships the MATH table (`tables/math.rs`: `Constants::axis_height`, `display_operator_min_height`, `script_percent_scale_down`, the `MathValue`/`MathValues` accessors Tier 3 names). `read-fonts` 0.37.0 exposes no MATH table. Only `ttf-parser` satisfies Tier 3. | +| C3 | "Protocol is v18 … `SUPPORTED=[6..=18]`" (Q#IM1) | Protocol is **v20**, `SUPPORTED=[6..=20]`. v19 = the vterm terminal family; v20 = the GPU initial-target bootstrap. The Q#IM1 *decision* (query font size locally, no protocol change) is unaffected. | +| C4 | `rebuild_code_slice()` at `pmacs-gpu/src/main.rs:4849` | Now `:6136`. The file grew through vterm Stage 3, tab-width parity and the initial-target work. | +| C5 | Tier 1 must patch an upstream grammar or ship an overlay; "defers enumerating which grammars need this" | **Already available for `.tex`.** LaTeX Stage 1 (#144) bundles `codebook-tree-sitter-latex`, whose grammar exposes `math_delimiter` and `math_environment`, and the in-repo overlay `builtin/queries/latex/highlights.scm` already captures both. The overlay mechanism this tier proposed is proven, not speculative. Markdown still has no math capture. | +| C6 | (silent on staging) | A sibling framing, `docs/latex-grammar-math-substrate-framing.md` (rev 3), names this note its parent and **decides Tier 2's staging in Q#LX5**: the parser lands *beside* its Tier 3 consumer, never ahead of it. Recorded in §Tier 2 below so it is not re-litigated. | +| C7 | (silent on contention) | Tier 4's render path is contended. Folding Stage 2 (#149) landed fold projection in the paint path, and its **Stage 3 (GPU) is unframed**. The **bottom-panel arc** is implementing Stage 1 now; its Stage 2 claims both `pmacs-gpu/src/main.rs`'s render path and the next protocol version. Tier 4 must re-scout against whichever lands first. | + +Unchanged and re-verified: `InlineAdornment` (`pmacs-protocol/src/message.rs:1391`), +`SquiggleRenderer` (`pmacs-gpu/src/main.rs:2825`), `TextView` +(`src/text_view.rs:45`), and the three referenced framings +(`multi-language-injections`, `pmacs-gpu-wavy-squiggles`, +`semantic-frontend-protocol`) all exist as described. + **Status: framing only; no implementation.** This note frames a feature that currently does not exist: rendering LaTeX math expressions as typed math (not raw source) in any buffer. @@ -75,19 +103,31 @@ struct MathSpan { start: BytePos, end: BytePos, kind: MathKind } ``` **Where it runs:** -- For buffers with a tree-sitter grammar: a `(math_expression)` node in - the grammar signals scanned injection ranges (reuses the existing - `ParseTreeBundle` + `Layer` machinery from +- For buffers with a tree-sitter grammar: a math node in the grammar + signals scanned injection ranges (reuses the existing `ParseTreeBundle` + + `Layer` machinery from `docs/multi-language-injections-framing.md`). Each grammar that can - contain LaTeX (markdown, org, raw TeX, etc.) needs a - `(math_expression) @math` capture rule added — either by patching the - upstream grammar or shipping an overlay `queries/math/highlights.scm`. - The framing defers enumerating which grammars need this until - adoption; markdown and LaTeX grammars are the obvious v0 targets. + contain LaTeX needs a math capture rule — either by patching the + upstream grammar or shipping an in-repo query overlay. + + **This is no longer speculative for `.tex` (C5).** LaTeX Stage 1 (#144) + bundles `codebook-tree-sitter-latex`, whose grammar already exposes + `math_delimiter` and `math_environment`, and the in-repo overlay + `builtin/queries/latex/highlights.scm` already captures both (`:57`, + `:290`). The overlay pattern this tier proposed therefore exists and is + proven; adding a `@math` capture beside the existing highlight captures + is an edit to a file the repo already owns, not new machinery. The node + names are the grammar's own — `math_environment` / `math_delimiter`, + **not** the `(math_expression)` this framing originally guessed. + + Markdown remains unaddressed: its grammar comes from the crate query + constants (the #146 web-grammars pattern, no in-repo overlay), so a + markdown math capture needs the overlay treatment first. Enumerating + further grammars stays deferred to adoption. - For buffers with no grammar: a fast byte-level scanner in Rust (two-pass: find `$` / `$$` / `\(` / `\[` boundaries, match pairs, handle escapes). Runs in the GPU frontend's `rebuild_code_slice()` - (`pmacs-gpu/src/main.rs:4849`) as a post-shape hook, not the edit + (`pmacs-gpu/src/main.rs:6136`) as a post-shape hook, not the edit path, so keystroke latency is unaffected. (The TUI's `src/text_view.rs` is a separate line-index view that does not participate.) @@ -168,6 +208,22 @@ codepoints (the `\alpha` → `U+03B1` map is ~200 entries for Greek + Hebrew + arrows + operators). The parser does not handle macro definitions, preamble material, or LaTeX3 — only math-mode markup. +**Staging: this tier lands beside Tier 3, never ahead of it (C6).** +`docs/latex-grammar-math-substrate-framing.md` (rev 3) carved out the +frontend-agnostic LaTeX substrate as its own lane and, in **Q#LX5**, +deliberately excluded this parser from it. Reviewer and author concurred +and recorded the decision "so it is not re-litigated". The rationale is +that `MathNode`'s shape is only validated by a layout consumer, so +building the parser first would fix an AST no one has exercised — the +same no-build-ahead discipline folding applied to `BlockAdornments` and +gpu-invocation applied to `FILE`. + +The practical consequence: **Tier 2 is not an independently shippable +slice.** It is pure, dependency-free and conflict-free, which makes it +tempting to land alone while other lanes hold the render path; that +temptation is exactly what Q#LX5 refused. A PR that ships the parser +must also ship enough of Tier 3 to exercise the AST. + ### Tier 3: Layout This is the hard part and the place where pmacs would do something no @@ -186,10 +242,28 @@ candidates, ranked: Bundled default: Latin Modern Math. The existing font-loading path (`build_font_system()` → `fontdb::Database::load_font_source()` in -pmacs-gpu via cosmic-text) already handles .ttf/.otf; adding the MATH -table means pulling in one of `ttf-parser` or `read-fonts` from -fontations as a new dependency (neither is in the tree today — both are -pure Rust, low-risk additions). +`pmacs-gpu/src/main.rs:217`, via cosmic-text) already handles .ttf/.otf. + +**Reading the MATH table needs `ttf-parser`, and it is already in the +build graph (C1, C2).** Revision 1 said a MATH crate must be added and +that "neither is in the tree today"; both parts were wrong: + +- `ttf-parser` 0.25.1 already reaches `pmacs-gpu` through + `fontdb` → `cosmic-text` → `glyphon` — the same `fontdb` the frontend + already calls directly. It is not an optional or dev-only path. Adding + it to `pmacs-gpu/Cargo.toml` declares a dependency the build already + compiles; it adds no new supply-chain surface and no new build cost. +- The choice is **not** "one of `ttf-parser` or `read-fonts`". Only + `ttf-parser` exposes the MATH table (`tables/math.rs`), and it supplies + exactly the constants this tier names below — `Constants::axis_height`, + `display_operator_min_height`, `script_percent_scale_down`, and the + `MathValue` / `MathValues` per-glyph accessors. `read-fonts` 0.37.0, + though present in the tree, has no MATH table; selecting it would be a + dead end. + +`skrifa`, `swash` and `font-types` are also present (via cosmic-text) but +are not MATH-table providers either. The bundled font itself is still a +real addition: ~200 KB for Latin Modern Math in `pmacs-gpu`. **B. Box model.** Each `MathNode` lays out into a `MathBox`: @@ -268,7 +342,24 @@ for the rest. The GPU frontend (`pmacs-gpu/src/main.rs`) already renders text through cosmic-text + glyphon with per-span styling. Math expressions are a new -layer inserted into the existing z-order: +layer inserted into the existing z-order. + +**This tier is contended and must re-scout before it is scheduled (C7).** +Revision 1 described this render path as though math were its only +claimant. Two other arcs now converge on it: + +- **Folding Stage 3 (GPU)** — Stages 1–2 landed (#142/#149); Stage 3 is + the next ranked item and is **unframed**. It inherits a named + obligation on this path: the `BufferSnapshot` fold-mirror clear. +- **The bottom-panel arc** — Stage 1 (core + TUI) is implementing now; + its **Stage 2 owns both** this render path (a projected panel cell grid + painted as a band) **and the next protocol version**. + +Neither blocks the design below, and this framing reserves no protocol +version (see §Protocol surface). But Tier 4's anchors are the ones most +likely to move, so whichever of the three lands second re-scouts against +the first — the same rule the bottom-panel and folding framings already +apply to each other. ``` Backgrounds → Squiggles → Code Text → Math → Gutter → Caret → Minimap → Minibuffer → Completion → Context menu @@ -329,13 +420,18 @@ frontends — the v0 approach pays the scan cost per frontend. | Component | Change | Risk | |-----------|--------|------| -| `src/math_parse.rs` (new) | ~500-line recursive-descent parser | Low; pure fn, no deps | -| `src/math_layout.rs` (new) | MATH-table loading + box layout | Medium; depends on font crate | -| `Cargo.toml` | Add `read-fonts` or `ttf-parser` for MATH table | Low; both pure Rust | -| `pmacs-gpu/Cargo.toml` | Add Latin Modern Math font bundling | Low; ~200 KB compressed | -| `pmacs-gpu/src/main.rs` | Detect math spans in visible text, insert MathBox draw calls | Medium; touches main render path | +| `src/math_parse.rs` (new) | ~500-line recursive-descent parser | Low; pure fn, no deps. **Ships with Tier 3, not alone (Q#LX5).** | +| `src/math_layout.rs` (new) | MATH-table loading + box layout | Medium; depends on `ttf-parser` | +| `pmacs-gpu/Cargo.toml` | Declare `ttf-parser` (already transitive via `fontdb`); bundle Latin Modern Math | Low; no new crate enters the graph, ~200 KB font | +| `pmacs-gpu/src/main.rs` | Detect math spans in visible text, insert MathBox draw calls | **High, and contended** — see C7: folding Stage 3 (GPU) is unframed and the bottom-panel arc's Stage 2 claims this same render path | +| `builtin/queries/latex/highlights.scm` | Add a `@math` capture beside the existing `math_environment` / `math_delimiter` captures | Low; the overlay already exists | | `semantic_render.rs` | No changes (v0) | None by design | +The row that changed most is `pmacs-gpu/src/main.rs`. Revision 1 rated it +"Medium; touches main render path" when that path was uncontested. It now +has two other arcs converging on it, so Tier 4 cannot be scheduled +without knowing which of them lands first. + ## Open questions ### Q#IM1 — Font size and DPI scaling @@ -347,8 +443,10 @@ current font size? Via the existing `FontFacts` protocol message (`protocol v17`), or queried from the `State` fields directly? **Proposed:** query `self.font_size` directly in the GPU frontend, same -as code text does. No protocol change. (Protocol is v18 at the time of -this framing — `SUPPORTED=[6..=18]`.) +as code text does. No protocol change. (Protocol is **v20** at revision 2 +— `SUPPORTED=[6..=20]`; v19 added the vterm terminal family and v20 the +GPU initial-target bootstrap. The decision is version-independent: it +holds precisely because it adds no wire surface. C3.) ### Q#IM2 — Color inheritance @@ -550,3 +648,20 @@ exercises the LSP path. The face resolution chain (`face-attribute → color → fallback`) would extend naturally to a `math-face` or `math-display-face` for themed math colors. + +## Sibling lane (added in revision 2) + +`docs/latex-grammar-math-substrate-framing.md` (rev 3) names this note its +parent and carves out the frontend-agnostic LaTeX substrate that can land +without touching a contended file. Revision 1 did not reference it, which +left the reader of this note unaware that part of its Tier 1 had already +shipped (#144) and that its Tier 2 staging had already been decided. + +Read that lane before scheduling any tier here: + +- its **Q#LX5** governs Tier 2 (parser lands beside Tier 3, not ahead); +- its landed portion supplies the `.tex` grammar and the query-overlay + precedent Tier 1 depends on; +- it explicitly defers Tier 3 layout, Tier 4 GPU render, and + instance-side `(math_environment) @math` injection detection back to + this arc.