Skip to content

feat(serve,export): render the test-result trace as a fold/expand tree (REQ-274) - #843

Merged
avrabe merged 4 commits into
mainfrom
feat/req-274-collapsible-trace-tree
Aug 25, 2026
Merged

feat(serve,export): render the test-result trace as a fold/expand tree (REQ-274)#843
avrabe merged 4 commits into
mainfrom
feat/req-274-collapsible-trace-tree

Conversation

@avrabe

@avrabe avrabe commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Customer-reported: deep chains — the ASPICE sw-req ← sw-detail-design ← unit-verification shape — rendered as a flat table. REQ-001 produces 40 hops
of undifferentiated rows.

The data was already a tree

trace_test_results walks breadth-first with a seen set, so every reached
artifact has exactly one via_target — it returns that tree flattened.
result_trace::as_tree restores the shape, and rendering goes through the
existing collapsible_tree component. A rendering change over an existing model
plus an existing component; no new infrastructure.

Native <details> means no JavaScript, so the same markup serves rivet serve and the static export. Verified in both — REQ-001 renders 3 foldable
branches + 37 leaf lines, identically, with Expand/Collapse All as progressive
enhancement.

The invariant that matters

A folded view must contain every hop. A branch that silently stops rendering
looks identical to one that is merely collapsed — you cannot tell by looking,
which is the whole problem.

tree_preserves_every_node pins it, including the orphan case (a node whose
parent was never reached is re-homed to top level, not dropped). Negative-
controlled:

disable orphan re-homing  →  FAILED: "tree dropped nodes: 2 in, 1 out"
restore                   →  ok

Two corrections after looking at real output

My first version was not actually more readable than the table it replaced:

  • Leaves rendered as <details> with "no further hops" inside — a disclosure
    triangle over nothing. Now plain lines.
  • All 33 depth-1 siblings opened by default — the same wall, with extra
    chrome. Now a large trace opens nothing and a small one still opens its first
    level, because folding three hops helps nobody.

Result for REQ-001: 40 rows → 37 compact lines + 3 collapsed branches with
"N more" counts.

A vacuous assertion I wrote and caught

The existing serve_integration test asserted the old Hops/Via table and
legitimately broke. My first rewrite compared rendered hop count against
/api/v1/trace-results/REQ-001, guarded on a non-empty response.

That route does not exist. It 404'd, reported == 0, the guard swallowed
it, and the parity assertion never ran — while the test reported green. Exactly
the defect class this session has been chasing, written by me into the test for
the feature about not hiding things.

Removed. Exact parity is asserted in a unit test against a synthetic trace where
the expected count is known; the integration test asserts the tree renders with
hops and that the flat table is gone.

Gates

cargo fmt --check · clippy --all-targets -D warnings on 1.97.0 ·
cargo test --workspace (exit 0, 65 ok, 0 failed) · rivet validate ·
rivet docs check — all exit 0.

REQ-274 → verified, 8 test markers.

avrabe added 2 commits August 21, 2026 20:27
Customer-reported low contrast in the dashboard and the static compliance
export, which share rivet-cli/src/render/styles.rs.

Oracle first: six unit tests compute WCAG 2.1 relative luminance over the
palette and assert 4.5:1 for body text, 3:1 for large text and UI. They parse
hex values OUT OF the live CSS constant instead of copying them, so a
stylesheet edit cannot drift away from its own audit. The suite was red on
three pairs before any colour changed -- accent on --bg 3.20:1, accent on
--surface 3.48:1, white-on-accent 3.48:1 -- matching the values measured when
the requirement was triaged.

Fix: accent #3a86ff -> #2059b8, accent-hover #2568d6 -> #18458d, hue preserved
and chosen for headroom rather than the bare minimum (worst pair 5.07:1, not
4.60:1). 27 translucent rgba washes reshaded to the new rgb so tints match the
colour they tint. .stat-orange #e67e22 -> #c66c1d.

Three findings the requirement's own audit did not have:

  1. The reported pair does not occur. No element paints white text on --bg.
     What a reader actually saw is the white label on the primary button
     (3.48:1) and every link (3.20:1). Both real, both fixed.
  2. Accent text sits on rgba(accent,.08/.12) washes in id chips, inline mono
     tags and source-line highlights. Composited, three of those measured
     4.08-4.42:1 and failed. A naive fg/bg audit cannot see them because the
     background is translucent rather than a palette variable, so the test
     composites alpha.
  3. .stat-orange measured 2.85:1 on --surface, failing even the 3:1
     large-text bar.

