feat(app): Wave 2a Cluster C — a11y and SEO#242
Merged
Merged
Conversation
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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
<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-viewseoTagsconst 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).<PrivacyGraph>gains internalloadingstate (initialized toBoolean(token)). Skeleton renders during loading withdata-testid="privacy-graph-skeleton",aria-busy="true",aria-label="Loading privacy graph". Empty state (data-testid="privacy-graph-empty") renders whenloaded && nodes.length === 0.onAuthClearclears loading alongside tree.role+aria-livesemantics #211 Toast role + aria-live semantics —kind: 'info' | 'success'→role="status"+aria-live="polite".kind: 'warn' | 'error'→role="alert"+aria-live="assertive". ToastProvider warn-styles test updated accordingly. Audited otherrole="status"callers (Banner, BetaBanner, TxStatusBadge, CooldownChip) — none are toasts, unaffected.aria-label+maxLength: -1#214 ChatSidebar input accessibility —<input>gainsaria-label="Ask SIPHER"(screen reader accessible name) andmaxLength={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.svgog: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: dropog:image+og:typefrom 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 oncontroller.signal.aborted) and.catch(only filteredAbortError.name). Addedif (controller.signal.aborted) returnat top of catch handler. New TDD test probes thesignal.abortedgetter viaObject.definePropertyto verify guard fires (red→green proven by removing the guard line: test fails with expected count not reached).Tests
Spec + reviews