Skip to content

feat(Dashboard): add controlled activeSection and defaultSection navigation - #420

Merged
k-deejah merged 1 commit into
Sorokit:mainfrom
jadonamite:feat/dashboard-controlled-section-errorboundary-retry
Jul 29, 2026
Merged

feat(Dashboard): add controlled activeSection and defaultSection navigation#420
k-deejah merged 1 commit into
Sorokit:mainfrom
jadonamite:feat/dashboard-controlled-section-errorboundary-retry

Conversation

@jadonamite

Copy link
Copy Markdown
Contributor

Resolves #407

1 & 2. Controlled and uncontrolled section navigation

Dashboard held the active section in private useState, so a consumer had no way to deep-link into a screen.

  • activeSection?: NavSection + onSectionChange?: (s: NavSection) => void for controlled mode. When activeSection is passed, Dashboard renders it and never overrides it internally — the parent owns the state and updates it from onSectionChange.
  • defaultSection?: NavSection for the uncontrolled initial section (defaults to wallet, preserving today's behaviour).

onSectionChange fires in both modes, so a parent can observe navigation without taking ownership of it. SCREENS[active] ?? SCREENS.wallet keeps an unknown section from rendering blank.

// Controlled: open Transactions from elsewhere in your app
const [section, setSection] = useState<NavSection>("wallet");

<button onClick={() => setSection("transactions")}>View Transactions</button>
<Dashboard activeSection={section} onSectionChange={setSection} />

// Uncontrolled: just start somewhere other than Wallet
<Dashboard defaultSection="soroban" />

3. ErrorBoundary onRetry

New onRetry?: () => void, invoked from reset before the error state clears — so the re-initialisation attempt has already happened by the time children re-mount under the new resetKey. It fires from the default fallback's "Try again" and from a custom fallback's reset callback alike. componentStack is also cleared on reset, which it wasn't before.

main.tsx now wires it up: client creation moved behind a createClient() factory inside a Root component, and the retry handler re-creates the client into state.

One deviation worth flagging. The issue describes the handler as calling initClient(createSorokitClient(...)) again. Neither createSorokitClient nor any initClient call exists in main.tsx on current main — it builds a mock client and passes it to SorokitProvider as a prop. I implemented the equivalent against the code as it stands (re-create the client, feed it back through the prop) rather than inventing an API. If you'd rather Root drive the getClient() singleton instead, that's a small change.

Verification

  • npx vitest run src/screens/Dashboard.test.tsx src/components/ErrorBoundary.test.tsx20 passed.
  • New Dashboard.test.tsx (9 cases): default screen, defaultSection, uncontrolled navigation, onSectionChange in uncontrolled mode, controlled render, controlled click reports without moving the view, following a parent activeSection change, defaultSection ignored when controlled, and active section propagating to Sidebar/TopBar. Screens and chrome are stubbed so the tests cover only Dashboard's own logic.
  • ErrorBoundary.test.tsx gains 3 cases: onRetry on the default fallback, via a custom fallback's reset, and an ordering test proving a child that throws until re-initialised renders successfully after one retry.

ESLint clean on all five changed files.

Two pre-existing things I noticed

  1. Sidebar restores a persisted section on mount. Sidebar's effect reads sorokit-active-nav from localStorage and calls onNavigate(saved) when it differs from active. In controlled mode that surfaces as one unprompted onSectionChange shortly after mount. I left it alone as it's outside this issue, but if the intent is for controlled mode to fully own the section it probably shouldn't fire — happy to follow up.

  2. npm run build / tsc -b don't pass on main. FeeEstimator.tsx and SorobanPanel.tsx each contain an unclosed block left by a merge, which also accounts for the current pre-existing test failures. Unrelated to this PR; glad to open a separate repair PR.

…gation

Adds external control over the active nav section so consumers can deep-link
into a screen, plus a retry hook on ErrorBoundary for failed client init.

- Dashboard: `activeSection` + `onSectionChange` for controlled mode, and
  `defaultSection` for the uncontrolled initial section. `onSectionChange`
  fires in both modes; in controlled mode the parent owns what renders.
- ErrorBoundary: `onRetry` runs before the error state clears, so children
  re-mount with a re-initialised dependency instead of throwing again.
- main.tsx: wire a retry handler that re-creates the client on retry.

Resolves Sorokit#407
@drips-wave

drips-wave Bot commented Jul 29, 2026

Copy link
Copy Markdown

@jadonamite Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

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.

feat(Dashboard): no controlled activeSection/onSectionChange prop, no defaultSection prop, ErrorBoundary has no retry for client init failure

2 participants