# Web grammars (HTML + CSS) + HTML injections — framing
**Revision 4 — implemented on branch `web-grammars` (PR #146). Ground truth:
canonical `main` @ `4daa1b8` (after LaTeX #144 and inline-math-docs #145),
2026-07-23.** Rev 2 settled the capture set, injection scope, and LSP claim
(round 1); rev 3 corrected the `#match?` predicate analysis (round 2); rev 4
names the intended `@attribute` retro-paint on the already-bundled rust/lua/yaml
grammars — verified on a Rust buffer and pinned by a test (round 3). See §0.1.
## 0.1 Revision history
### Round 1 (rev 1 → rev 2)
- **F1 (high).** Q#WEB4 deferred the capture mapping. The upstream queries
settle it: HTML and CSS need exactly **`tag`** and **`attribute`**; `@tag.error`
prefix-walks to `tag`; there is **no** `@tag.delimiter` (rev-1 speculation).
Q#WEB4 now names both mappings and their styles, and acceptance 6 paints an
HTML **attribute** explicitly (a tag/property-only test could pass with
`attribute` unverified since `property` is already recognized).
- **F2 (medium).** The HTML injections query has **exactly two** patterns —
`script_element`→javascript, `style_element`→css. Q#WEB5 no longer claims
event-handler / `style=` coverage; inline-attribute injection is now a named
deferral.
- **F3 (medium).** No HTML/CSS language server is bundled: `lsp.lua`'s default
config list ends at YAML (`:247`) and `:521` returns `nil` without a
configured command. §2 and §5 corrected — the grammars give stable language
IDs so a *user-supplied* config attaches automatically, but nothing ships or
starts by default.
### Round 2 (rev 2 → rev 3)
- **R2-1 (medium).** The rev-2 claim that pmacs *ignores* `#match?` was wrong.
`src/syntax.rs:1701` passes the source to `QueryCursor::captures`, so
tree-sitter evaluates the standard `#match?`/`#eq?`/`#any-of?` text predicates
natively (pmacs special-cases only `#is? local`, `:1703`). Corrected in §2 and
Q#WEB4: an ordinary CSS property has a single `@property` capture; only a
`--custom-property` also receives `@variable` (a benign double-capture).
Acceptance 6 no longer claims an ordinary property pins custom-property
precedence — it just verifies `color` → `@property` paints.
- **R2-2 (low).** The folding footprint in §0 was stale. At
`githubsucks/folding` @ `036a994` the branch touches `src/fold.rs`,
`editor.rs`, `editor_core.rs`, `lib.rs`, `lua_bindings/{fold.rs,mod.rs}`,
`semantic_render.rs`, `fold.lua`, and tests — not
`overlay.rs`/`daemon.rs`/`syntax.rs`. §0 now lists the real set; the
zero-overlap conclusion is unchanged (stronger, if anything).
### Round 3 (rev 3 → rev 4)
- **R3-1 (medium).** Q#WEB4 verified which captures pmacs *lacks* but not the
reverse: which already-bundled grammars *use* `@attribute`. Three do — rust
(`attribute_item`), lua (``), yaml (directives) — so the new
`("attribute", fg(3))` entry retro-paints their previously-unpainted spans
yellow. Named in Q#WEB4 as intended (verified on a Rust buffer: uniform
yellow, an improvement over unpainted), and pinned by
`rust_attribute_repaints_via_shared_attribute_capture`. `tag` is unaffected
(HTML/CSS only).
Add tree-sitter **HTML** and **CSS** grammars so `.html`/`.htm` and `.css`
buffers get lexical highlighting, and — the north-star payoff — light up
HTML's embedded-language **injections**: `` parses as JavaScript and
`` parses as CSS, via HTML's crate-exported injections query
riding the #122 injection engine. CSS is registered here so the `` and `` produces a
`ParseTreeBundle` whose child layers resolve to `css` and `javascript`, and
a grid-paint asserts an injected CSS property and JS keyword paint **inside**
the embedded regions (mirroring `grid_paints_injected_child_keyword`).
6. **Paint (non-vacuous highlighting)**: parse `` and assert **both**
the `` tag (`@tag`) **and** the `href` attribute name (`@attribute`) paint
the two new non-default styles — the attribute assertion is load-bearing, since
a tag-only test could pass with `@attribute` unverified. Also assert a CSS
selector (`@tag`) paints the new `tag` style and an ordinary CSS property
(`color` → `@property`) paints non-default (an ordinary property has a single
unconditional capture, so no precedence subtlety — Q#WEB4).
7. **Full gate suite** per `CLAUDE.md`.
## 7. Prior art in pmacs
- **LaTeX lane #144** (`docs/latex-grammar-math-substrate-framing.md`) — the
grammar-add mechanics, the table-guard/smoke/paint test conventions, and the
compile-gate-plus-paint-test discipline. (Its overlay convention is not
needed here.)
- **Multi-language injections #122**
(`docs/multi-language-injections-framing.md`) — the `ParseTreeBundle` +
`Layer` engine HTML's injections ride.
- **markdown → rust fenced code** (`src/syntax.rs:848`, tested by
`src/highlight.rs`'s `grid_paints_injected_child_keyword`) — the working
injection precedent this lane's acceptance 5 mirrors.