Skip to content

AADL diagrams stuck on 'Loading…' in static HTML export (compliance reports) — inline SVG at export time #468

Open

Description

@avrabe

Bug (user-reported)

AADL architecture diagrams in the published static compliance report never render — they're stuck on "Loading AADL diagram…" forever. Reproduced at https://pulseengine.eu/reports/rivet/0.15.0/compliance/documents/ARCH-001.html (every diagram: System Architecture, Core Process Internals, CLI Process, Adapter Pipeline, Dashboard).

Root cause (traced through the code)

AADL diagrams are produced as a placeholder that is filled client-side at runtime, only by rivet serve:

  • rivet-core/src/document.rs:567 (and rivet-cli/src/render/artifacts.rs:627) emit
    <div class="aadl-diagram" data-root="…"><p class="aadl-loading">Loading AADL diagram...</p></div>.
  • The JS that fills it — initAadlDiagrams() in rivet-cli/src/serve/js.rs:741 — is part of the serve bundle only and:
    • fetchAadlSources() → fetch('/source-raw/arch') + fetch('/source-raw/arch/<file>')
    • checkWasmAvailable() → fetch('/wasm/spar_wasm.js')
    • renders via the spar WASM module in-browser.

A static export (rivet export --format html, used for the compliance report) bundles neither that JS nor those server routes, so the placeholder is never touched → perpetual "Loading…". Two compounding problems:

  1. The export doesn't include/invoke the AADL JS at all (so every JS error/fallback path — "requires spar WASM", "Failed to load sources" — never even runs; hence "Loading…" not a fallback message).
  2. Even if it did, the fetch paths are absolute-from-domain-root (/source-raw/…, /wasm/…), wrong for a report served under /reports/rivet/0.15.0/compliance/.

On rivet serve the diagrams render only if the spar WASM was built (./scripts/build-wasm.sh); otherwise serve shows the "requires spar WASM" fallback (not a hang).

Fixes

  • Immediate (done, #REQ-196 / linked PR): the export now rewrites the perpetual "Loading AADL diagram…" into an honest static note pointing to the interactive rivet serve view, so the report no longer looks hung.
  • Proper (this issue): inline the rendered SVG at export time. rivet-core/src/wasm_runtime.rs already has a server-side spar renderer (render_aadl_via_wasm / call_render, "load the real spar WASM component"). At rivet export --format html, for each .aadl-diagram data-root="X", run that renderer over the .aadl sources (the arch/ dir) and inline the <svg> directly — no client JS, no server routes, works in any static host.
    • Requires the wasm feature in the export/release build + the spar component present; needs a clean fallback when unavailable (the #REQ-196 note).
    • Alternative considered: bundle spar_wasm.js + .wasm + the .aadl sources as relative files and rewrite the fetch URLs to relative (reuses the existing client renderer offline). Heavier payload; compileStreaming from a static host is fine over http.

Workaround now

View the document in rivet serve with the WASM built (./scripts/build-wasm.sh); the dashboard renders the diagrams interactively.

Activity

  1. avrabe commented on Jun 4, 2026

    @avrabe
    ContributorAuthor

    Deeper root cause — the WASM is stubbed everywhere, not just in static export.

    Tracing the asset pipeline:

    • .gitignore excludes rivet-cli/assets/wasm/*.wasm and assets/wasm/js/ — the WASM is never committed (correct, they're binaries).
    • rivet-cli/build.rs::build_wasm_assets() falls back to writing empty stubs when no assets are on disk and no local spar checkout exists (spar_wasm.js = // stub: spar WASM not available, spar_wasm.core*.wasm = 0 bytes), so include_bytes! compiles.
    • serve/mod.rs:65-68 include_bytes!s those assets into the binary → the released binary serves the stub at /wasm/spar_wasm.js → AADL renders nowhere (serve or export).
    • scripts/fetch-wasm.sh looks for a spar_wasm.wasm release asset, but spar publishes no WASM asset (v0.10.0/v0.12.0 ship vsix/tarballs/sbom only) → it can't populate real assets.
    • scripts/build-wasm.sh needs a local spar checkout + wasm32-wasip2 + Node/jco → fragile, the reported breakage.

    So the "Loading…" placeholder is the visible symptom; the underlying cause is no real WASM ever reaches the build.

    Fix is cross-repo (filed pulseengine/spar#259): spar's release publishes the jco-transpiled browser bundle (spar_wasm.js + spar_wasm.core*.wasm, e.g. spar-wasm-browser-<ver>.tar.gz) + optionally the raw spar_wasm.wasm. Then on the rivet side: fetch-wasm.sh pulls that bundle into assets/wasm/js/ and runs in CI before cargo build (so the binary embeds real WASM), pin bump to that spar version. The export-time SVG inlining proposed above then also has a real component to render with.

    PR #469 (the honest "Loading…" → static note) stands as the interim so reports don't look hung.

  2. avrabe commented on Jun 5, 2026

    @avrabe
    ContributorAuthor

    Triage pass — premise confirmed against current main and the author's deeper-root-cause comment above. The full diagnosis is:

    • rivet-core/src/document.rs:567 + rivet-cli/src/render/artifacts.rs:627 emit the <p>Loading AADL diagram...</p> placeholder unconditionally.
    • The filler JS (initAadlDiagrams() in rivet-cli/src/serve/js.rs:741) lives only in the serve bundle and fetches absolute-from-domain-root URLs (/source-raw/..., /wasm/...) that don't exist in a static export.
    • Underlying that: rivet-cli/build.rs::build_wasm_assets() writes empty stubs for spar_wasm.js / spar_wasm.core*.wasm when no real assets are on disk and no local spar checkout exists — and spar publishes no WASM asset to its releases (v0.10.0 / v0.12.0 ship .vsix / tarballs / SBOM only). So the released binary embeds the stub at /wasm/spar_wasm.js and AADL silently renders nowhere — serve or export.
    • The export rewrite landed via fix(export): honest AADL fallback in static HTML, no perpetual Loading (REQ-196, #468) #469 (REQ-196) — the "honest static note" interim — so reports no longer look hung; the diagrams still aren't there.

    The proper fix is well-scoped but blocked on a cross-repo dependency that's outside this agent's write surface (restricted to pulseengine/rivet): pulseengine/spar#259 has to publish the jco-transpiled browser bundle (spar_wasm.js + spar_wasm.core*.wasm) as a release asset before scripts/fetch-wasm.sh can pull real WASM into the build. Once that ships, the rivet-side work has a clean shape — and the "one PR per issue, keep PRs focused" rule means I need the done-state encoded before picking it up.

    Could you add an explicit ## Acceptance Criteria block? Sketch — feel free to revise / re-scope (deliberately scoped to the in-repo half; the spar-side bundle publishing stays out of scope and tracked in spar#259):

    ## Acceptance Criteria
    - [ ] **Prerequisite (out of scope, tracked in pulseengine/spar#259):** spar publishes the jco-transpiled browser bundle (`spar_wasm.js` + `spar_wasm.core*.wasm`, e.g. `spar-wasm-browser-<ver>.tar.gz`) as a release asset. This issue is blocked until that lands.
    - [ ] `scripts/fetch-wasm.sh` is updated to pull the spar browser bundle into `rivet-cli/assets/wasm/js/` (replacing / supplementing the current `spar_wasm.wasm`-only logic), and runs in CI before `cargo build` so the released binary embeds real WASM instead of the `build.rs` empty stubs
    - [ ] Pin bump in `Cargo.toml` to the spar version that publishes the bundle (recorded in a `Refs: spar#259` trailer)
    - [ ] At `rivet export --format html`, for each `.aadl-diagram data-root="X"` placeholder, `render_artifacts` invokes `rivet_core::wasm_runtime::render_aadl_via_wasm` (the existing server-side renderer — `wasm_runtime.rs::call_render` / "load the real spar WASM component") over the `arch/` sources and **inlines the resulting `<svg>` directly** — no client JS, no server routes, works under any static host (verified by an export served from `file://` and a `/reports/rivet/<ver>/compliance/` subpath)
    - [ ] **Clean fallback when WASM is unavailable** (feature flag off, build-time assets missing, or spar component fails to load): the export falls back to the honest static note from #469 (REQ-196) rather than hanging on "Loading…" or failing the export. Covered by a unit test that disables the `wasm` feature and asserts the fallback path
    - [ ] Regression test: golden fixture export of an `.aadl-diagram` placeholder under default features produces an inlined `<svg>` (not the placeholder, not an absolute fetch URL); same fixture with `--no-default-features` (or `wasm` off) produces the static fallback note. Covers all five diagrams listed in the issue (System Architecture, Core Process Internals, CLI Process, Adapter Pipeline, Dashboard)
    - [ ] End-to-end verification on the published-shape path: a static export written under a `/reports/rivet/<ver>/compliance/documents/` prefix has zero AADL-related network requests in the rendered page (verified by inspecting the HTML for `fetch(`, `/source-raw/`, `/wasm/`)
    - [ ] `rivet validate` on the rivet repo still PASS; `cargo test -p rivet-core -p rivet-cli` green on both feature configurations; `cargo fmt --check` / `clippy --all-targets -- -D warnings` clean
    - [ ] Commit trailers: `Implements: REQ-NNN` (file a new REQ for offline AADL inlining; e.g. REQ-200+) + `Refs: REQ-196, spar#259`
    - [ ] **Explicitly out of scope (file separately if wanted):** the cross-repo spar bundle publishing (tracked in spar#259); the `rivet serve` interactive path (still uses the fetch-based renderer — unchanged by this PR); replacing the placeholder emission in `document.rs:567` (the placeholder remains the canonical pre-render shape; only its post-render fate changes); a relative-URL bundle-and-rewrite alternative (heavier payload, considered and rejected in the body)

    Borderline calls worth your input even before AC is final:

    • Where does the rendered-SVG inlining live? Two options: (a) extend render_artifacts in rivet-cli/src/render/artifacts.rs:627 to call wasm_runtime directly during the export pass (closer to the placeholder emission site); (b) post-process the rendered HTML in the export pipeline after the document model is rendered (more centralized but adds a re-parse). (a) is closer to the existing structure and reuses the per-.aadl-diagram walk you'd already do for the fallback note — recommending (a) unless there's a reason to prefer the post-process pass.
    • Is the wasm feature default-on for release builds? If yes, the inlined-SVG path is the always-on default and the static-note fallback is for --no-default-features only. If the feature is opt-in, the released binary still ships the static note. The body assumes default-on; worth confirming once validate/list (CLI hot path) force a cold build of the entire serve+MCP+LSP stack — no minimal-build feature #456's feature-gating proposal settles.
    • Path-resolution of arch/ sources at export time: render_aadl_via_wasm takes the .aadl text; the export pass has to load the project's arch/ files for each data-root="X". This is the same path resolution the serve /source-raw/arch route uses — should it factor through a shared helper, or inline it in the export path?

    Process side-rail (same as the other open triage threads today): https://pulseengine.eu/blog/ is still HTTP 503 — same expired-TLS symptom flagged on #420. The binding workflow guidance can't be consulted, so no PR will land from this triage run regardless. Even with AC added, this issue is blocked on spar#259 publishing the bundle. Comment is the AC ask only.

    Not implemented this run.


    Generated by Claude Code — issue-triage agent run 2026-06-05.


    Generated by Claude Code

  3. avrabe commented on Jun 5, 2026

    @avrabe
    ContributorAuthor

    ⚠️ Re-opening the substance: I dogfooded the real rivet export --format html (built current main, exported all 988 pages, scanned them) and the perpetual "Loading AADL diagram…" placeholder is still shipping — in documents/ARCH-001.html (this exact page) and artifacts/ARCH-SYS-001.html.

    REQ-196 (#469) didn't actually fix it. It patched static_aadl_fallback in rivet_core::export::render_document_body_for_export, but the html export is driven by cmd_export_html (rivet-cli) → the render/ serve renderers + a wrap_page layout, which never calls that rivet-core function. So REQ-196 was a no-op for the real export (and the compliance report) — it was marked implemented on a helper unit test, not an e2e export check.

    Fixed properly in #472… (REQ-200): the placeholder→honest-note replacement now lives in wrap_page (the universal per-page wrapper), with an e2e test that walks the whole export and asserts zero raw placeholders. The honest interim fallback is now genuinely present in the export.

    (The real diagrams still need spar to publish the WASM bundle — spar#259 — that part is unchanged.)

  4. avrabe commented on Jun 5, 2026

    @avrabe
    ContributorAuthor

    Status from the dogfooding loop — the static-export AADL placeholder is being fixed in two parts:

    Part 1 — the perpetual "Loading AADL diagram…" placeholder (shipped). PR #473 (REQ-200) is merged. Root cause turned out to be that the earlier REQ-196 patch was a no-op for the real export: it patched rivet_core::export::render_document_body_for_export, but rivet export --format html renders via cmd_export_html in rivet-cli, which never calls that function. The fix moved the placeholder→honest-note replacement into wrap_page, the universal per-page wrapper, so every exported page is covered. An end-to-end test now walks the full export and asserts zero pages contain the raw placeholder. So the published report will no longer show a forever-spinning "Loading…"; it shows an honest note that interactive AADL rendering is available in rivet serve.

    Part 2 — actually rendering the SVG inline at export time (still open). This is the real ask in your title and it remains parked on an upstream dependency: AADL diagrams are rendered by spar's WASM module, which rivet serve fetches at runtime. Static export has no JS runtime, so to inline the SVG we need spar to publish a prebuilt WASM artifact that rivet can bundle at build time (tracked in spar#259, currently unanswered) — the local build-wasm.sh path needs spar + wasm32-wasip2 + jco and fetch-wasm.sh targets a release asset that doesn't exist yet. Once spar ships that asset, rivet can bundle it and render the diagrams inline during export.

    Separately, while dogfooding this I found and fixed REQ-201 (PR #474): rivet export --format html --single-page hung for minutes on the full rivet repo because the static graph render had no node budget — the serve path already caps at 200 nodes, the static path didn't. Not your bug, but adjacent in the same export path.

  5. avrabe commented on Jun 17, 2026

    @avrabe
    ContributorAuthor

    Triage from the issue-fixing routine: this has detailed root-causing and a "Proper fix" direction (inline SVG at export time using wasm_runtime::render_aadl_via_wasm), but no explicit "fixed when …" line the routine can verify before opening a PR, so it's deferred until one is added.

    A useful shape matching #532's kill-criterion model: "fixed when rivet export --format html on a project containing AADL diagrams produces report HTML in which every .aadl-diagram placeholder is replaced by inline rendered ``, and the placeholder text 'Loading AADL diagram…' appears zero times in the export output (with a clean fallback note when the spar component is unavailable)". Once that's pinned I'll pick it up.


    Generated by Claude Code

  6. avrabe commented on Aug 6, 2026

    @avrabe
    ContributorAuthor

    Partially shipped — staying open with a named residual.

    Shipped in v0.30.0 (tag contains 77faf5e, REQ-273, PR #734): the static compliance report no longer sits on a perpetual Loading AADL diagram…. Static export now emits an honest fallback — a "rendered interactively in rivet serve" note plus the AADL source in a <details> block — and the fix lands at the render source, so the earlier post-hoc text replacements became harmless no-ops.

    Remaining: the original ask — real inline SVG at export time — is not done. It needs a non-stub spar-wasm component embedded at build time; rivet-cli/assets/wasm/ still ships a stub because spar does not yet publish a WASM release asset (spar#259). The host-side render path is already wired, so inline SVG activates automatically once a real component is embedded, with no further rivet code change.

    Blocked on spar#259 — cross-repo. Closing this would overstate what shipped.

  7. avrabe commented on Sep 6, 2026

    @avrabe
    ContributorAuthor

    Triage note (scheduled routine, first pass on this issue — filed 2026-06-04, six prior comments none of which are an AC block).

    The body traces the root cause through source cleanly: the AADL placeholder (rivet-core/src/document.rs:567, rivet-cli/src/render/artifacts.rs:627) is filled only by initAadlDiagrams() in the serve bundle (rivet-cli/src/serve/js.rs:741), which fetches from /source-raw/… and /wasm/… — routes a static export doesn't ship. "Immediate" (rewrite to an honest note) is marked done; "Proper" (inline the rendered SVG at export time via the existing server-side renderer render_aadl_via_wasm in rivet-core/src/wasm_runtime.rs) is the slice this issue owns. What is missing to make it implementable is an explicit Acceptance Criteria section.

    Sketch matching the shape used across #807 / #862 / #880 / #885 / #887 / #892 — drop in / revise:

    ## Acceptance Criteria — inline AADL SVG at export time
    
    - [ ] `rivet export --format html` (feature `wasm` on, spar component available) inlines a rendered `<svg>` for every `<div class="aadl-diagram" data-root="X">` in the exported document tree, replacing the placeholder before it hits disk. No client JS, no server routes required to view the report.
    - [ ] Regression fixture: export the ARCH-001 document (or a minimal repro containing one `.aadl-diagram` div) and grep the output — pre-fix, `aadl-loading` appears; post-fix, `<svg` appears inside `class="aadl-diagram"` and `aadl-loading` does not.
    - [ ] When the `wasm` feature is off or the spar component is not present, the export falls back to the honest "see rivet serve" note from #REQ-196 (not a hang, not a stack trace) — same fallback path already shipped.
    - [ ] Rendered SVGs are self-contained (no external font/image fetches from the exported page) so the report works on any static host, including offline. The `pulseengine.eu/reports/rivet/<ver>/…` URL from the body renders the diagrams after the next report rebuild.
    - [ ] `rivet serve` behaviour is unchanged (interactive AADL still works via the existing client renderer).
    - [ ] `cargo test --workspace` (with `wasm` feature) green; `cargo fmt` / `cargo clippy --all-targets -- -D warnings` clean; `rivet validate` PASS on the rivet repo.
    - [ ] Commit trailers: `Fixes: REQ-<N>` for the export-render contract (file a new REQ if none pins it; the STPA lineage is worth having) + `Refs: #468, REQ-196`.
    - [ ] Explicitly out of scope: the "bundle spar_wasm.js + relative-URL rewrite" alternative — deliberately deferred, would ship a heavier report and doesn't remove the runtime-fetch requirement.

    Not implemented this run — no PR opened; the AC block on the body is the gating input. Blog checked (https://pulseengine.eu/blog/) — no workflow-shape post supersedes existing conventions.

    https://claude.ai/code/session_011kxvVgyDABR3yAJeY6ZbUC


    Generated by Claude Code

  8. avrabe commented on Sep 9, 2026

    @avrabe
    ContributorAuthor

    Half of this is verifiably fixed; the half in the title is not. Staying open,
    with the residual named.

    The reported symptom is gone

    Reproduced against the shipped v0.37.0 compliance report, not against main —
    downloaded rivet-v0.37.0-compliance-report.tar.gz from the release and read
    documents/ARCH-001.html, the exact file this issue cites:

    "Loading AADL diagram" occurrences: 0
    

    What each diagram block contains now:

    <div class="aadl-diagram" data-root="RivetSystem::Rivet.Impl">
      <p class="aadl-loading">AADL diagram — rendered interactively in <code>rivet serve</code>; source below.</p>
      <details class="aadl-source"><summary>AADL source</summary><pre>root: RivetSystem::Rivet.Impl</pre></details>
    </div>

    So the forever-spinner is replaced by a statement of where the diagram does
    render, plus the AADL source inline. Five such blocks in that document. That
    shipped as REQ-273 in v0.30.0.

    I checked this positively rather than trusting the zero: a count of zero
    "Loading" strings would read identically if the file simply had no diagrams, so
    the diagram blocks were located and read.

    The title's ask is still open

    Inline SVG at export time is REQ-247, still proposed. Diagrams do not
    render in the static export; they are described and their source is shown.

    Someone opening this issue because they want to see the architecture in the
    published report
    still cannot. That is why this is not being closed on the
    spinner fix — closing here would report the ask as met when the honest state is
    "the misleading part was removed, the missing part is still missing".

    Scope correction

    REQ-247 was scoped to v0.25.0 — a release long past — while still
    proposed. That combination makes the board misreport itself: a release query
    for v0.25.0 could never go green, and the requirement looked scheduled when
    nothing was scheduled. Moving it to backlog, which is where its blocker
    actually puts it.

    The blocker, stated: the inline-SVG path needs a spar-wasm build available
    at export time. That is cross-repo work, not a change confined to this
    repository, which is why it has sat. Naming it here so the next person does not
    re-derive it.

    Evidence: gh release download v0.37.0 -p 'rivet-v0.37.0-compliance-report.tar.gz',
    then grep -c 'Loading AADL diagram' documents/ARCH-001.html → 0, and reading
    the five aadl-diagram blocks in that file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions