Skip to content

fix(ui-ux): expose bare loading text as live regions - #558

Merged
seonghobae merged 5 commits into
mainfrom
fix/uiux-accessibility-loading-status-live-regions
Aug 24, 2026
Merged

fix(ui-ux): expose bare loading text as live regions#558
seonghobae merged 5 commits into
mainfrom
fix/uiux-accessibility-loading-status-live-regions

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Gap

Async loading/status text was rendered as plain <p> text with no role/aria-live, in ~20 places across frontend/src/App.tsx and frontend/src/components/FiveW1H.tsx (this worktree's snapshot has a different line count than the original gap report, but the same underlying pattern). Assistive-tech users are never told when a Loading… placeholder is replaced by real content.

Highest-traffic instances:

  • App.tsx — the app-root auth gate (if (auth.isLoading) return <p>{t("Loading authentication state...")}</p>) — every session passes through this before anything else renders.
  • App.tsx — the post-detail popup's {!post && !error && <p>{t("Loading...")}</p>} — focus lands in the popup, but the loading→title transition is silent to AT.
  • App.tsxEventLineageSection's two Loading lineage... returns, plus 15 more bare loading paragraphs across related panels (evidence, related nodes, evaluation, tickets, activity, affiliate tree, analysis runs, rankings, calendar, reports, customer master, related posts).
  • FiveW1H.tsxLoading 5W1H....

This contrasts with the existing correct pattern already used a few components over, e.g. App.tsx's <p role="status">{t("Loading posts...")}</p> for the board list.

Fix

Added role="status" to every bare loading <p> identified (19 in App.tsx, 1 in FiveW1H.tsx), matching the existing role="status" convention used elsewhere in the same files. These are plain transient text nodes with no retry action or extra description, so a raw role="status" attribute is the smallest correct fix — no need to wrap them in a richer status component. No behavior change beyond the attribute; no new user-facing strings, so no new i18n keys needed.

Tests

  • Added it("announces the app-root auth loading gate as a live region", ...) — sets mockAuth.isLoading = true and asserts screen.getByRole("status") carries the Loading authentication state... text (previously only reachable via getByText).
  • Added it("announces the post-detail popup loading state as a live region before the post resolves", ...) — extends the existing stubBackend helper with a deferPostOne option (mirroring the existing deferMe pattern) to deterministically observe the popup's pre-resolution state, then asserts within(popup).getByRole("status") before asserting the loaded content appears and the loading text is gone.

Checks run (from frontend/)

  • corepack pnpm exec tsc -b — same 2 pre-existing errors as on the unmodified base commit (App.tsx:104 unused import, App.tsx:4623 AdminPanel prop type), confirmed unrelated by diffing against HEAD before these edits. No new errors.
  • corepack pnpm exec oxlint src/App.tsx src/App.test.tsx src/components/FiveW1H.tsx — same 2 pre-existing warnings (same unused import), no new warnings/errors.
  • corepack pnpm exec vitest run src/App.test.tsx80 passed (80), including the 2 new tests.

🤖 Generated with Claude Code

https://claude.ai/code/session_011jWJzKUd82yy97esEBfJbt


Open in Devin Review

Async loading placeholders in App.tsx and FiveW1H.tsx rendered as
plain <p> text with no role/aria-live, so assistive tech never
announced the transition from "Loading..." to the resolved content.
This included the app-root auth gate every session passes through,
and the post-detail popup's loading paragraph.

Add role="status" to every bare loading <p> (19 in App.tsx, 1 in
FiveW1H.tsx), matching the role="status" pattern already used
elsewhere in the same files (e.g. "Loading posts...",
SourceResearchPanel). No behavior change beyond the attribute.

Tests: add an RTL assertion that the auth-loading gate and the
post-detail popup's loading state are exposed via getByRole("status")
rather than only getByText, extending stubBackend with a
deferPostOne option to deterministically observe the popup's
pre-resolution state.

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 53 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: f66b793a-c87a-4ae9-a19b-4e7cc4c1be73

📥 Commits

Reviewing files that changed from the base of the PR and between b4911f8 and bf79ec4.

📒 Files selected for processing (3)
  • frontend/src/App.test.tsx
  • frontend/src/App.tsx
  • frontend/src/components/FiveW1H.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.

@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: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@seonghobae
seonghobae enabled auto-merge August 24, 2026 01:26
@opencode-agent
opencode-agent Bot disabled auto-merge August 24, 2026 04:40
- The unauthenticated Log in handler now calls returnUrlFromLocation()
  then rememberOidcReturnUrl() before signinRedirect, per ADR 0109, so a
  shared /?post= link still opens that post after enterprise SSO. The
  previously unused oidcReturnUrl import is now load-bearing.
- AdminPanel renders only when accessToken is a string; the OIDC access
  token is string | undefined before isAuthenticated narrowing.
devin-ai-integration[bot]

This comment was marked as resolved.

…egions

# Conflicts:
#	frontend/src/App.test.tsx
#	frontend/src/App.tsx
@seonghobae
seonghobae enabled auto-merge August 24, 2026 05:55
@seonghobae
seonghobae merged commit a568a34 into main Aug 24, 2026
29 of 30 checks passed
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