Skip to content

feat(app): Wave 2a Cluster C — a11y and SEO#242

Merged
rz1989s merged 6 commits into
mainfrom
feat/a11y-seo
May 11, 2026
Merged

feat(app): Wave 2a Cluster C — a11y and SEO#242
rz1989s merged 6 commits into
mainfrom
feat/a11y-seo

Conversation

@rz1989s
Copy link
Copy Markdown
Member

@rz1989s rz1989s commented May 11, 2026

Summary

Wave 2a Cluster C — Accessibility and SEO improvements. Adds per-route SEO meta tags via React 19 native metadata, a loading skeleton to Privacy Graph, proper role + aria-live semantics to Toast, and an aria-label + maxLength to ChatSidebar input.

Issues closed

Closes #206
Closes #210
Closes #211
Closes #214

Changes

  • [P2] Add <h1> + Open Graph meta tags for SEO + social previews #206 Per-route SEO + Open Graph meta tags — Inline <title> + <meta name="description"> + <meta property="og:title"> + <meta property="og:description"> in 13 views (Dashboard, Vault, Chains, Keys, About, Herald, Squad, Settings, PrivacyReport, Deposit, Withdraw, Chat, NotFound). React 19 auto-hoists to <head>. Per-view seoTags const pattern for views with auth-gated early returns (7 views); inline for views without branches (6 views). Title pattern: SIPHER — <View> (Index = SIPHER — Multi-chain privacy command center).
  • [P2] Privacy Graph has no visual loading state #210 Privacy Graph loading state skeleton<PrivacyGraph> gains internal loading state (initialized to Boolean(token)). Skeleton renders during loading with data-testid="privacy-graph-skeleton", aria-busy="true", aria-label="Loading privacy graph". Empty state (data-testid="privacy-graph-empty") renders when loaded && nodes.length === 0. onAuthClear clears loading alongside tree.
  • [P2] Audit toast role + aria-live semantics #211 Toast role + aria-live semanticskind: 'info' | 'success'role="status" + aria-live="polite". kind: 'warn' | 'error'role="alert" + aria-live="assertive". ToastProvider warn-styles test updated accordingly. Audited other role="status" callers (Banner, BetaBanner, TxStatusBadge, CooldownChip) — none are toasts, unaffected.
  • [P2] ChatSidebar input missing aria-label + maxLength: -1 #214 ChatSidebar input accessibility<input> gains aria-label="Ask SIPHER" (screen reader accessible name) and maxLength={4000} (defensive client cap).

Code-review fixes (2 follow-up commits)

  • 858347f — og:image path dropped (Path A) — Original code-quality review flagged the /icons/sipher.svg og:image path as broken (no asset exists in repo; SPA fallback serves index.html → social-card crawlers get HTML when they expect an image). Chose Path A: drop og:image + og:type from all 13 views. Social cards still work via title+description. Asset can be reintroduced in a single follow-up commit when the brand mark stabilizes. No test changes required (existing SEO tests only asserted og:title/og:description).
  • bbfdf24 — PrivacyGraph catch abort guard — Original code-quality review flagged inconsistency between .then (correctly guarded on controller.signal.aborted) and .catch (only filtered AbortError.name). Added if (controller.signal.aborted) return at top of catch handler. New TDD test probes the signal.aborted getter via Object.defineProperty to verify guard fires (red→green proven by removing the guard line: test fails with expected count not reached).

Tests

  • App tests: 488 passed (was 476; +12 net: 3 SEO + 2 PrivacyGraph + 5 Toast + 1 ChatSidebar + 1 PrivacyGraph catch guard)
  • TSC: clean (`pnpm exec tsc --noEmit` exit 0)
  • 75 test files (was 74; +1 new `Toast.test.tsx`)

Spec + reviews

  • Spec: docs/superpowers/specs/2026-05-11-qa-sweep-tier-4-design.md → Wave 2 appendix → Cluster C
  • Plan: docs/superpowers/plans/2026-05-11-qa-sweep-tier-4-wave-2a.md → Cluster C section
  • Spec-compliance review: ✅ APPROVED (D-C1 through D-C4 met)
  • Code-quality review: 🔴 INITIALLY BLOCKED (1 Critical og:image, 1 Important PrivacyGraph catch) → 🟢 RESOLVED via fix-loop (2 new commits above)
  • 10 Minor follow-ups to file as `tech-debt,priority:low` (SEO duplication, missing og:url/og:site_name, missing image dimensions, index.html static title, test fragility in document.head cleanup, etc.)

rz1989s added 6 commits May 11, 2026 10:15
All 13 view components referenced og:image="/icons/sipher.svg", but no such
asset exists in the repo (app/public/ directory doesn't exist; no SVGs in
tree). Social-card preview crawlers (Slack, Discord, Twitter, Facebook) would
hit the SPA history-API fallback and receive index.html when expecting an
image, breaking the og:image investment entirely.

Path chosen: drop og:image + og:type. Rationale: cleanest fix with no asset
dependency, no maintenance burden, no future drift between asset and code.
Social cards still work via og:title + og:description (which remain). When
the brand mark stabilizes, og:image + og:type can be reintroduced as a
single follow-up commit with the asset committed to app/public/icons/.

Views updated (13): About, Chains, Chat, Dashboard, Deposit, Herald, Keys,
NotFound, PrivacyReport, Settings, Squad, Vault, Withdraw.

Existing SEO tests (VaultView, DashboardView) only assert og:title and
og:description content; no test changes required.
The .then branch correctly guards on controller.signal.aborted, but the
.catch branch only filtered on err.name === 'AbortError'. If a network
error fires concurrent with unmount (race after controller.abort() but
before the .catch handler runs), the catch body would execute setTree([])
and setLoading(false) on an unmounted component. React 19 silently
discards these, but the pattern was inconsistent within the same effect.

The new first line mirrors the .then branch:
  if (controller.signal.aborted) return

Verified red-green TDD: new test fails (0 reads of signal.aborted in catch
path) without the guard, passes (>= 1 read) with the guard. Test probes the
guard predicate by replacing the signal's `aborted` getter with a counter
before the deferred rejection fires post-unmount.

Tests: 488 (was 487, +1 new) across 75 files. Typecheck clean.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
sipher Ready Ready Preview, Comment May 11, 2026 3:39am

@rz1989s rz1989s merged commit ed74653 into main May 11, 2026
8 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

1 participant