feat: complete DarkModeSwitcher component with validations, a11y, interactive states, and stories - #367
Merged
godamongstmen897 merged 264 commits intoSep 1, 2026
Conversation
…onSignerPanel - Move state declarations before closeModal callback (WalletSelectorModal) - Remove eslint-disable and add missing hook dependency (TransactionSignerPanel)
…addresses Add state serialization to rabe_connector so active wallet addresses survive page reload cycles. New exports: - RABE_CACHE_KEY: localStorage key for the cache entry - RabeActiveAddressCache: versioned interface (version, address, savedAt, network) - validateRabeAddressCache: strict runtime validation (address format, timestamp, network) - serializeRabeAddressCache / deserializeRabeAddressCache: JSON round-trip with validation on read-back; returns null on corrupt data - saveRabeAddressCache: writes to localStorage; SSR-safe; logs warning on QuotaExceededError via existing logRabeWarning - loadRabeAddressCache: reads + validates from localStorage; SSR-safe - clearRabeAddressCache: removes entry on disconnect; no-op if absent Tests: 33 new cases covering validation, serialization round-trips, and all localStorage integration scenarios (save/load/clear, overwrite, corrupt data, storage errors, timestamp bounds). All 376 existing tests continue to pass. TypeScript: 0 errors. Closes Goldii-locks#137
Implements the app dark/light theme toggle as a keyboard-operable, ARIA-compliant switch (role=switch, aria-checked, aria-label) that persists the chosen theme to localStorage and applies it to the document root. Adds React Testing Library tests verifying node rendering, accessible state, theme application, keyboard operation and persistence. Closes Goldii-locks#319
Implements the navbar alert bell badge as a keyboard-operable (role=button) disclosure with ARIA compliance (Goldii-locks#320): accessible name, aria-haspopup/aria-expanded/aria-controls, aria-live announcement regions, aria-hidden on decorative glyphs, focus-visible rings and design-token contrast. Adds field error indicators and alerts (Goldii-locks#324): validation field configs render role=alert error text that toggles as validation triggers, wired via aria-describedby and counted toward the unread badge. Adds React Testing Library tests covering both requirements. Closes Goldii-locks#320 Closes Goldii-locks#324
Stack the stat grid and content rows to a single column on mobile, two on tablet, three on desktop, and use responsive padding/spacing so LoadingSkeleton scales cleanly at every breakpoint. Closes Goldii-locks#275
Fade the skeleton in on mount (animate-fade-in) instead of popping in abruptly, and stagger each placeholder bar's pulse animation-delay so the loading state reads as a smooth wave rather than a flat blink. Closes Goldii-locks#278
Replace the bare "No jobs found" line with a reusable EmptyState component (icon, heading, supporting copy) shown once loading finishes with zero jobs for the connected wallet. Closes Goldii-locks#276
…e modal The branch predates Goldii-locks#375's rewrite of DisputeRaiseModal, and its own edit to that file is broken independently of the merge: it deletes the counterparty <dd>'s closing tags and splices a stray label/textarea fragment in their place, so the branch does not parse. The merge inherited that. The lib half is sound and is the real contribution, so it was kept and the component rebuilt around it: the panel becomes a plain flex column, DISPUTE_MODAL_CLASSES.overlayWrapper takes the padding and the height cap, and a scrollableContent region wraps everything between the header and the actions -- so on a short viewport the middle scrolls while the title and buttons stay pinned. Two assertions in the responsive suite described the old arrangement and now follow it: the p-4/sm:p-6/lg:p-8 checks moved from the panel to the wrapper, and the panel's overflow-y-auto check became a query for the scrollable region inside it. The branch's own "keeps action buttons clickable" case also assumed the confirm button is live with an empty reason, which Goldii-locks#375 changed -- it now fills the field first, which is what a user reaching those buttons would have done anyway. lint 0 errors / tsc 0 errors / 2098 tests passing / build OK
…spute-modal-mobile-overlay-wrappers Goldii-locks#336 Handle mobile viewports navigation styling in dispute_raise_modal
Two Navbar conflicts: this branch swaps the inline provider select and Connect button for a <WalletSelectorModal>. That is incidental to the PR -- its subject is TransactionSignerPanel (Goldii-locks#222) and none of its tests touch the Navbar -- and main's navbar-wallet-kit suite queries the select by label and the button by name, so main's Navbar was kept. TransactionSignerPanel then failed lint: it parsed the XDR in a mount effect and wrote the result to three pieces of state, which react-hooks/set-state-in-effect flags as cascading renders. Parsing is a pure function of the XDR and the wallet/network in play, so it now derives during render through useMemo. The signing lifecycle is still real state (idle -> signing -> success/error), so it stays in useState as `signingState`, with a parse failure outranking it: const state = parseErrorMessage ? "parse-error" : signingState; Rendering is unchanged -- `state`, `structure` and `parseErrorMessage` mean exactly what they did before. lint 0 errors / tsc 0 errors / 2140 tests passing / build OK
…on-signer-panel Feat(wallet): build TransactionSignerPanel component (Goldii-locks#222)
…disconnect_handler (Goldii-locks#235) - Add WalletDisconnectUserRejectedError class for user rejection scenarios - Add isWalletDisconnectUserRejected() function to detect rejection errors - Modify disconnectWalletWithCheck to handle rejections with toast notifications - Return success=true when user rejects (intentional cancellation) - Remove active key from persistent store even on rejection - Add comprehensive test cases for rejection handling (23 new tests) - Pattern mirrors albedo_connector and freighter_connector for consistency
…i-locks#273) - Add aria-busy to advertise the in-progress loading state - Add motion-reduce variants to disable pulse/fade animations - Preserve role=status, aria-live, sr-only message, and keyboard support - Add tests covering ARIA semantics, reduced motion, and keyboard nav
…oldii-locks#277) - Add error and validationErrors props that render accessible role=alert messages with aria-live=assertive - Link alerts to the skeleton via aria-describedby - Support both field-keyed record and array-of-errors forms - Apply high-contrast error styling (text-red-400 on bg-red-950) - Add tests covering validation message rendering and toggling
- Add DashboardList.stories.tsx covering loading, error, empty, single/multiple jobs, expanded panels, role badges, and large milestone counts
…#272) - Cover loading, error, empty, and disconnected-wallet states - Verify job rendering, ID prefix, funded status, role badges, and search/filter - Assert ARIA attributes for expand/collapse, filters, and accessibility
The rabe_connector conflict was structural, not semantic: this branch's addition is a single append-only hunk at the end of the file (156 lines, zero deletions), but main has grown past that point, so git aligned the two additions and split an existing function in half. Rebuilt as main's file plus the branch's block appended -- the persistent address cache lands whole. Running the suite then exposed a real defect in dashboard-accessibility.test.tsx (from Goldii-locks#306): five waitFor calls that are never awaited. Three are nested inside an outer waitFor, so the click replays on every retry and the inner assertions never gate the test; one of them leaked a rejection that made vitest exit 1 while reporting all tests passed. Two more sit in synchronous tests, where nothing inside them ever ran. With the assertions actually running, two failed for a second reason: the dashboard auto-expands the first job once the fetch resolves, so an unconditional click closed the very panel they were looking for. They now click only while the row is still collapsed, and go through fireEvent so React flushes the update inside act(). lint 0 errors / tsc 0 errors / 2173 tests passing / build OK, exit 0
…e-connector-persistent-cache feat(rabe_connector): implement secure persistent caching for active addresses
…-micro-animations-and-tests fixed ci test
…eleton-a11y feat: ensure a11y ARIA compliance for loading_spinner_skeleton (Goldii-locks#273)
…list-stories feat: add Storybook stories for dashboard_list (Goldii-locks#271)
The suite merges cleanly but does not typecheck -- 8 errors, all from
untyped helpers in the new file:
- `mockJobsResponse(jobs = [])` infers `never[]` from the default, so
every call that passes real job objects is rejected. It now takes
`MockJob[]`, derived from mockJob's own return type so the fixture
and its consumers cannot drift apart.
- `mockJob(overrides = {})` and `setupFetch(response)` were implicitly
`any`.
- `let resolveFetch` is assigned inside a Promise executor, which
TypeScript cannot see as definite; it now carries a definite-assignment
annotation and its call signature.
No assertions were changed -- all 67 cases pass as written.
lint 0 errors / tsc 0 errors / 2240 tests passing / build OK
…gnature The branch merges cleanly but fails tsc twice: toastSpy is declared as ReturnType<typeof vi.fn>, which widens to Mock<Procedure | Constructable> and will not satisfy disconnectWalletWithCheck's showToast parameter. Declared the handler signature once and threaded it through both the declaration and vi.fn<...>(), so the spy is checked against the shape it is actually passed as. No assertions changed. lint 0 errors / tsc 0 errors / 2242 tests passing / build OK
…list-tests test: add comprehensive RTL tests for dashboard_list (Goldii-locks#272)
…llet-disconnect-user-rejection-handling feat: gracefully catch user signature rejection exceptions in wallet_…
…eton CI failed with "Parsing error: File appears to be binary" because app/components/LoadingSkeleton.tsx was committed as UTF-16LE -- git records it as a binary blob (Bin 6228 -> 17612 bytes), and eslint will not parse it. Converted back to UTF-8, that file is identical to main's apart from two artefacts of the round-trip: a byte-order mark, and the ellipsis in "Loading job data…" mangled to "ΓǪ" (its UTF-8 bytes re-read as a single-byte codepage). The validation support the PR describes is already on main, so the component carried no change of its own -- only the damage. Took main's copy. The new suite is the real contribution and passes as written against main's component: 34 cases, all green. Worth checking the editor that produced this -- saving .tsx as UTF-16 will keep breaking CI the same way. lint 0 errors / tsc 0 errors / 2276 tests passing / build OK
…eleton-validation feat: render validation messages and alerts in loading_spinner_skeleton (Goldii-locks#277)
Two independent designs of this component met here. main has a small
bell driven by count/onClick/ariaLabel, rendered by the Navbar and
covered by 23 assertions; this branch brings a 253-line version with a
dropdown, loading and error states, and its own 13 assertions plus
Storybook stories.
Both were kept rather than picking a winner:
- The component is the branch's, extended with count, onClick and
ariaLabel as aliases for unreadCount, onBellClick and the derived
label. Every internal use now reads through a resolved value, so the
two prop shapes cannot diverge.
- The test file is the union of both suites. They sit under distinct
top-level describes ("NotificationBell" and "NotificationBell
Component"), so the only real work was reconciling the import blocks --
the raw conflict had split main's closing braces from its describe.
- Navbar keeps the WalletBadge this branch predates, and its existing
<NotificationBell count={0} /> call site is unchanged.
The branch styled with raw palette classes (bg-indigo-600,
ring-indigo-400, text-gray-300). The repo styles with design tokens and
the existing suite asserts them, so those were switched to
bg-accent, ring-accent-soft, text-text-secondary, hover:bg-surface-field
and border-surface-page. The badge also regained its
"N unread notifications" label and the button its aria-live="polite".
All 45 assertions across both suites pass together.
lint 0 errors / tsc 0 errors / 2298 tests passing / build OK
…tion-bell-storybook-328 feat(storybook): build Storybook interface mocks for notification_bell
Both a DarkModeSwitcher and a dark_mode_switcher already live on main,
and this branch deletes the latter as part of its rename. That delete
was declined: __tests__/dark_mode_switcher.test.tsx still imports it, so
removing it takes a passing suite with it. Both modules stay for now.
For the rest the branch is the more complete side and was taken:
- It adds @storybook/test as a devDependency and uses fn() in the
stories. main's copy carries the comment "@storybook/test not
installed - use no-op" and stubs onToggle with () => {} -- a
workaround this PR is the fix for.
- Its stories test is 106 lines against main's 36.
The stories file and its test have to move together: the merge had
initially kept main's no-op stories alongside the branch's test, which
expects Empty.args.isDarkMode to be null rather than undefined. Taking
the branch's stories settles both.
package-lock.json had 24 conflicting hunks, so it was regenerated from
the merged package.json rather than hand-resolved, and verified with a
clean npm ci.
lint 0 errors / tsc 0 errors / 2304 tests passing / build OK
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.
This PR completes the DarkModeSwitcher component with all required features, resolving all associated open issues.
Features implemented:
Closes #310
Closes #311
Closes #312
Closes #313
Closes #314
Closes #315
Closes #316
Closes #317
Closes #318
Closes #319