Skip to content

fix(ui-ux): make analysis-run digest verification keyboard/touch accessible - #557

Closed
seonghobae wants to merge 1 commit into
mainfrom
fix/uiux-touch_interaction-digest-disclosure
Closed

fix(ui-ux): make analysis-run digest verification keyboard/touch accessible#557
seonghobae wants to merge 1 commit into
mainfrom
fix/uiux-touch_interaction-digest-disclosure

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Gap

Dimension: touch_interaction
Severity: medium

The Analysis-run reproducibility digest (Code/Config/Result SHA) shown in the
analysis-run detail popup exposed its full-precision hash only through the
title attribute of a bare <span>:

<span className="visually-hidden">Hover a prefix to read the full digest for verification. </span>
...
<span title={part.digest}>{`${part.label} ${analysisRunDigestPrefix(part.digest)}`}</span>

That <span> carries no tabIndex, role, or onClick, so the full digest
was reachable only via mouse hover -- a WCAG 2.1.1 (keyboard) failure with no
touch equivalent. App.test.tsx previously asserted the full digest was
retrievable only via screen.getByTitle(...), confirming there was no
other path to it.

Severity is medium, not high: the truncated prefix stays visible and
screen-reader-audible to everyone, so no one is blocked from the core
analysis-run-detail workflow -- only from copying/comparing the full digest
without a mouse.

Fix

Swapped the bare <span title=...> for the same native <details>/<summary>
disclosure pattern this codebase already uses elsewhere for "compact summary,
full detail on demand" (e.g. the Evidence operations / Related posts
disclosures in the same file):

<details className="analysis-run-digest">
  <summary>{`${part.label} ${analysisRunDigestPrefix(part.digest)}`}</summary>
  {part.digest}
</details>

<details>/<summary> is natively focusable and toggled by Enter/Space/
click/tap with zero JS, so this closes the keyboard and touch gap without a
new button+state pattern.

Two small adaptations:

  • The wrapping element changed from <p className="post-meta"> to
    <div className="post-meta">, since <details> is flow content, not
    phrasing content, and is not valid inside <p>.
  • Minor CSS (.analysis-run-digest) keeps the disclosures reading inline
    within the ·-separated post-meta line -- the other <details> usages in
    this file are block-level list items, so this is the one visual adaptation
    needed.
  • The visually-hidden instruction text changed from "Hover a prefix..." to
    "Open a prefix..." to match the new interaction.

Tests

Updated the two existing tests that exercised this component
(App.test.tsx) to replace the getByTitle-only assertions with assertions
that:

  • the disclosure starts closed (not.toHaveAttribute("open")),
  • the <summary> carries no tabIndex/role (proving it relies on native
    semantics, not a hand-rolled focus hack),
  • clicking (equivalent to tap/keyboard-triggered activation) opens the
    disclosure and reveals the full digest text.

These tests fail against the pre-fix <span title> markup (no <details> to
query, getByTitle assertions gone) and pass after the fix.

Checks

From frontend/:

  • corepack pnpm exec tsc -b -- 2 pre-existing errors, both unrelated to
    this change and present identically on main (unused oidcReturnUrl
    import, AdminPanel accessToken optional-vs-required type) -- verified via
    git stash before/after comparison.
  • corepack pnpm exec oxlint src/App.tsx src/App.test.tsx src/App.css -- exit
    0; only the same 2 pre-existing unused-import warnings noted above.
  • corepack pnpm exec vitest run src/App.test.tsx -- 78 passed (78).
  • corepack pnpm exec vitest run src/i18n.test.ts -- 18 passed (18)
    (sanity check; i18n.ts itself is untouched).

Scope note