Removed the drift class behind (2) and (3) rather than fixing instances: five
hand-copied hex literals of the accent lived OUTSIDE the CSS -- in stats.rs,
source.rs, doc_linkage.rs and serve/layout.rs -- still painting the old blue
after the palette moved. styles::ACCENT_HEX is now the source of truth, one
test asserts the CSS agrees with it, and a static scan fails if the retired
value reappears anywhere.

The scan's first version listed only render/ modules and missed
serve/layout.rs, which was the one file still painting the retired tint under
new-accent text. Widened, and the scan is negative-controlled: injecting
#3a86ff into stats.rs turns it red and names the file; removing it turns it
green.

Verified in rendered output rather than in source alone: `rivet export --format
html` and `rivet serve` both emit --accent #2059b8 with zero retired
references. (One match remains in artifacts/REQ-276.html, where the
requirement's own prose quotes the old hex.)

Confirmed with cargo fmt --check, clippy --all-targets -D warnings on 1.97.0,
cargo test --workspace (exit 0, 65 ok), rivet validate and rivet docs check --
all exit 0.

Implements: REQ-276
Verifies: REQ-276
…e (REQ-274)

Customer-reported: deep chains (the ASPICE sw-req <- sw-detail-design <-
unit-verification shape) rendered as a flat table. REQ-001 produces 40 hops of
undifferentiated rows.

The data was already a tree and nobody was using it. trace_test_results walks
breadth-first with a `seen` set, so every reached artifact has exactly one
via_target -- it returns that tree flattened. rivet_core::result_trace::as_tree
restores the shape; rendering goes through the existing collapsible_tree
component, so this is a rendering change over an existing model plus an
existing component.

Native <details> means no JavaScript, so the same markup serves `rivet serve`
AND the static export. Verified in both: REQ-001 renders 3 foldable branches
plus 37 leaf lines, identically, with Expand/Collapse All as progressive
enhancement.

The load-bearing invariant is that a folded view must contain every hop --
a branch that silently stops rendering is indistinguishable from one that is
merely collapsed. tree_preserves_every_node pins it including the orphan case
(a node whose parent was never reached is re-homed to top level, not dropped),
and it is negative-controlled: disabling the re-homing turns it red with
"tree dropped nodes: 2 in, 1 out".

Two presentation corrections after looking at real output rather than a
synthetic case. Leaves render as plain lines instead of <details>, because a
disclosure triangle over "no further hops" is noise and the first version was
no more readable than the table it replaced. And a large trace now opens
nothing by default -- 33 expanded siblings is the same wall -- while a small
one still opens its first level, because folding three hops helps nobody.

The existing serve_integration test asserted the old Hops/Via table and
legitimately broke. Rewritten to assert the tree. Note that my first rewrite
compared the rendered hop count against /api/v1/trace-results, guarded on a
non-empty response -- that route does not exist, so it 404'd, the guard
swallowed it, and the assertion never ran while reporting green. Removed; exact
parity is asserted in a unit test against a synthetic trace where the expected
count is known.

Confirmed with cargo fmt --check, clippy --all-targets -D warnings on 1.97.0,
cargo test --workspace (exit 0, 65 ok), rivet validate, rivet docs check --
all exit 0.

Implements: REQ-274
Verifies: REQ-274
@github-actions

github-actions Bot commented Aug 21, 2026

Copy link
Copy Markdown

📐 Rivet artifact delta

Change Count
Added 0
Removed 0
Modified 1
Downstream impacted (depth ≤ 5) 0

Graph

graph LR
  REQ_274["REQ-274"]:::modified
  classDef added fill:#d4edda,stroke:#28a745,color:#155724
  classDef removed fill:#f8d7da,stroke:#dc3545,color:#721c24
  classDef modified fill:#fff3cd,stroke:#ffc107,color:#856404
  classDef overflow fill:#e2e3e5,stroke:#6c757d,color:#495057,stroke-dasharray: 3 3
Loading
Modified
ID Changes
REQ-274

📎 Full HTML dashboard attached as workflow artifact rivet-delta-pr-843download from the workflow run.

Posted by rivet-delta workflow. The graph shows only changed artifacts; open the HTML dashboard (above) for full context.

@codecov

codecov Bot commented Aug 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.56115% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
rivet-core/src/result_trace.rs 98.56% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Rivet Criterion Benchmarks'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.20.

Benchmark suite Current: 4f6d189 Previous: 6ab55ac Ratio
store_insert/100 84757 ns/iter (± 798) 67629 ns/iter (± 2912) 1.25
store_insert/1000 910221 ns/iter (± 5062) 738715 ns/iter (± 2984) 1.23
store_insert/10000 17322224 ns/iter (± 1049564) 13024659 ns/iter (± 1380431) 1.33
store_lookup/100 1928 ns/iter (± 12) 1473 ns/iter (± 65) 1.31
store_lookup/1000 24301 ns/iter (± 45) 17970 ns/iter (± 661) 1.35
store_lookup/10000 362680 ns/iter (± 2077) 251833 ns/iter (± 12449) 1.44
store_by_type/100 96 ns/iter (± 0) 74 ns/iter (± 0) 1.30
store_by_type/1000 96 ns/iter (± 0) 74 ns/iter (± 0) 1.30
store_by_type/10000 96 ns/iter (± 0) 74 ns/iter (± 0) 1.30
schema_load_and_merge 1512034 ns/iter (± 16320) 1167816 ns/iter (± 43304) 1.29
link_graph_build/100 167020 ns/iter (± 5801) 126478 ns/iter (± 885) 1.32
link_graph_build/1000 2007772 ns/iter (± 10033) 1486732 ns/iter (± 29512) 1.35
link_graph_build/10000 38408486 ns/iter (± 1090795) 30560062 ns/iter (± 1587461) 1.26
validate/100 475624 ns/iter (± 2017) 356229 ns/iter (± 29454) 1.34
validate/1000 15950912 ns/iter (± 165206) 11595257 ns/iter (± 127171) 1.38
validate/10000 1157651527 ns/iter (± 15451059) 828365146 ns/iter (± 8650155) 1.40
traceability_matrix/100 4325 ns/iter (± 20) 3283 ns/iter (± 44) 1.32
traceability_matrix/1000 46784 ns/iter (± 168) 35250 ns/iter (± 188) 1.33
traceability_matrix/10000 844872 ns/iter (± 6593) 566762 ns/iter (± 9543) 1.49
diff/100 63982 ns/iter (± 321) 47909 ns/iter (± 126) 1.34
diff/1000 727938 ns/iter (± 10941) 529057 ns/iter (± 2786) 1.38
diff/10000 9081784 ns/iter (± 497615) 6255974 ns/iter (± 250553) 1.45
query/100 1157 ns/iter (± 2) 859 ns/iter (± 2) 1.35
query/1000 15019 ns/iter (± 119) 11226 ns/iter (± 24) 1.34
query/10000 225336 ns/iter (± 1257) 184013 ns/iter (± 892) 1.22
document_parse/10 21443 ns/iter (± 146) 16510 ns/iter (± 33) 1.30
document_parse/100 146352 ns/iter (± 357) 112486 ns/iter (± 954) 1.30
document_parse/1000 1367787 ns/iter (± 20821) 1042829 ns/iter (± 6111) 1.31

This comment was automatically generated by workflow using github-action-benchmark.

…ping nodes

The rivet-core mutation gate flagged 4 survivors in as_tree, and acting on them
found a real defect rather than just a test gap.

The `depth > 64` guard survived mutation to both `==` and `>=`, and `depth + 1`
survived mutation to `*`, meaning nothing exercised the guard at all. Writing
that test showed why it mattered: past depth 64 the guard returned an empty Vec,
silently dropping the entire subtree. That is precisely the node loss
tree_preserves_every_node exists to forbid — a rendered tree smaller than the
trace it claims to show — and it was invisible because the only inputs anyone
tested were shallow.

Replaced the depth cap with a current-path visited set. Cycles still terminate
(a genuine revisit is skipped) but a legitimately deep chain renders in full;
a 200-deep chain now nests to 200 and keeps all 200 nodes.

Also generalised the preservation sweep. It previously re-homed only nodes whose
parent was unknown; a set of nodes cycling among themselves with no path from
the root still vanished. It now sweeps for any input node absent from the tree,
so "nothing is lost" holds by construction rather than by special case.

Three tests added: a 200-deep chain keeps every node and nests to full depth, a
cycle terminates and keeps both nodes, and is_empty is pinned so it cannot
become `true` and make a populated subtree read as empty.

Confirmed with cargo fmt --check, clippy --all-targets -D warnings on 1.97.0,
cargo test --workspace (exit 0, 65 ok), rivet validate and rivet docs check --
all exit 0. result_trace unit tests 11/11.

Verifies: REQ-274
@avrabe
avrabe merged commit 36f2cd7 into main Aug 25, 2026
33 of 34 checks passed
@avrabe
avrabe deleted the feat/req-274-collapsible-trace-tree branch August 25, 2026 23:12
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