Skip to content

Viewer: detail-pane UX parity with deployed ara-hub (#55-#59) - #65

Merged
EYH0602 merged 4 commits into
mainfrom
viewer/detail-pane-ux
Jul 26, 2026
Merged

Viewer: detail-pane UX parity with deployed ara-hub (#55-#59)#65
EYH0602 merged 4 commits into
mainfrom
viewer/detail-pane-ux

Conversation

@EYH0602

@EYH0602 EYH0602 commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

Test plan

  • cargo test -p ara-viewer — 170 passed
  • scripts/embed-viewer.sh --check — embedded bundle up to date
  • Live-verified in browser (trunk build): node id/isolated pill render correctly (synthetic isolated-node test), DEPENDS ON / DEPENDED ON BY chips resolve and jump-select correctly, collapsible blocks expand/collapse with correct counts, and the chip-truncation ellipsis fix renders correctly before/after

EYH0602 added 3 commits July 25, 2026 15:45
- #55: render exhibit captions (Exhibit.description) above exhibit bodies
- #56: mono node id + isolated-subtree pill in the detail header
- #57: DEPENDS ON block with jump chips that select the target node
- #58: collapsible evidence/built-on/deps/result/sources blocks with counts
- #59: ignore global replay arrows while a modal is open or in a textarea

Bump 0.1.12, regenerate embedded viewer bundle, update stage-3-viewer and
hub-parity docs.
`.chip-jump` inherited `display: inline-flex` from the base `.chip` class,
on which `text-overflow: ellipsis` silently has no effect, so long labels
were hard-clipped mid-word. Override to `display: inline-block` and
regenerate the embedded viewer bundle via scripts/embed-viewer.sh.

@fenfenai fenfenai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review Summary

Reviewed the functional diff in crates/ara-viewer/src/detail.rs, crates/ara-viewer/src/replay.rs, and crates/ara-viewer/public/styles.css across bug detection, error handling, type design, test coverage, comment quality, and guidelines-compliance dimensions (parallel specialized reviewers, findings filtered to confidence ≥75 and cross-checked against the actual PR source).

Solid PR overall: the pure detail_model() transform is thoroughly unit-tested (7 new cases covering isolated-subtree inheritance, cycle guards, and depends-on resolution in both directions), docs/CHANGELOG/version bump all follow this repo's CLAUDE.md conventions, and the plans/docs/ workflow was followed correctly. Error handling, comment accuracy, and guidelines compliance reviewers found nothing to report.

One likely UI-state bug and a few smaller design/coverage gaps below.

Important Issues (confidence 75-89)

crates/ara-viewer/src/detail.rs:511 — Collapsed block state may leak across node selection (confidence: 78)

CollapsibleBlock sets <details open=true> as a static (non-reactive) attribute. DetailPane's single top-level move || {...} re-invokes render_detail(model, selected).into_any() on every selected change, but since every Some(node) branch produces the same concrete view type, Leptos's AnyView reconciliation patches the existing <details> elements in place rather than remounting them — static attributes set once at initial build() aren't reapplied on rebuild(). If a user manually collapses e.g. the "evidence" block (native <details> toggle) and then selects a different node, that block stays collapsed for the new node's data with no visual cue it's hidden.
Suggestion: make open reactive per node id (e.g. open=move || ... keyed off m.id), or force a full remount on node change (<Show>/keyed wrapper) so collapse state doesn't persist across node switches.

crates/ara-viewer/src/detail.rs:68-74DepView.known: bool + label: String can drift out of sync (confidence: 78)

known is meant to indicate whether label came from a resolved node vs. a raw-id fallback, but nothing in the type ties the two together — both fields are pub and nothing prevents constructing an inconsistent DepView (e.g. known: true with a raw-id label). Today dep_view() is the only constructor so it's not exploited, but the render layer trusts known alone to decide whether to emit a clickable jump chip.
Suggestion: enum DepTarget { Resolved { id: NodeId, label: String }, Unresolved(NodeId) }, or keep the struct but make fields private behind DepView::resolved()/DepView::unresolved() constructors so known is derived, not independently settable.

crates/ara-viewer/src/detail.rs:93DetailModel.id: String duplicates the NodeId newtype (confidence: 76)

DepView.id (added in this same PR, line 70) correctly uses NodeId, but DetailModel.id is typed String and populated via node.id.as_str().to_string() — discarding the type-level guarantee NodeId already provides, inconsistent with the sibling field added in the same diff.
Suggestion: type as pub id: NodeId and convert to a display string at the Leptos render call site instead of at model-construction time.

Test gap: multi-parent Child links to the same node are untested (confidence: 82)

is_in_isolated_subtree's upward walk picks the first matching Child link via .find() over manifest.links (i.e., insertion/iteration order) when a node has more than one incoming Child link. The viewer deserializes Manifest directly from JSON without routing through ara-core's parse.rs tree-building validation (which would normally enforce single-parent trees), so a hand-edited or malformed manifest reaching the viewer is a real, reachable input — the isolated pill could flip depending on link order.
Suggestion: add a test with two Child links pointing at the same child from an isolated and a non-isolated root, and assert/pin the "first parent wins" contract explicitly (or reject/warn on the ambiguity if that's preferred).

Test gap: duplicate DependsOn links are untested (confidence: 76)

ara-core::parse.rs::dedupe_links normally strips duplicate links at compile time, but the viewer bypasses that path (see above) — a duplicate DependsOn link between the same pair would render as two identical duplicate chips with no dedup in detail_model.
Suggestion: add a test asserting the current (duplicate-preserving) behavior is intentional, or dedupe in detail_model if not.

Positive Observations

  • is_in_isolated_subtree's cycle guard (HashSet-based, matches tree_model's existing guard pattern) and the depends_on/depended_on_by link-direction filtering are correct and well covered by tests.
  • The CSS fix comment on button.chip-jump (why inline-flex breaks text-overflow: ellipsis) is technically accurate and a good "why" comment.
  • docs/hub-parity.md and docs/stage-3-viewer.md updates accurately describe the shipped behavior, not just touched files.
  • Version bump (0.1.11 → 0.1.12) and CHANGELOG.md entries correctly follow this repo's CLAUDE.md conventions, including the plans/docs/ rewrite workflow (plans/viewer-detail-pane-ux.md added then folded into docs within this PR's commit lineage).

Comment thread crates/ara-viewer/src/detail.rs Outdated
Comment thread crates/ara-viewer/src/detail.rs Outdated
Comment thread crates/ara-viewer/src/detail.rs Outdated
Comment thread crates/ara-viewer/src/detail.rs
Comment thread crates/ara-viewer/src/detail.rs
… types

- CollapsibleBlock: force-apply `open` via NodeRef effect keyed on
  `selected` — Leptos patches <details> in place and skips unchanged
  attribute writes, so a user-collapsed block used to stay collapsed
  for the next node (web test: collapsible_block_reopens_on_node_switch)
- DepView: private fields behind resolved()/unresolved() constructors
  so `known` can never drift out of sync with `label`
- DetailModel.id: String -> NodeId (matches DepView.id, keeps the
  newtype guarantee to the render boundary)
- Pin two malformed-manifest contracts with tests: multi-parent Child
  links are first-parent-wins; duplicate DependsOn links render as-is
- Fix web tests querying div.built-on-block/div.result-block — blocks
  are <details> since #58
@EYH0602

EYH0602 commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the thorough review — all five findings addressed in fc91ee7. Replies inline below.

1. Collapsed block state leaking across node selection (detail.rs:511) — Confirmed and fixed, with one correction to the suggested approach: a reactive open=move || { selected.track(); true } does NOT work — Leptos diffs attribute values and skips the DOM write when the value is unchanged (truetrue), even though the user mutated the live DOM via native <details> toggle. Fixed instead with a NodeRef + Effect that watches selected and forcibly calls el.set_open(true) on the patched-in-place element. Covered by a new browser test collapsible_block_reopens_on_node_switch (collapse EVIDENCE on N01 → switch to N02 → assert open re-applied), which fails against the old code and passes now.

2. DepView.known/label drift (detail.rs:68-74) — Fields are now private; construction goes through DepView::resolved(id, label) / DepView::unresolved(id) only, so known can no longer be set independently of how label was derived.

3. DetailModel.id: String duplicating the NodeId newtype (detail.rs:93) — Retyped to pub id: NodeId; the display string conversion moved to the render call site ({m.id.to_string()}).

4. Multi-parent Child links untested — Added isolated_multi_parent_first_child_link_wins: two Child links into the same child from an isolated and a non-isolated root, asserted in both link orders, pinning the "first parent wins" contract explicitly (including its order-dependence).

5. Duplicate DependsOn links untested — Added duplicate_depends_on_links_rendered_as_is, pinning the current duplicate-preserving behavior as intentional: the viewer renders the manifest faithfully rather than silently editing it.

Failing viewer-web-test check — Root cause was in the PR itself: #58 changed the EVIDENCE/BUILT ON/DEPENDS ON/RESULT/SOURCES blocks from <div> to <details>, but the pre-existing browser tests still queried div.built-on-block / div.result-block. Selectors updated to the class-only form; the full 54-test browser suite passes locally (wasm-pack test --headless --chrome), plus clippy, fmt, native tests (172), and scripts/embed-viewer.sh --check (embedded bundle regenerated).

@fenfenai fenfenai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-Review: Comment Resolution

Resolved (5 threads)

  • detail.rs:511 — Collapsed block state leak ✓ Fixed. CollapsibleBlock now takes selected and forces set_open(true) on the live <details> element via a NodeRef + Effect keyed on selected, working around Leptos's in-place patching. Verified by a new wasm_bindgen_test (collapsible_block_reopens_on_node_switch) that collapses a block on N01, switches to N02, and asserts the block re-opened — this directly exercises the failure mode I flagged.
  • detail.rs:68-74DepView.known/label drift ✓ Fixed. Fields are now private; construction goes through DepView::resolved() / DepView::unresolved(), so the invariant is enforced by the type, not just by convention.
  • detail.rs:93DetailModel.id: String ✓ Fixed. Now NodeId, matching DepView.id; converted to a display string only at the render call site (m.id.to_string()).
  • detail.rs:280 (multi-parent Child links) ✓ Fixed. New test isolated_multi_parent_first_child_link_wins pins the "first Child link wins" contract in both link orderings.
  • detail.rs:239 (duplicate DependsOn links) ✓ Fixed. New test duplicate_depends_on_links_rendered_as_is pins the current preserve-duplicates behavior as intentional.

Verification

  • cargo test -p ara-viewer --lib: 172 passed (up from 170; +2 native tests for the two pinned contracts above).
  • Viewer bundle (index.html, hashed .js/.wasm) was correctly regenerated to match the new source.
  • No regressions spotted in the rest of the diff; the tests/web.rs selector fixes (div.built-on-block.built-on-block etc.) are a correct follow-on from blocks becoming <details> in #58, not new scope creep.

All prior findings addressed. No new issues found.

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.

2 participants