This region of App.tsx (analysis-run detail: labels like "Code"/"Config"/
"Result", "Cutoff"/"Requested", etc.) is not yet wrapped in t() anywhere,
including in the sibling helper functions right next to this component. To
match the existing local convention and avoid unrelated scope creep, the
changed/added instructional text follows that same (currently un-i18n'd)
convention rather than introducing a lone t()-wrapped string in an
otherwise plain-English panel.

🤖 Generated with Claude Code

https://claude.ai/code/session_011jWJzKUd82yy97esEBfJbt


Open in Devin Review

…ssible

The reproducibility digest (Code/Config/Result SHA) in the analysis-run
detail popup exposed its full hash only via the `title` attribute on a
bare `<span>`, which has no keyboard or touch equivalent -- a WCAG 2.1.1
failure with no way to copy/compare the full digest without a mouse.

Swap the `<span title=...>` for the same native `<details>/<summary>`
disclosure pattern already used elsewhere in App.tsx for "compact
summary, full detail on demand". `<details>/<summary>` is natively
focusable and toggled by Enter/Space/click/tap with zero JS. The
wrapping `<p>` becomes a `<div>` since `<details>` is not valid phrasing
content inside `<p>`; minor CSS keeps the disclosures reading inline in
the '·'-separated post-meta line (the other `<details>` usages in this
file are block-level list items).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011jWJzKUd82yy97esEBfJbt
@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 4 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cf7d43c2-5804-4b6e-b1c3-ab7b6fb5bb49

📥 Commits

Reviewing files that changed from the base of the PR and between ef6f5a5 and ee3ce87.

📒 Files selected for processing (3)
  • frontend/src/App.css
  • frontend/src/App.test.tsx
  • frontend/src/App.tsx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@seonghobae
seonghobae enabled auto-merge August 24, 2026 01:23

@devin-ai-integration devin-ai-integration 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.

Devin Review found 2 potential issues.

Open in Devin Review

Comment thread frontend/src/App.tsx
Comment on lines 2614 to 2620
<span key={part.label}>
{index > 0 ? " · " : null}
<span title={part.digest}>{`${part.label} ${analysisRunDigestPrefix(part.digest)}`}</span>
<details className="analysis-run-digest">
<summary>{`${part.label} ${analysisRunDigestPrefix(part.digest)}`}</summary>
{part.digest}
</details>
</span>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Disclosure nested inside inline span

Each <details> disclosure sits directly inside a <span>, but a span accepts only phrasing content while <details> is flow content, so the markup is invalid. This is the same validity problem that prompted changing the wrapping element from <p> to <div>, left unfixed for the inner span.

Suggested change
<span key={part.label}>
{index > 0 ? " · " : null}
<span title={part.digest}>{`${part.label} ${analysisRunDigestPrefix(part.digest)}`}</span>
<details className="analysis-run-digest">
<summary>{`${part.label} ${analysisRunDigestPrefix(part.digest)}`}</summary>
{part.digest}
</details>
</span>
<Fragment key={part.label}>
{index > 0 ? " · " : null}
<details className="analysis-run-digest">
<summary>{`${part.label} ${analysisRunDigestPrefix(part.digest)}`}</summary>
{part.digest}
</details>
</Fragment>
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread frontend/src/App.tsx
Comment on lines +2616 to +2619
<details className="analysis-run-digest">
<summary>{`${part.label} ${analysisRunDigestPrefix(part.digest)}`}</summary>
{part.digest}
</details>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Full digest now always present in DOM text

The full digest was previously only in the title attribute; it is now text inside the closed <details>, so it is always in textContent. The component is only mounted in the detail popup (AnalysisRunReproducibilityDigests at frontend/src/App.tsx:2913), so the home-list negative assertion still holds. Updated tests click to reveal it.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@seonghobae

Copy link
Copy Markdown
Contributor Author

Closing as out of lane for LineageWeave. Analysis-run digest chrome stays off the unique GNB-four slice (#258). Accessibility work attaches to that unique head, not a second analysis-run surface. Do not restack onto #258.

@seonghobae seonghobae closed this Aug 24, 2026
auto-merge was automatically disabled August 24, 2026 02:13

Pull request was closed

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