--- name: html-conformance description: > Incrementally make Panache's CST shape for HTML-block / raw-HTML conform to pandoc's AST shape under `Flavor::Pandoc`, so downstream consumers (linter, salsa anchor index, LSP, formatter) see the same structural decisions pandoc would have made. The `pandoc -f markdown -t native` projector at `crates/panache-parser/src/pandoc_ast.rs` is a test-only diagnostic - divergence from pandoc-native points at a wrong CST, not a fix-it-here problem. Lift HTML structure into the CST by tokenizing existing source bytes at finer granularity (e.g. `HTML_ATTRS` inside `HTML_BLOCK_TAG`), retag wrappers (e.g. `HTML_BLOCK` → `HTML_BLOCK_DIV`), and emit inner block content as real CST children — so the projector becomes a trivial structural walk rather than a second-stage parser. --- Use this skill when asked to advance Panache's HTML conformance, unblock a regression that involves raw HTML attributes (issue #263 and its descendants), or pick "the next best phase" of the HTML lift. ## What this skill is NOT - **Not a chase for the conformance pass-rate.** The pass-rate is a metric, not a goal. A passing case can still hide a wrong CST if the projector compensates (re-parses bytes, walks text instead of children, makes context-dependent decisions at projection time). When that happens, the projector silently absorbs structural bugs the CST should have surfaced. - **Not a place to add projector logic that papers over CST gaps.** The projector at `crates/panache-parser/src/pandoc_ast.rs` is a test-only diagnostic — its job is to reveal CST shape problems by diffing against pandoc-native. Putting logic there to make a test pass while the CST stays wrong destroys the diagnostic value. The consumers of structural HTML decisions (linter, salsa, LSP, formatter) read the CST, not the projector output. - **Not "make it look like pandoc's output text."** The objective is for our CST to encode the same structural decisions pandoc encodes in its AST — Plain vs Para, Div vs RawBlock, RawBlock vs RawInline, matched-pair vs single emit, etc. — so reading the CST gives you the same answers reading pandoc's AST would. If a session's diff is mostly in `pandoc_ast.rs` (other than removing existing compensation), that's a smell. The fix probably belongs in the parser. ## Scope boundaries - Target is HTML-block + raw-HTML parsing under `Flavor::Pandoc`. Block-level: `crates/panache-parser/src/parser/blocks/html_blocks.rs` + `block_dispatcher.rs`. Inline-level: `crates/panache-parser/src/parser/inlines/inline_html.rs`. Projection: `crates/panache-parser/src/pandoc_ast.rs`. Salsa indexer: `src/salsa.rs`. - `Flavor::Pandoc` only. CommonMark dialect must stay byte-identical in CST and pandoc-native projection. `Dialect::CommonMark` keeps the opaque `HTML_BLOCK` shape; lifts are gated on `Dialect::Pandoc`. - Pandoc-native (`pandoc -f markdown -t native`) is the **behavioral reference**. Existing parser fixtures and projector output are not the reference — when they disagree with pandoc-native, fix toward pandoc-native. - This is a **long-horizon effort** (5 phases — see "Phased plan" below). Each session moves at most one phase forward; no sweeping rewrites in a single go. - Reuses the existing **pandoc-conformance harness** verbatim. New cases live in `crates/panache-parser/tests/fixtures/pandoc-conformance/corpus/-
-/` with section prefix `html-block` (block-level) or `html-inline` (inline-level). The pandoc allowlist (`crates/panache-parser/tests/pandoc/allowlist.txt`) gets new section header comments `# html-block` / `# html-inline`. - Out-of-scope and deferred to `crates/panache-parser/tests/pandoc/blocked.txt`: `markdown="1"` / `markdown="0"` (Ext_markdown_attribute, default off in pandoc-flavored markdown); `` / `` legacy anchor lift (pandoc does NOT lift these in default `markdown` — treat as opaque RawInline); malformed/unbalanced tags (fall back to opaque `HTML_BLOCK` / `INLINE_HTML`). Follow the Pandoc conformance, parser, and formatter invariants in the repository's root `AGENTS.md`. ## Phased plan The HTML lift is bounded by 5 phases. Pick one (or part of one) per session. Latest phase status lives in `RECAP.md`. **Phase 1** — Block-level `
` lift (Pandoc dialect). Parser emits `HTML_BLOCK_DIV` for matched `
...
`; projector consumes it and emits `Block::Div(attrs, blocks)`; salsa indexer extracts `id` from the open tag and registers it in `crossref_declarations`. Unblocks issue #263. **Phase 2** — Inline `` lift (Pandoc dialect). Mirrors Phase 1 on the inline side. Coordinate with `pandoc-ir-migrate` Phase 1 — `` is already a `ConstructKind::PandocOpaque` event in `inline_ir.rs`, so the lift must not double-handle the byte range. **Phase 3** — Sectioning (`
`, `
`, `