Wiki pages today render Mermaid diagrams via fenced ``mermaid blocks. Make
dashboards equally embeddable: a wiki author writes
and the wiki post-processor inlines the dashboard via an iframe pointing at
/dashboard/<id>?embed=1. This finally fuses Q-LIVE's framework into the
17K-page corpus without duplicating render logic. CRITICAL: must NOT regress
the 2026-04-21 Mermaid fence-stripping incident
(memory/project_scidex_mermaid_fence_incident.md) — fence handling here
must be additive and test-covered.Acceptance Criteria
☐ New module scidex/atlas/wiki_dashboard_fence.py (≤350 LoC) with
rewrite_content_md(md: str) -> str that converts
fences into a sanitized iframe HTML block, leaves all other fences
(mermaid, python, json, ...) untouched byte-for-byte.
- [ ] iframe attributes: `src=/dashboard/<id>?embed=1&<params>`,
`loading=lazy`, `sandbox="allow-scripts allow-same-origin"`,
`style="width:100%;height:<height>px;border:0"`. `<id>` is whitelisted
against `artifacts WHERE artifact_type='dashboard'` at render time;
unknown ids render as a visible error banner.
- [ ] `embed=1` mode on `/dashboard/<id>` strips the page chrome
(`scidex/senate/dashboard_engine.create_dashboard_page_html` at
line ~788) and renders just the inner_html.
- [ ] Hooked into `wiki_quality_pipeline.py` and `bulk_mermaid.py` after
Mermaid rewriting so the same protect-fences logic is reused.
- [ ] Pytest with 12 fixtures:
- mermaid fence preserved verbatim,
- mixed mermaid + dashboard fences both rewritten correctly,
- unknown dashboard id renders the banner not the iframe,
- YAML body without `id` is rejected,
- iframe params are URL-encoded,
- height clamped to [200, 1200],
- embed=1 strips the page header.
- [ ] CI guard `tests/atlas/test_no_mermaid_regression.py` re-runs the
mermaid-incident regression suite to make sure this PR doesn't strip
mermaid fences from any page in
data/scidex-artifacts/wiki_fixtures/.
☐ Doc page docs/atlas/wiki_dashboard_fences.md (≤2 pages) explains
the macro and lists the dashboards available for embedding.Approach
Read bulk_mermaid.py and wiki_quality_pipeline.py to learn the
exact post-process hook order; insert the dashboard-fence rewriter
strictly after Mermaid handling.
Use a fenced-block parser that tokenizes by delimiter ( `` ) rather
than regex-replace on the raw string; this is the safer pattern post?embed=1 flag is a tiny branch in create_dashboard_page_html — passembed: bool from the route and skip the page chrome template.e352460b-2d76 — view_spec_json DSL41620b88-115d — seed dashboards (sources of embeddable ids)bulk_mermaid.py (must not regress)Files created/modified:
scidex/atlas/wiki_dashboard_fence.py (233 LoC) — rewrite_content_md using delimiter-based tokenizer; DB whitelist; iframe builder; error banner; height clamping; URL-encoded params.scidex/senate/dashboard_engine.py — added dashboard_embed.html template; embed: bool param to create_dashboard_page_html and render_dashboard_page.api.py — added embed: bool = Query(False) to dashboard_standalone route; passes through to render_dashboard_page.tests/atlas/test_wiki_dashboard_fence.py — 17 tests covering all 12 spec fixtures.tests/atlas/test_no_mermaid_regression.py — 13 regression tests; all 30 tests pass.docs/atlas/wiki_dashboard_fences.md — macro reference, available dashboards, embed=1 mode._FENCE_OPEN_RE regex on line boundaries rather than scanning raw string, which keeps mermaid fences byte-for-byte identical and prevents the Apr-21 stripping pattern._db=None optional parameter on rewrite_content_md allows test injection without a live DB; falls back to get_db() in production.bulk_mermaid.py and wiki_quality_pipeline.py don't have a post-processing hook point — both are batch scripts rather than pipelines. The fence rewriter is designed to be called anywhere before content is stored; wiki save paths that need dashboard embedding should call rewrite_content_md after any Mermaid processing.