Merge pull request #154 from levineuwirth/inline-math-rescout

docs(math): re-scout inline-math framing against landed state (rev 2)
This commit is contained in:
Levi Neuwirth 2026-07-24 19:08:27 +00:00 committed by GitHub
commit 352bf0b2ab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 151 additions and 21 deletions

View File

@ -1,5 +1,35 @@
# Inline math rendering — framing
**Revision 2 — framing only; no implementation. Ground truth re-scouted
against canonical `main` @ `ddaa80d`, protocol v20, 2026-07-24; every
anchor re-checked at `f07b75b` (the vterm PTY-flake fix #153, test-only,
which moved no anchor here).**
Revision 1 was written against protocol v18, before LaTeX Stage 1 (#144),
web grammars (#146), folding Stages 12 (#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. The MATH module is feature-gated, so the declaration must name its features deliberately — see Tier 3 §A. |
| 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 +105,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 +210,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 +244,41 @@ 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, so it adds no new supply-chain surface.
**Declare the feature set deliberately, or the "no rebuild" part stops
being true.** The MATH module is gated: `ttf-parser` re-exports `math`
under `#[cfg(feature = "opentype-layout")]`. It is compiled today only
because `fontdb` asks for it — and `fontdb` asks with
`default-features = false`, features `["opentype-layout",
"apple-layout", "variable-fonts", "glyph-names", "no-std-float"]`,
which is **not** `ttf-parser`'s own default set (that one adds `std`
and drops `no-std-float`). A plain `ttf-parser = "0.25"` therefore
unions `std` in and forces a one-time rebuild of `ttf-parser`,
`fontdb`, `cosmic-text` and `glyphon`. The zero-rebuild spelling is
`default-features = false, features = ["opentype-layout"]` — a subset
of what `fontdb` already enables.
- 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 +357,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 12 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 +435,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` as `default-features = false, features = ["opentype-layout"]` (already transitive via `fontdb`); bundle Latin Modern Math | Low; no new crate enters the graph, ~200 KB font. Spelling the features matters — bare `ttf-parser = "0.25"` unions `std` in and rebuilds the font chain |
| `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 +458,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 +663,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.