Skip to content

feat: client-side AADL diagram rendering via spar WASM#5

Merged
avrabe merged 2 commits intomainfrom
feat/client-side-wasm-rendering
Mar 9, 2026
Merged

feat: client-side AADL diagram rendering via spar WASM#5
avrabe merged 2 commits intomainfrom
feat/client-side-wasm-rendering

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented Mar 9, 2026

Summary

  • Replace server-side /api/render-aadl (shelled out to spar CLI) with browser-side rendering using the jco-transpiled spar WASM component
  • Add /wasm/ and /source-raw/ routes for serving WASM assets and raw file content
  • AADL diagrams in documents now render client-side with zoom/pan controls and caption bars
  • Add comprehensive AADL/spar integration guide to getting-started.md
  • Update build-wasm.sh for --instantiation async jco transpilation

Test plan

  • cargo check --all passes
  • cargo clippy --all-targets clean
  • rivet serve renders AADL diagrams in architecture.md documents
  • Zoom controls (+/-/reset) and mouse wheel zoom work
  • Drag panning works on diagram viewport
  • No double-rendering of diagrams on page load

🤖 Generated with Claude Code

avrabe and others added 2 commits March 9, 2026 08:28
Replace the server-side /api/render-aadl endpoint (which shelled out
to the spar CLI) with browser-side rendering using the jco-transpiled
spar WASM component.

- Add /wasm/{*path} route to serve jco-transpiled JS + WASM assets
- Add /source-raw/{*path} route for raw file access (files + dir listings)
- Rewrite AADL_JS to load spar WASM module with a virtual WASI
  filesystem adapter, call renderer.render() client-side
- Add diagram caption bar with AADL badge, root name, component count
- Add zoom controls (+/-/reset) and mouse wheel zoom + drag pan
- Remove server-side SparAnalyzeOutput, find_aadl_files, build_instance_graph
- Exclude /wasm/ and /source-raw/ from HTMX redirect middleware
- Fix double-render by removing DOMContentLoaded listener

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add comprehensive AADL integration section to getting-started.md
  covering setup, architecture artifacts, diagram embedding, dashboard
  views, and client-side WASM rendering flow
- Update build-wasm.sh to use --instantiation async for jco transpilation
- Update WASM README with async instantiation instructions
- Fix MSRV reference (1.85 -> 1.89)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@avrabe avrabe merged commit 9a4c4d1 into main Mar 9, 2026
11 checks passed
@avrabe avrabe deleted the feat/client-side-wasm-rendering branch March 9, 2026 07:31
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 9, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

