diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index dc270902..a4314fd9 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -18,6 +18,7 @@
{"id":"bd-12fpz","title":"D4/D5: HTML writer emits odd/even and header row classes","description":"## What\n\n`write_table_row` in `crates/pampa/src/writers/html.rs:1490` emits bare `
` for every row. Pandoc's reference HTML writer (which Q1 inherits) adds:\n\n- `
` on header (thead) rows.\n- `
` / `
` alternating on body rows.\n\n## Fix\n\nPass row index + is_header from the table-writing loop into `write_table_row` (it already gets `is_header`); add the class accordingly.\n\n## Tests (TDD)\n\nSnapshot test on a 3-row table (1 header + 2 body) showing:\n- header row: `
`\n- first body row: `
`\n- second body row: `
`\n\n## Plan\n\nclaude-notes/plans/2026-05-20-table-default-rendering-parity.md (D4, D5 sections — bundled because they share the same writer code path)","status":"closed","priority":3,"issue_type":"bug","created_at":"2026-05-20T20:55:24.955808Z","created_by":"cscheid","updated_at":"2026-05-20T21:31:14.339774Z","closed_at":"2026-05-20T21:31:14.339617Z","close_reason":"Implemented in this commit: row classes (header/odd/even) emitted.","source_repo":".","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-12fpz","depends_on_id":"bd-hir7j","type":"parent-child","created_at":"2026-05-20T20:55:24.955808Z","created_by":"cscheid","metadata":"{}","thread_id":""}]}
{"id":"bd-12vrr","title":"Callout default-title synthesizer + popup-menu callout-type switching (Plan 6 §B)","description":"Plan 6's audit identified crates/quarto-core/src/transforms/callout_resolve.rs:267 (default callout title 'Note'/'Tip'/'Warning'/...) as an AST synthesizer that today emits SourceInfo::default(). To bring it into the Generated shape Plan 6 establishes, it needs: (a) a new By::callout() constructor in quarto-source-map, (b) an atomicity decision (is_atomic_kind), (c) the fix at callout_resolve.rs:267, (d) a per-transform test. Deferred from Plan 6 because it was not enumerated in the plan body and requires the new By constructor + atomicity decision.","status":"open","priority":3,"issue_type":"task","created_at":"2026-05-22T16:19:01.484116Z","created_by":"gordon","updated_at":"2026-05-22T16:45:31.449690Z","source_repo":".","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-12vrr","depends_on_id":"bd-129m3","type":"related","created_at":"2026-05-22T16:19:13.342003Z","created_by":"gordon","metadata":"{}","thread_id":""}],"comments":[{"id":27,"issue_id":"bd-12vrr","author":"Gordon Woodhull","text":"Why this matters beyond the audit fix: the React preview needs a UI affordance to **switch callout type** (Note → Tip → Warning → Important → Caution) via a popup menu on the callout block. For that to round-trip cleanly through Plan 7's writer, the default-title text (\"Note\", \"Tip\", …) must NOT be treated as user-editable Original content — otherwise typing-as-edit semantics would force the writer to materialize \"Note\" as literal text into source, even when the user only wanted to swap the type via the menu.\n\nThe `Generated { by: callout(), … }` stamping + the atomicity decision determine:\n\n1. **React-side**: whether the title region is gated read-only via Plan 2A's atomic check (so the user can't accidentally type into it; the menu is the only path to mutation).\n2. **Writer-side**: how a menu-driven type change serializes. The menu rewrites the Callout CustomNode's `plain_data` (e.g. `{\"type\": \"tip\"}`) and leaves the title-Generated either re-synthesized on next pipeline run or replaced with a fresh Generated whose `by.data` reflects the new type. Plan 7's let-user-win path handles the CustomNode rewrite naturally; the title region soft-drops on direct edits (Q-3-42-style).\n\nRecommend `is_atomic_kind = true` for `By::callout()`'s title-synthesizer use so the title is non-editable text and only menu-driven type changes mutate it. The popup-menu component lives in hub-client's React framework registry (post-Plan 2B); this beads owns the Rust-side provenance support that makes the round-trip behave.\n","created_at":"2026-05-22T16:45:31Z"}]}
{"id":"bd-140x","title":"Contribute samod fix upstream with pure-rust reproduction test","description":"Cherry-pick the NotFound race condition fix (431333f) from quarto branch to main on cscheid/samod fork, and write a pure-rust integration test in samod-core/tests/ that reproduces the bug independently of quarto-hub. The test should demonstrate that a document synced from a client to a DontAnnounce server is dropped during handle_load when pending_sync_messages are ignored. PR target: alexjg/samod.","status":"open","priority":1,"issue_type":"task","created_at":"2026-03-04T00:58:02.303737Z","created_by":"cscheid","updated_at":"2026-03-04T00:58:02.303737Z","source_repo":".","compaction_level":0,"original_size":0}
+{"id":"bd-14rer","title":"ExecuteResult.filters set by Knitr engine is never consumed downstream","description":"Workspace-wide grep for `result.filters` / `ExecuteResult.*filters` returns hits in tests, the Knitr write site, extension parsing, and a comment in capture-splice — but no production consumer.\n\nConcretely:\n- crates/quarto-core/src/engine/knitr/mod.rs:215 sets `filters: result.filters` from the R subprocess output (test fixture asserts `vec![\"rmarkdown/pagebreak.lua\"]`, knitr/types.rs:281).\n- crates/quarto-core/src/filter_resolve.rs only resolves filters from YAML `filters:` document metadata, not from ExecuteResult.filters.\n\nSo Knitr's pagebreak filter is silently dropped today. This also blocks any future engine (like a mermaid Lua-filter approach) that wants to declare filters dynamically.\n\nDecision needed: wire ExecuteResult.filters into the resolver, or remove the field if the architectural direction is different (e.g. all engine-side filters should be expressed as AST transforms instead).\n\nDiscovered during mermaid engine design — see claude-notes/plans/2026-05-28-mermaidjs-engine-design.md gap G1.","status":"open","priority":2,"issue_type":"bug","created_at":"2026-05-28T13:45:45.986759Z","created_by":"cscheid","updated_at":"2026-05-28T13:45:45.986759Z","source_repo":".","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-14rer","depends_on_id":"bd-c6h96","type":"discovered-from","created_at":"2026-05-28T13:45:45.986759Z","created_by":"cscheid","metadata":"{}","thread_id":""}]}
{"id":"bd-15dw","title":"Navbar icon-only item enrichment tie-break","description":"Phase 3 enrichment fills navbar item.text from profile title only when text is None. An item that supplies icon but no text (common for social links) currently stays text-less. Confirmed intentional. Revisit if users ask for auto-text from titles even for icon-only items. See 2026-04-24-websites-phase-3.md §Follow-up beads.","status":"open","priority":4,"issue_type":"task","created_at":"2026-04-24T19:43:02.249186Z","created_by":"cscheid","updated_at":"2026-04-24T19:43:02.249186Z","source_repo":".","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-15dw","depends_on_id":"bd-fqyg","type":"discovered-from","created_at":"2026-04-24T19:43:02.249186Z","created_by":"cscheid","metadata":"{}","thread_id":""}]}
{"id":"bd-18wn","title":"Fix file-locking test failures in quarto-hub on Windows","description":"2 storage tests fail because Windows uses mandatory file locking vs Unix advisory. File::create fails with sharing violation before try_lock_exclusive runs. Fix: use OpenOptions::new().write(true).create(true) instead of File::create, or map sharing violation error to HubAlreadyRunning. Affects test_storage_manager_prevents_double_lock and test_storage_manager_standalone_prevents_double_lock.","status":"open","priority":2,"issue_type":"bug","created_at":"2026-03-20T13:36:06.424470600Z","created_by":"cderv","updated_at":"2026-03-20T13:36:06.424470600Z","source_repo":".","compaction_level":0,"original_size":0}
{"id":"bd-195t","title":"Lua attribute-mutation: proxy tables so cb.attr.attributes[k]=v persists","description":"Quarto 2's Lua bridge returns fresh copies of `cb.attr` (and of `cb.attr.attributes`) on every read. In-place mutation like `cb.attr.attributes[\"k\"] = v` — the idiomatic Pandoc-Lua pattern — silently hits an ephemeral copy and is discarded. Surfaced while exercising Phase 3.5's filter-authored spans fixture (04-filter-authored-spans); the workaround there rebuilds the whole Attr with pandoc.Attr(...) and assigns as one value. Before we document the Lua-filter path to syntax highlighting as a user-facing feature, the idiomatic pattern must persist. See claude-notes/plans/2026-04-20-syntax-highlighting-phase-3.5.md 'Follow-up task: Lua attribute-mutation proxy'. Plan: claude-notes/plans/2026-04-21-lua-attr-mutation-proxy.md","status":"open","priority":1,"issue_type":"bug","created_at":"2026-04-21T17:19:19.121444Z","created_by":"cscheid","updated_at":"2026-04-21T17:19:19.121444Z","source_repo":".","compaction_level":0,"original_size":0}
@@ -109,6 +110,7 @@
{"id":"bd-55n0g","title":"JSON-OUTPUT-SCHEMA.md describes a stale Q-1-XX numbering scheme that does not match the catalog or src/error.rs","description":"crates/quarto-yaml-validation/JSON-OUTPUT-SCHEMA.md lines 134-152 list Q-1-13 = 'String too short', Q-1-14 = 'String too long', Q-1-16 = 'Number out of range', etc. — but the actual mapping in src/error.rs::ValidationErrorKind::error_code and the catalog assigns those numbers to other kinds (Q-1-13 = ArrayLengthInvalid, Q-1-16 = ObjectPropertyCountInvalid). The file is broadly stale and would mislead anyone reading it. Needs a wholesale rewrite to match the current catalog.\n\nDiscovered while fixing bd-gdzlq (Q-1-20 double-allocation). Only the Q-1-20 → Q-1-29 line was updated in that fix; the rest of the file stays stale until this issue is taken.","status":"open","priority":3,"issue_type":"bug","created_at":"2026-05-22T20:26:45.950001Z","created_by":"cscheid","updated_at":"2026-05-22T20:26:45.950001Z","source_repo":".","compaction_level":0,"original_size":0,"labels":["documentation","error-reporting","yaml"],"dependencies":[{"issue_id":"bd-55n0g","depends_on_id":"bd-gdzlq","type":"discovered-from","created_at":"2026-05-22T20:26:45.950001Z","created_by":"cscheid","metadata":"{}","thread_id":""}]}
{"id":"bd-56b0","title":"Cross-doc dependency channel audit for q2 preview","description":"Enumerate every Quarto feature that creates a cross-document dependency the preview pipeline needs to react to: include shortcodes, listing content globs, bibliography/csl paths, theme SCSS imports, project-scoped resources, _extensions/ Lua filters, and anything else. For each: which DocumentProfile channel encodes it today (if any), which need to be added, which stay manual-refresh-only. Drives Phase B (channels already on DocumentProfile) and informs Phase D (new channels). Until this lands, the always-visible manual force-refresh button (Phase A.6) is the user escape hatch. See claude-notes/plans/2026-05-11-q2-preview-epic.md §Recommended next steps item 3 + Risk 2.","status":"open","priority":2,"issue_type":"task","created_at":"2026-05-11T15:40:54.896280Z","created_by":"cscheid","updated_at":"2026-05-11T15:40:54.896280Z","source_repo":".","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-56b0","depends_on_id":"bd-kw93","type":"related","created_at":"2026-05-11T15:40:54.896280Z","created_by":"cscheid","metadata":"{}","thread_id":""}]}
{"id":"bd-57y4","title":"Vendor and integrate quarto-listing.scss with theme-CSS pipeline","description":"Per L3 D5 (decided 2026-05-06), the listing JS pair (list.min.js + quarto-listing.js) shipped via the artifact store as Project-scoped js: artifacts in the L3 phase 7 commit. The third asset, quarto-listing.scss, was deferred because it requires Bootstrap variable wiring + media-breakpoint mixins from the existing theme-CSS pipeline (CompileThemeCssStage / quarto_sass::SassLayer). That's a larger design task than the static JS bundling.\n\nThis issue: integrate the listing SCSS as a SassLayer that gets composed with the website's theme bundle. The Q1 source file lives at external-sources/quarto-cli/src/resources/projects/website/listing/quarto-listing.scss; copy to resources/listing/quarto-listing.scss per the External Sources Policy and add it to the theme bundle when at least one listing is rendered.\n\nWhile the SCSS isn't compiled, listings render with default browser styling — the markup and JS are unchanged. The list / grid / table layouts work but lack the polished card / table styling Q1 ships.\n\nDiscovered while shipping L3 phase 7 (bd-ml8z); see D5 in claude-notes/plans/2026-05-06-listings-L3-resolve-transform.md.\n\nL5 (bd-5vsr) lands the markup that consumes this SCSS; merging bd-57y4 restores Q1 visual parity for category sidebars and per-item category chips.","status":"open","priority":2,"issue_type":"task","created_at":"2026-05-06T20:52:15.894830Z","created_by":"cscheid","updated_at":"2026-05-07T13:46:18.157025Z","source_repo":".","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-57y4","depends_on_id":"bd-ml8z","type":"discovered-from","created_at":"2026-05-06T20:52:15.894830Z","created_by":"cscheid","metadata":"{}","thread_id":""}]}
+{"id":"bd-5ijtt","title":"User-facing docs for mermaid diagrams","description":"Add a user-facing docs page under docs/ explaining `{mermaid}` code blocks: syntax, what gets rendered, browser-runtime caveats (requires network access to jsdelivr; diagram appears after page load), known limitations (HTML output only in first cut). Render with cargo run --bin q2 -- render docs/ (Q2, not Q1) per CLAUDE.md.\n\nPlan: claude-notes/plans/2026-05-28-mermaidjs-engine-design.md","status":"open","priority":3,"issue_type":"task","created_at":"2026-05-28T13:45:22.896182Z","created_by":"cscheid","updated_at":"2026-05-28T13:45:38.414661Z","source_repo":".","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-5ijtt","depends_on_id":"bd-gwfdo","type":"blocks","created_at":"2026-05-28T13:45:38.414195Z","created_by":"cscheid","metadata":"{}","thread_id":""},{"issue_id":"bd-5ijtt","depends_on_id":"bd-je48v","type":"parent-child","created_at":"2026-05-28T13:45:22.896182Z","created_by":"cscheid","metadata":"{}","thread_id":""}]}
{"id":"bd-5qnj","title":"Manage trace size for use as replay/regression-test fixtures","description":"Spun out of bd-45yw (replay engine). Once traces double as replay fixtures (and as user-attached bug-report artifacts), trace size becomes a real constraint — they will be checked into the repo as regression fixtures and posted by users in issues.\n\nCurrent quarto-trace output captures per-stage pipeline state (see crates/quarto-trace/src/lib.rs and crates/quarto-core/src/stage/trace.rs::JsonTraceObserver), which is already on the heavy side for routine diagnostic use.\n\nInvestigate:\n- Where the bulk lives in current traces (per-stage AST snapshots? supporting_files content? something else?)\n- Which content is actually load-bearing for replay vs. diagnostics, and whether the two roles can share one artifact (the design preference noted in bd-45yw's plan)\n- Compression on disk; lazy loading on read\n- Whether per-stage AST snapshots can be elided/diffed when not needed\n- Size budgets — what is reasonable for (a) checked-in CI fixtures and (b) user-attached bug reports\n\nGoal: make 'one trace serves both diagnostic and replay roles' practical. If size cannot be bounded, fall back to two separate artifacts and document why.\n\nReferences:\n- crates/quarto-trace/src/lib.rs (TraceDocument, TraceEntry)\n- crates/quarto-core/src/stage/trace.rs (JsonTraceObserver)\n- claude-notes/plans/2026-05-03-replay-engine.md (open subquestion in Phase 1)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-05-03T21:11:19.859036Z","created_by":"cscheid","updated_at":"2026-05-03T23:29:05.405253Z","closed_at":"2026-05-03T23:29:05.405068Z","close_reason":"Phases 1, 2, 3, and 5 complete. End-to-end verified on bd-5qnj fixtures: big.qmd 16.3 MB pretty -> 62 KB gzipped+deduped (~265x). Unified-artifact promise realized post-merge with bd-45yw. Real-engine supporting_files re-measurement tracked as bd-sr73.","source_repo":".","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-5qnj","depends_on_id":"bd-45yw","type":"related","created_at":"2026-05-03T21:11:19.859036Z","created_by":"cscheid","metadata":"{}","thread_id":""}]}
{"id":"bd-5vsr","title":"L5 — Categories sidebar","description":"Right-margin category list emitted from L3's resolved item set, grouped by category. Three Q1-parity styles: category-default, category-unnumbered, category-cloud. Templates embedded as doctemplate via MemoryResolver. Click-to-filter interactivity scoped out of v1. See claude-notes/plans/2026-05-05-listings-epic.md §L5.","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-05-05T19:53:32.333630Z","created_by":"cscheid","updated_at":"2026-05-07T16:00:41.250061Z","closed_at":"2026-05-07T16:00:41.249924Z","close_reason":"L5 — categories sidebar landed on feature/listings via merge 9e8afa0d (impl 2750546b + follow-on 67a985f4). Per-item chips + right-margin sidebar with three modes (default/unnumbered/cloud); Q-12-11 and Q-12-12 diagnostics; aggregate across multi-listing pages. End-to-end CLI verification recorded in plan §End-to-end CLI verification record. cargo xtask verify clean (Rust + hub-client + WASM). Test count 8570 → 8621 (+51). Discovered-from follow-ups filed: bd-99ru (localize labels), bd-754f (review encoding scheme), bd-ra5j (hub-client browser smoke deferred), bd-nwyp (PandocInlines parser audit).","source_repo":".","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-5vsr","depends_on_id":"bd-61cd","type":"parent-child","created_at":"2026-05-05T19:53:32.333630Z","created_by":"cscheid","metadata":"{}","thread_id":""},{"issue_id":"bd-5vsr","depends_on_id":"bd-ml8z","type":"blocks","created_at":"2026-05-05T19:53:32.333630Z","created_by":"cscheid","metadata":"{}","thread_id":""}]}
{"id":"bd-5yff4","title":"Sequential multi-engine execution (engine: [a, b, ...])","description":"Investigate and design support for running multiple Quarto 2 execution engines in sequence for a single document.\n\nTwo coupled changes:\n\n1. YAML config: allow 'engine:' to be an ordered array (e.g. engine: [knitr, mermaidjs]) in addition to the current singular string/map forms. Distinct engines only; order is significant (engine N may emit code cells consumed by engine N+1). Array merge across config layers uses the existing default (!concat); no engine-specific tag default for now.\n\n2. Pipeline: thread N engines through EngineExecutionStage. The AST->text->engine->text->AST->reconcile loop already type-checks end-to-end; each subsequent engine starts from the AST after the prior engine's results were reconciled in. Generalize the FileId/intermediate-file slot handling (currently 2 slots: .qmd + one .rmarkdown) to N+1 slots.\n\nTracing/replay/preview redesign: TraceDocument.engine_capture (single Option slot, name-keyed replay) becomes per-engine. Capture one AST snapshot + one EngineCapture per engine invocation within the stage, mirroring the existing transform: sub-entry pattern. Preview record/replay (record_capture, with_replay, preview cache) generalize from one capture to an ordered list.\n\nValidation/testing uses a simple file-backed test engine (reads a results file and splices per-cell outputs in order) so multi-engine sequencing can be exercised deterministically without R/Python/Jupyter, and without committing to a real second engine (e.g. mermaidjs) yet.\n\nPlan: claude-notes/plans/2026-05-27-multi-engine-execution.md\n\nStatus: design/investigation. Implementation gated on user go-ahead after plan review.","status":"in_progress","priority":2,"issue_type":"feature","created_at":"2026-05-27T14:07:37.941942Z","created_by":"cscheid","updated_at":"2026-05-27T14:33:39.118501Z","source_repo":".","compaction_level":0,"original_size":0}
@@ -194,10 +196,12 @@
{"id":"bd-c263g","title":"Author error-docs pages for lua subsystem (1 codes)","description":"Author stub-quality pages for all 1 lua subsystem error codes under docs/errors/lua/. Follow template in docs/errors/yaml/Q-1-10.qmd. See claude-notes/plans/2026-05-22-error-docs-content.md.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-05-24T21:07:58.103168Z","created_by":"cscheid","updated_at":"2026-05-24T21:15:23.074966Z","closed_at":"2026-05-24T21:15:23.074602Z","source_repo":".","compaction_level":0,"original_size":0}
{"id":"bd-c3jh","title":"Phase 9 follow-up: GC stale VFS artifacts at session end","description":"When the hub-client preview re-renders, the WASM Pass-2 produces new artifact paths (theme-css fingerprints change when content changes) but the *old* artifacts under /.quarto/project-artifacts/... linger in VFS storage. The new HTML never references them — so they don't poison the page — but they do leak.\n\nGC pass at session-end (or periodically): walk /.quarto/project-artifacts/, drop any entry whose path doesn't appear in a 'live' set (the union of artifact paths from the most-recent project render).\n\nPhase 9 plan §Risks: 'Add a follow-up to GC /.quarto/project-artifacts/... entries with no live references at session end. Not a Phase-9 blocker.'","status":"open","priority":4,"issue_type":"task","created_at":"2026-04-29T00:32:31.194561Z","created_by":"cscheid","updated_at":"2026-04-29T00:32:31.194561Z","source_repo":".","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-c3jh","depends_on_id":"bd-0tr6","type":"parent-child","created_at":"2026-04-29T00:32:31.194561Z","created_by":"cscheid","metadata":"{}","thread_id":""},{"issue_id":"bd-c3jh","depends_on_id":"bd-ayj6","type":"discovered-from","created_at":"2026-04-29T00:32:31.194561Z","created_by":"cscheid","metadata":"{}","thread_id":""}]}
{"id":"bd-c5u2g","title":"Cache engine-discovery so we don't re-spawn per document","description":"Replace JupyterEngine::find_executable's sh -c subprocess spawn with which::which (parity with KnitrEngine), and memoize both find_jupyter and find_rscript with OnceLock