Skip to content

refactor(connect): extract the leaf concerns from CantonConnectProvider - #71

Draft
fernandomg wants to merge 19 commits into
feat/51-discovered-wallets-and-partiesfrom
refactor/70-provider-leaf-extraction
Draft

refactor(connect): extract the leaf concerns from CantonConnectProvider#71
fernandomg wants to merge 19 commits into
feat/51-discovered-wallets-and-partiesfrom
refactor/70-provider-leaf-extraction

Conversation

@fernandomg

Copy link
Copy Markdown
Member

Summary

Closes #70

Pure code motion out of CantonConnectProvider.tsx, sized by #63's pre-undraft review pass: the
two leaf concerns with little or no shared state get their own modules; the race core stays put.
The provider goes from 671 to 596 lines with zero behavior change.

Draft until the #47#63 train merges; GitHub retargets this PR automatically when #63's branch is
deleted on merge.

Changes

  • buildAdditionalAdapters and AdapterConfig move to src/adapters.ts; AdapterConfig stays
    module-internal, nothing else needed it.
  • TxStatusSnapshot and CantonConnectContextValue move to src/types.ts, beside the config
    types they reference; index.ts and useExecute.ts import them from there directly.

Acceptance criteria

From #70:

  • buildAdditionalAdapters and AdapterConfig move to src/adapters.ts
  • TxStatusSnapshot and CantonConnectContextValue move to src/types.ts. Diverged on the
    mechanics: no re-export shim from CantonConnectProvider — the only importers were our own
    index.ts and useExecute.ts, repointed directly, so the package surface is identical with
    one source of truth.
  • The optional picker-bridge extraction was judged at implementation time and skipped: its
    boundary needs six crossings (inPagePicker, offeredWallets, select, cancel,
    cancelPending, plus a pending-choice probe for cancelAttempt's condemnation message),
    which buys ~90 moved lines at the cost of a chattier seam than the file length ever was.
    Recorded so the next reader doesn't re-litigate.
  • The race core (connect, runConnect, syncFromStatus, the mount effect, disconnect)
    stays in CantonConnectProvider.tsx, per canton-connect/CLAUDE.md
  • pnpm -C canton-connect test passes with no test edits; no public API change

Test plan

Automated tests

No new tests: pure code motion, and the proof is the existing suite passing textually unchanged —
85 tests, plus tsc, biome, knip, and the tsdown build, all green. Root gate re-runs before this
leaves draft.

Manual verification

No manual steps required.

Breaking changes

None. The package exports the same names from the same entries.

Checklist

  • Self-reviewed my own diff
  • Tests added or updated (none needed: the unchanged suite is the point)
  • Docs updated (if applicable): no behavior or layout rule changed
  • No unrelated changes bundled in

Screenshots

None.

- every symbol the barrel exports carries a block: the six hooks and their
  result types, CantonConnectConfig and each field, Party, ConnectionStatus,
  the provider and its context value, the mock adapter, and the ./testing
  factories
- each hook names its wagmi counterpart — useParty maps to useAccount,
  useExecute to useWriteContract plus useWaitForTransactionReceipt — and says
  where the shapes diverge: wagmi's hooks are TanStack Query mutations, ours
  resolve plain promises
- satisfies the root CLAUDE.md rule that barrel exports carry JSDoc, and is the
  input issue #16 consumes for llms.txt and a docs site
- comments only: no behaviour, no signature, no export changed
- drop the wrapper blocks on result and options interfaces (`Return value of useX`, `Options for …`)
- drop property blocks that repeat the name and type; keep only defaults, gates and throws
- compress every surviving block to one or two lines
- 45 blocks kept of 61; 174 comment lines removed, 56 added

Per review feedback on #48.
- applies to the four blocks added in 1d0a258 only
- the em dashes elsewhere in the package predate this branch
The SDK returns one record per account, each carrying its own required status.
Both doubles now model that per-account rather than wallet-wide: the fake
wallet passes a declared status straight through, and the mock defaults to
allocated when an account declares none.
- only `allocated` parties are offered: `initialized` never finished signing, `removed` is gone
- an account reporting no status is kept, so older wallets and test doubles still work
- `disabled` is never filtered on — such a party signs through the participant and still works
- the primary account leads; the rest keep the wallet's order
- provider tracks parties beside party; one applyAccounts callback maps both
- initial listAccounts read and the accountsChanged push share that mapping
- parties empties wherever party goes undefined: lock, disconnect, dead probe
- party is always parties[0]; a test states the invariant
- overlapping connect() calls each stored a teardown closure; the second overwrote the first
- the orphaned wiring survived disconnect() and kept applying wallet pushes (#57)
- guard with an attempt ref ahead of setStatus('connecting') and the listener teardown
- non-async guard: an async wrapper would re-wrap the shared promise per caller, rejecting unhandled
- keep one handler on the shared attempt so a fire-and-forget joiner cannot reject unhandled
- document the idempotent-while-in-flight contract on the context value and useConnect
…nnected state

- add a rejection handler to the mount init chain: connectError + disconnected, cancelled-aware
- syncFromStatus claims connected only after the account read completes
- a failed read tears down wiring, clears party/parties, surfaces the error, lands disconnected
- syncFromStatus never throws, so connect()'s failure path keeps its original error
- locked restore keeps its shape: no accounts to read, connected + isLocked directly
- extract the repeated transitions into named helpers instead of setter piles
… failed connects

- resetToDisconnected sets connectError unconditionally, so disconnect() clears a stale error
- tear down wiring in runConnect's no-session branch: no live listeners on a disconnected provider
- record the original error after recovery, so connectError matches what connect() rejects with
- walletSelection: 'in-page' hands the wallet choice to the dApp's own UI
- the provider keeps the picker's resolve/reject pair and publishes the offered entries
- useWalletPicker() exposes isOpen/wallets/select/cancel; select answers the SDK
- an explicit walletPicker still wins over the mode; the default popup path is untouched
- select with an unoffered id rejects the attempt with a plain Error naming the id
- ConnectedWallet type defined for the wallet-identity task that follows
…d connect attempt

- record why a pending choice was cancelled: user, disconnect, or unmount
- connect()'s failure path recovers only after a user cancel; disconnect and unmount just re-throw
- disconnect() cancels the choice, waits for the in-flight attempt to settle, then resets
- a choice arriving while disconnect or unmount kills the attempt rejects instead of pending
- unmount cancels the pending choice, so the attempt settles without unhandled rejections
- fold the init-failure path onto resetToDisconnected(err)
- new connectedWallet.ts: the record under canton-connect:connected-wallet
- written only when an attempt lands: connect success or the probe ending connected-and-locked
- restored sessions read the record; a probe finding no session deletes it, as does disconnect()
- a supplied walletPicker is wrapped only to note its result; the wrapper identity stays stable
- corrupt or partial records read as absent, never throw
- useParty() gains wallet (wagmi: useAccount().connector); popup mode reports none by design
- an inline walletPicker or additionalAdapters rebuilds the SDK per re-render (kept, documented)
- a replacement instance whose restore probe finds nothing now resets status and party too
- replacement keys on instance identity, so a StrictMode re-run never resets a first mount
- an idle provider stays idle across swaps: only non-idle state is stale enough to clear
- pin in-page mode: rerenders keep one SDK instance and a single init()
- tests: dispose each fake wallet when its role ends, so a failing assertion leaks no listeners
- delete comments that restate the identifier below them: file headers, toParty, toParties
- shorten every survivor running past ~100 characters
- remove a stray /** that doubled the walletPicker JSDoc opener
…seam

- add useParties, useWalletPicker, and walletSelection to the README tables
- render connectError outside the isConnected branch in the quickstart
- state that a rejected wallet is indistinguishable from a locked one from outside the SDK
- note that an inline walletPicker or additionalAdapters rebuilds the SDK every render
- state the two limits: wallet list only during an attempt, no wallet identity in popup mode
- architecture: the themed picker is issue #50; describe the in-page context seam
…g state

- a wallet window closed without answering leaves the connect RPC pending
  forever; disconnect() awaited that attempt and deadlocked before any reset.
  The kill is now recorded on the attempt itself and a condemnation race
  settles the caller, so disconnect cleans up immediately (found by hand in
  the harness with a real extension wallet, task 12)
- the same fences close what the review pass then found: an in-page choice
  arriving after disconnect no longer opens over the clean slate (the picker
  fails closed and each choice dooms its own attempt); the cancel-recovery
  probe re-checks its fate after every await instead of only at catch entry;
  a mount restore resolving mid-attempt yields instead of wiring beneath it
- guards runConnect's success path too, so a late wallet answer writes no
  state and never evicts a newer attempt
- also braces the single-line guards and trues up the JSDoc the rework left
  stale
- the config identity note claimed additionalAdapters churn rebuilds the SDK;
  only walletPicker does, a new adapters array re-runs init() on the instance
- the mount bullet now carries the replacement-instance rule from 51c096a
- useConnect's disconnect JSDoc says it settles an unanswered connect
- buildAdditionalAdapters and AdapterConfig move to src/adapters.ts;
  AdapterConfig stays module-internal, nothing else needed it
- TxStatusSnapshot and CantonConnectContextValue move to src/types.ts beside
  the config types; index.ts and useExecute import them from there directly,
  so the package surface is unchanged and no re-export shim is needed
- the race core stays in CantonConnectProvider.tsx per canton-connect/CLAUDE.md
- no behavior change: all 85 tests pass textually unchanged
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@fernandomg fernandomg linked an issue Aug 3, 2026 that may be closed by this pull request
5 tasks
@fernandomg
fernandomg force-pushed the feat/51-discovered-wallets-and-parties branch 2 times, most recently from f05a67b to c3f7c30 Compare August 3, 2026 13:05
@gabitoesmiapodo
gabitoesmiapodo force-pushed the feat/51-discovered-wallets-and-parties branch from c3f7c30 to f293a46 Compare August 3, 2026 16:34
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.

Extract the leaf concerns from CantonConnectProvider

2 participants