avrabe added a commit that referenced this pull request Apr 22, 2026
…f-life) (#189)

* fix(embed,docs,ui): v0.4.2 UX polish + 4 embed bugs

Surfacing existing-but-invisible data and fixing silent-accept in the
embed pipeline that the v0.4.1 Mythos pipeline would have flagged.

UI surfacing (data was always there, UI didn't show it):
- Artifact detail now lists documents that reference the artifact
  via [[ID]] links, with line numbers per occurrence.
- Mermaid/AADL diagrams on artifact detail and schema-show pages now
  wrap in .svg-viewer so they get the same zoom / fullscreen / popout
  toolbar as graph and doc-linkage views.

Embed correctness:
- {{group:TYPE:FIELD}} two-arg form — the second arg was silently
  discarded, causing every artifact to bucket into "unset" because
  the first arg was read as the field name. Now scopes by type and
  groups by field as the syntax suggests.
- {{query ...}} now honors fields= to customize table columns and
  rejects colon-prefixed option syntax (:limit 10) with a helpful
  error pointing to key=value form — previously silently dropped.
- Standalone {{artifact|links|table:...}} on its own line no longer
  wraps in <p>, which produced invalid HTML nesting. Block-level
  embeds emit directly.

Docs-check gate:
- rivet docs check now honors rivet.yaml's docs: list instead of
  only scanning the top-level docs/ directory. Projects with
  crates/*/docs or rivet/docs layouts are no longer silently missed.

Implements: REQ-004, REQ-008, REQ-010
Refs: FEAT-001

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(validate): count flow-style and named-field links for cardinality

Two silent-accept bugs in the link cardinality validator that let
"required: true, cardinality: one-or-many" link-fields appear absent
while rivet validate stayed PASS.

Flow-style: `links: [{type: X, target: Y}]`
  The rowan CST parser records flow sequences but does not emit nested
  flow-mapping nodes, so extract_links walked an empty Sequence and
  returned zero links. Added a serde_yaml fallback that re-parses the
  value text when the CST yields no Sequence — flow and block styles
  now produce byte-identical Link vectors.

Named-field form: schema `link-field.name: targets` + artifact
`targets: [SEC-AS-001]`
  shorthand_links was declared on ArtifactTypeDef but never populated,
  so the yaml_hir shorthand path never fired and `targets:` fell into
  custom-fields instead of becoming a threatens-link. Schema::merge
  now derives shorthand_links from each link-field automatically.

Both shapes were displayed correctly by `rivet get` (the parser saw
them for rendering) but the cardinality counter saw zero — the worst
kind of bug for safety tooling. Regression test pins the invariant
that flow-style and block-style yield identical Link vectors.

Fixes: REQ-004
Verifies: REQ-004

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(schema,validate): treat undeclared link types as ERROR + add schema-consistency check

Two changes that close a silent-accept loop in the schema layer:

1. validate.rs — `unknown-link-type` Severity bumped from Warning to
   Error. An undeclared link-type means the schema's cardinality and
   target-type guarantees silently don't apply to those links; that's
   not an advisory, it's an integrity failure. De-duplicates per
   (artifact, link_type) so a single typo doesn't drown the report.

2. schema.rs — new `Schema::validate_consistency()` returns a list of
   schema-internal issues:
   - link-fields referencing undeclared link types
   - link-fields with unknown target artifact types
   - traceability rules with unknown from/target types

   This mirrors what `rivet schema validate` already reported but is
   now a library function callable from any load path, so a downstream
   project can fail-fast on a broken schema instead of validating
   artifacts against silently-broken rules.

Two regression tests pin the invariants: undeclared link type emits
exactly one Error diagnostic per (artifact, link-type) pair, and
schema_consistency flags both dangling link-types and missing target
types.

Implements: REQ-010
Verifies: REQ-004, REQ-010

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(docs-check): external-namespace exemption + skip directives + AGENTS.md template fix

Three-layered escape hatch so the ArtifactIdValidity gate can run as a
required CI check on projects that legitimately reference external IDs
(Jira, Polarion, hazard catalogs, upstream stakeholder docs).

1. rivet.yaml `docs-check.external-namespaces: [GNV, GNR, HZO, UC, ...]`
   exempts every `<PREFIX>-NNN` matching one of those prefixes (case-
   insensitive). Most discoverable form — namespace-level allow-list.

2. rivet.yaml `docs-check.ignore-patterns: [<regex>, ...]` for cases
   where namespace matching isn't enough. Free-form regex escape hatch.

3. HTML-comment directives in any markdown:
   `<!-- rivet-docs-check: ignore GNV-396 FOO-1 -->` skips the named
       IDs anywhere in the doc.
   `<!-- rivet-docs-check: ignore-line -->` skips every ID on the same
       line as the directive.
   Most surgical — keeps the exemption visible right at the citation.

Bundled AGENTS.md template now embeds an `ignore SC-1 REQ-001 FEAT-042`
directive so a fresh `rivet init && rivet docs check` doesn't fail on
its own example IDs.

Two regression tests cover both the config and directive paths. The
24 existing doc_check tests still pass — the new context fields are
additive, all in-tree call sites updated.

Implements: REQ-004
Refs: REQ-008

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(stamp): batch filter flags --type, --changed-since, --missing-provenance

Three filter flags so provenance stamping no longer requires shell loops
like `rivet list | grep ... | xargs -I{} rivet stamp {} ...`. All
combinable with each other and with the existing `id "all"` form.

  --type PATTERN
    Glob (`SEC-*`) matches IDs by prefix; otherwise exact artifact-type
    name (`requirement`). Combine with `id "all"` or a wider glob.

  --changed-since REF
    Restrict to artifacts whose source YAML was touched relative to the
    given git ref — committed diff plus uncommitted modifications.
    Falls back to "no matches" on git error so the flag never panics.

  --missing-provenance
    Skip artifacts that already carry a `provenance:` block. Idempotent
    bulk stamping: rerun safely without overwriting human-reviewed
    provenance.

Glob support uses an in-tree minimal `*` / `?` matcher (5 unit tests)
to avoid pulling in the `glob` crate for one tiny need.

Implements: REQ-007
Refs: REQ-008

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: cover AUDIT marker, conditional-rules, rivet-managed contract; require --yes for --force-regen

Closes the documentation gaps the user flagged in Issue #5:

- AUDIT: marker syntax for the ArtifactCounts invariant — explains
  when to use it (manual override of "N items" claims) and that the
  date is not parsed (advisory only).
- conditional-rules: worked example showing when/then structure with
  `equals` condition and `required-fields` requirement.
- rivet-managed BEGIN/END markers contract — content outside the
  markers is preserved across `rivet init --agents` regeneration; only
  use --force-regen if markers were lost.
- rivet docs check escape hatches — three layered exemption
  mechanisms (config namespaces, ignore-patterns regex, HTML-comment
  directives) documented in one place.

CLI safety: `--force-regen` now requires `--yes` (clap requires=).
The destructive overwrite was previously one accidental flag away;
now it needs both `--force-regen --yes` to fire.

CLI clarity: error message for `rivet embed artifact:X` / `links:X` /
`table:T:F` now explains why those embeds only render inside markdown
documents, instead of the cryptic "handled inline" string. Saves
users an hour of debugging.

Refs: REQ-008, REQ-007

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(lsp,yaml): LSP resolves workspace schemas; YAML parser tolerates inline comments

Issue #6 — two LSP-noise sources that train users to ignore the LSP.

(a) LSP schema resolution
    The LSP previously called resolve_schemas_dir(cli) using cli.project,
    which is whatever directory the LSP process was launched from — often
    not the workspace root. So an LSP started from VS Code would look in
    `<cwd>/schemas/` and never find user schemas like
    `<workspace>/schemas/ulinc.yaml`. Result: every artifact-type defined
    in a project schema reported as "unknown artifact type" even though
    `rivet validate` accepted them.

    Now the LSP resolves the schemas directory from the workspace
    `project_dir` (derived from root_uri), with --schemas as the explicit
    override and the binary-relative location as a final fallback.

(b) YAML inline-comment handling
    The CST mapping parser fell to the generic error branch on any
    line whose first non-whitespace token was a Comment, producing
    "expected mapping key, found Some(Comment)" diagnostics on every
    `.github/workflows/*.yml` line with a leading `# ...` comment.
    serde_yaml and python yaml.safe_load both parse the same input fine.

    parse_block_mapping now eats Comment tokens at the key position and
    continues, treating them as trivia. Trailing inline comments and
    nested-mapping leading comments were already handled.

Three regression tests pin the YAML behaviour:
- mapping_with_comment_only_line
- mapping_with_indented_comment_line
- mapping_with_inline_trailing_comment_on_value

Implements: REQ-029, REQ-028
Verifies: REQ-029

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(ui,docs,e2e): TOC anchors + variant dashboard docs + Playwright parity

Folds the items originally deferred to v0.4.3 back into v0.4.2 because
the customer needs them shipped together.

UI:
- Markdown headings now emit `id="..."` slugs so in-page TOC links and
  external `#anchor` URLs actually navigate. New `slugify_heading`
  strips embedded HTML (the inline embed resolver may have substituted
  artifact cards), lowercases, and collapses non-alnum runs to single
  hyphens. Two regression tests pin the slug shape and the heading id.

Docs:
- getting-started.md gains a "Variants in the dashboard" subsection
  documenting the auto-discovery convention, the sidebar+dropdown UI,
  and the `/variants` overview page. Closes the doc-reality drift the
  user flagged ("how would I even see the variant").
- what-is-rivet.md section 4.5 mentions the dashboard variant selector
  alongside the CLI workflow.

Playwright:
- New `diagram-viewer.spec.ts` codifies the architectural invariant:
  every dashboard view rendering a diagram (graph, doc-linkage,
  schema-linkage) wraps it in `.svg-viewer` with the same toolbar
  (zoom-fit, fullscreen, popout). Fullscreen toggle verified.
- `artifacts.spec.ts` extended with: (a) artifact mermaid diagram
  wrapped in svg-viewer, (b) "Referenced in Documents" reverse-index
  block visible (skips when fixture has no references).
- `documents.spec.ts` extended with: rendered headings carry `id`
  attributes — TOC navigation regression guard.

Implements: REQ-008
Refs: FEAT-001, REQ-007

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(embed,sexpr,yaml): close 6 silent-accept findings from project audit

Background audit (run after the v0.4.2 batch) flagged 6 more sites
where invalid input was silently swallowed instead of surfaced. All
close before v0.4.2 ships.

P1 — visible silent skips that masked typos as "no data":

- {{coverage:typo-rule}} now errors with a list of known rule names
  instead of rendering "No coverage rules defined" — previously a
  typo'd rule was indistinguishable from a project genuinely without
  rules.
- {{diagnostics:warnings}} (typo for `warning`) now errors with the
  three valid severities. Previously the unknown severity matched the
  `_ => true` arm and returned ALL diagnostics, hiding the typo.
- {{matrix:UnknownType:OtherType}} now errors before rendering. The
  blank table that used to appear was indistinguishable from "rule
  applies but nothing covered yet" — the user couldn't tell their
  typo from a real coverage gap.

P2 — narrower-scope clarity wins:

- sexpr_eval `links-count` now distinguishes "second arg isn't a
  symbol" from "operator literally empty" from "operator not in the
  allowed set", with a hint listing the six valid operators.
- yaml_hir `extract_string_list` now falls back to serde_yaml when the
  CST yields zero items from a non-empty FlowSequence — same defensive
  pattern that fixed `extract_links` for flow-style mappings.
- yaml_hir top-level mapping walk now emits a Warning diagnostic when
  an unknown key looks like a typo of a schema-defined section
  (singular vs plural, fuzzy match). Legitimate metadata keys still
  pass silently. Catches misspellings like `control-action:` (should
  be `control-actions:`) that previously dropped every nested item.

Three new regression tests pin the P1 invariants: matrix rejects
unknown types, diagnostics rejects unknown severities, coverage
rejects unknown filter rules. Each verifies the error message names
the bad input.

Pattern: every fix here returns Err with a hint listing valid values
or known names — the same shape we adopted in the v0.4.2 main batch
for `{{group}}` and `{{query}}` parameter validation.

Implements: REQ-004, REQ-029
Verifies: REQ-004

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(schema,parse): deny_unknown_fields on schema structs + fix docs-check UTF-8 boundary panic

Surfaces SCRC "deterministic parsing / strict schemas" requirement:
typo'd YAML keys in schema author files now error at load time
instead of silently dropping. Annotates every schema-author struct
with `#[serde(deny_unknown_fields)]`:

- SchemaFile, SchemaMetadata, ArtifactTypeDef, MistakeGuide,
  FieldDef, LinkFieldDef, LinkTypeDef, TraceabilityRule,
  ConditionalRule, AlternateBacklink (new) — schema authoring
- Link, Provenance — artifact-level

The strict deserialization immediately surfaced two missing fields the
bundled schemas were already using:
- LinkFieldDef.description — explanatory text per link-field
- TraceabilityRule.alternate_backlinks (new struct) — alternate
  backlink shapes used by safety-case schemas to express
  "supported-by OR decomposed-by OR has-sub-goal" without rule
  duplication

Both added as `#[serde(default)]` Optional/Vec to preserve back-compat;
14 in-tree TraceabilityRule constructors updated to pass `vec![]`.

Tangential bug found while landing this:
- doc_check.rs:557 panicked on multi-byte boundary when slicing the
  32-byte VersionConsistency context window if the cut landed inside
  a multi-byte char (e.g. an em-dash in a heading). Walks back to a
  char boundary first.

Also clarified the resolve_str function in sexpr_eval.rs with a
SAFETY-REVIEW-style comment explaining the intentional empty-string
semantics for missing fields in filter queries (NOT silent-accept;
it's the correct semantics for `(= asil "ASIL-D")` filters that
should naturally exclude artifacts without an `asil` field).

Hardened render_diagnostics severity match: the `_ => true` arm is now
unreachable after the upstream validation pass landed earlier in this
release; replaced with explicit `None` arm + `unreachable!()` for any
other Some(value), so a contract bug fails loudly instead of silently.

CHANGELOG.md gains a v0.4.2 entry covering all 18 silent-accept
findings and the v0.4.3 SCRC roadmap.

Implements: REQ-010, REQ-004
Verifies: REQ-010

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(ci): rivet-delta PR workflow with graphical artifact diff + Playwright coverage

New informational workflow (.github/workflows/rivet-delta.yml) that runs
on every PR touching artifacts/schemas/rivet.yaml/rivet-core/rivet-cli.
For each PR it:

  1. Checks out base (merge-base) and head side-by-side.
  2. Builds rivet-cli in release mode.
  3. Runs `rivet diff --base ... --head ... --format json`.
  4. Runs `rivet impact --since <base-sha> --depth 5 --format json`.
  5. Runs `rivet export --format html --offline` for the head tree.
  6. Generates a PR-comment markdown via scripts/diff-to-markdown.mjs.
  7. Uploads the full delta-out/ (HTML dashboard + JSON) as a workflow
     artifact retained for 14 days.
  8. Posts or updates a single PR comment (found via the hidden marker
     `<!-- rivet-delta-bot -->`) so the diff stays fresh across
     subsequent pushes without stacking.

Security:
  * All user-derived inputs (`pull_request.number`, `pull_request.base.sha`,
    `run_id`, `repository`) are captured at job-scope env and referenced
    from run: blocks via $VAR — follows GitHub's workflow-injection guide.
  * Every diff/impact/export step is `continue-on-error: true`; the script
    gracefully emits a warning comment if either JSON fails to load
    instead of crashing the workflow.
  * Never blocks merge — strictly informational.

Markdown format (scripts/diff-to-markdown.mjs):
  * Hidden `<!-- rivet-delta-bot -->` marker for comment-update lookup.
  * Counts table (added / removed / modified / impacted).
  * Mermaid link-graph diagram, capped at 30 nodes with an `overflow`
    sentinel node for the remainder; 3 classDef styles for
    added/removed/modified.
  * Collapsible `<details>` blocks per change class with 200-row cap
    plus `… +N more` overflow.
  * Workflow-artifact download link.
  * All user-controlled strings escaped (pipe, backtick, *, _, [, ],
    angle brackets) so hostile artifact IDs can't break out of the
    comment structure.

Playwright regression suite (tests/playwright/rivet-delta.spec.ts):
  * 6 tests pinning the "visible and usable" contract:
    - shipping summary renders end-to-end (counts table visible,
      "Modified" details expands, artifact link has correct href).
    - Empty-diff path emits the no-change sentinel (no mermaid block).
    - Malformed-JSON path produces a warning comment, not a crash.
    - Mermaid source parses with the real bundled mermaid.js parser
      (catches broken syntax before a reviewer sees a broken diagram).
    - 30-node cap produces the `overflow` sentinel.
    - Markdown metacharacters in artifact IDs (`*evil*`, `|pipe|`) are
      escaped, not interpreted.

Refs: FEAT-001, REQ-008

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: bump workspace + VSCode + npm to 0.4.2

Three surfaces kept in sync for the v0.4.2 release tag:
- workspace Cargo.toml
- vscode-rivet/package.json
- npm/package.json

Per-platform npm packages (darwin-x64, linux-arm64, etc.) are generated
with matching versions by the release-npm.yml workflow at publish time;
no hand-editing needed.

Trace: skip

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant