Repository navigation
feat(react): harden wallet detection, Albedo fallback and session persistence - #1266
Open
memplethee-lab wants to merge 13 commits into
Open
memplethee-lab wants to merge 13 commits into
memplethee-lab wants to merge 13 commits into
Conversation
…sistence ## Summary Hardens the wallet connection layer with SSR-safe detection, an extension-free fallback, and reload-persistent sessions. - **Freighter detection** (BCPathway#1169) — isFreighterInstalled() probes the exact global FreighterAdapter reads and distinguishes *present* from *present but broken*. whenFreighterReady() handles asynchronous extension injection with a bounded ~1s budget and an AbortSignal for cancellation. The uninstalled path renders an install link and a clean error instead of throwing. - **Albedo fallback** (BCPathway#1170) — loadAlbedoScript() lazily injects the Albedo intent script, adopting an existing tag, sharing one in-flight promise, and polling for the global after load. Failures resolve to a structured reason rather than throwing, and a failed tag is recorded so it is not retried on every render. @albedo-link/intent is **not** added as an npm dependency. - **WalletSelectModal** (BCPathway#1171) — lists detected wallets with inline-SVG icons, Installed / Not installed badges and arget="_blank" rel="noopener noreferrer" install links, highlights what is available, and starts the connection on selection. Self-contained: own portal, focus trap, Escape, backdrop dismissal, and closing is blocked while a connection is in flight. - **Session persistence** (BCPathway#1172) — �c-forge:wallet-session and �c-forge:wallet-preference store only the wallet name (never a secret or a stale address), validated against the WalletName union on read and tolerant of malformed JSON. Storage access is wrapped for Safari private mode and degrades to no-ops. ## Notes for the reviewer - useWallet() is **additive only** — status, connected, publicKey, ame, error, connect and disconnect are unchanged. connectWallet, �vailableWallets and estoring are new. - connect / disconnect now read state through a ref, which gives them **stable identities**. This is what stops the restore effect from re-firing on every state change. - Session storage is read inside an effect, never during render, so server and client first paint are identical — no hydration mismatch. - A restored session reuses the existing passphrase validation, so reconnecting on the wrong network lands in the existing wrong-network state rather than silently proceeding. A session whose wallet has since been uninstalled is cleared and stays disconnected. - Two existing test files (context.test.tsx, ConnectWallet.test.tsx) needed a localStorage.clear() in �eforeEach. Without it, a session saved by one test is silently reconnected at the start of the next one and races the existing assertions — a direct consequence of persisting state by default. - estoreSession defaults to rue to match the acceptance criteria; pass estoreSession={false} to opt out. ## Verification Nothing here has been machine-verified — jest, eslint and tsc were not run, as requested.⚠️ **One step needed before CI is green**: pm run test:visual:update --workspace @bc-forge/react on the arm64 Playwright image. The visual suite screenshots every story against zero-tolerance baselines with updateSnapshots: 'none', and the new Wallet/ stories have no committed PNGs. Note that the modal portals to document.body, so the story-frame baseline captures the trigger button only. Closes BCPathway#1169 Closes BCPathway#1170 Closes BCPathway#1171 Closes BCPathway#1172
|
@memplethee-lab 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! 🚀 |
…sion
useWallet returned �vailableWallets, connectWallet and
estoring, but
WalletDetectionActions only declared detectWallets and
estoring. The cast
to WalletConnectionState therefore failed with TS2352 and took down every test
suite that imported the context. �vailableWallets now lives on the public
interface it was always returned from.
parseSession accepted a bare stored name (reighter) but not that same name
serialised on its own ("freighter"), so a truncated or hand-written store was
silently discarded. Both forms now resolve, which is what the existing test for
a truncated value asserts.
Two test corrections: the wallet rows each render their own install badge, so
getByText on the shared label matched twice and had to become
getAllByText; and the focus-trap test moved focus outside �ct, which drove
a React state update through the dialog's onFocus.
Closes BCPathway#1169
Closes BCPathway#1170
Closes BCPathway#1171
Closes BCPathway#1172
react/visual/stories.spec.ts compares every story against a zero-tolerance PNG baseline, so a new story without one fails \ pm run test:visual\. Those PNGs are only reproducible on ubuntu-24.04-arm inside the pinned Playwright container, which is awkward to run by hand. This workflow wraps that step and commits the result. It is workflow_dispatch only, so the regular visual workflow remains the gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
\WalletSelectModal\ gained the \portalContainer\ prop that
\ConfirmationDialog\ already had, and the story now portals into a stage
inside the frame. The dialog was rendering outside \canvasElement\, so the
baseline captured only the trigger button, and \getByText('Installed')\
matched both wallet rows at once.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
\WalletFixture\ mounts \WalletProvider\ without \�dapterFactories\, so the real SDK adapters probe \window.freighter\ and \window.albedo\. Neither exists in the Storybook preview, so no wallet is installed and every row is badged as missing — the assertion that two rows read 'Installed' could never hold. It now asserts the missing badge and the install link, which is the state a visitor without an extension actually sees. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both were scaffolding for producing the committed artefacts this branch needs: the visual baselines (only reproducible on ubuntu-24.04-arm in the pinned Playwright container) and a lockfile regenerated on the platform CI installs on. Both artefacts are now committed, so the workflows have nothing left to do and only add an approval gate on the PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…visories Four advisories were published after the last green run on main, so `Dependency Audit` and `Smoke Test & Publish Indexer` fail on every branch, including main itself. - anyhow 1.0.102 -> 1.0.103 (RUSTSEC-2026-0190) - handlebars 4.7.9 -> 4.7.10 (CVE-2026-106445, CVE-2026-106446) - proxy-addr 2.0.7 -> 2.0.8 (CVE-2026-90711) - uuid 8.3.2 -> 11.1.1 (GHSA-w5hq-g745-h8pq, reached through node-cron) The npm entries are patched in place instead of re-resolved: a full `npm install` rewrites the lockfile against manifests whose ranges float (root `vite` is 8.3.0 while `react/demo` asks for ^6.0.0), producing a tree `npm ci` rejects. Only the affected entries change, so every other pin is preserved exactly. `node-cron` pins uuid at an exact 8.3.2, so its declared range is widened to accept the bump. The handlebars exceptions record the October 2026 disclosure window while the bump propagates through the published indexer image. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
memplethee-lab
force-pushed
the
feature/wallet-adapters-and-session-persistence
branch
from
October 16, 2026 01:02
fd24f14 to
d7aa71b
Compare
Merging main into this branch left a bad conflict resolution in react/src/components/index.ts: the Dropdown and ConnectWallet export blocks were pasted back in at the top of the file, on top of the copies main already carries further down. Both esbuild and tsc reject the barrel (TS2300 duplicate identifier, "Multiple exports with the same name"), which is what failed React Package, both React peer-compat jobs and the visual job - each of them died at the build step before a single test or screenshot ran. Restore the barrel to main's version and re-add only the two lines this branch actually needs: WalletSelectModal and its prop type. The same merge also reformatted seven files this PR never touches (ConnectionStatusBar, CopyToClipboard, TransactionToast and their tests, plus ProductComponents.test.tsx) - quote style and trailing commas only, with zero semantic difference. Revert them so the diff is just the wallet work.
This branch has not been deployed
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 Hardens the wallet connection layer with SSR-safe detection, an extension-free fallback, and reload-persistent sessions. - Freighter detection (#1169) ΓÇö isFreighterInstalled() probes the exact global FreighterAdapter reads and distinguishes present from present but broken. whenFreighterReady() handles asynchronous extension injection with a bounded ~1s budget and an AbortSignal for cancellation. The uninstalled path renders an install link and a clean error instead of throwing. - Albedo fallback (#1170) ΓÇö loadAlbedoScript() lazily injects the Albedo intent script, adopting an existing tag, sharing one in-flight promise, and polling for the global after load. Failures resolve to a structured reason rather than throwing, and a failed tag is recorded so it is not retried on every render. @albedo-link/intent is not added as an npm dependency. - WalletSelectModal (#1171) ΓÇö lists detected wallets with inline-SVG icons, Installed / Not installed badges and arget="_blank" rel="noopener noreferrer" install links, highlights what is available, and starts the connection on selection. Self-contained: own portal, focus trap, Escape, backdrop dismissal, and closing is blocked while a connection is in flight. - Session persistence (#1172) ΓÇö �c-forge:wallet-session and �c-forge:wallet-preference store only the wallet name (never a secret or a stale address), validated against the WalletName union on read and tolerant of malformed JSON. Storage access is wrapped for Safari private mode and degrades to no-ops. ## Notes for the reviewer - useWallet() is additive only ΓÇö status, connected, publicKey, ame, error, connect and disconnect are unchanged. connectWallet, �vailableWallets and estoring are new. - connect / disconnect now read state through a ref, which gives them stable identities. This is what stops the restore effect from re-firing on every state change. - Session storage is read inside an effect, never during render, so server and client first paint are identical ΓÇö no hydration mismatch. - A restored session reuses the existing passphrase validation, so reconnecting on the wrong network lands in the existing wrong-network state rather than silently proceeding. A session whose wallet has since been uninstalled is cleared and stays disconnected. - Two existing test files (context.test.tsx, ConnectWallet.test.tsx) needed a localStorage.clear() in �eforeEach. Without it, a session saved by one test is silently reconnected at the start of the next one and races the existing assertions ΓÇö a direct consequence of persisting state by default. - estoreSession defaults to rue to match the acceptance criteria; pass estoreSession={false} to opt out. ## Verification Nothing here has been machine-verified ΓÇö jest, eslint and tsc were not run, as requested. ΓÜá∩╕Å One step needed before CI is green: pm run test:visual:update --workspace @bc-forge/react on the arm64 Playwright image. The visual suite screenshots every story against zero-tolerance baselines with updateSnapshots: 'none', and the new Wallet/ stories have no committed PNGs. Note that the modal portals to document.body, so the story-frame baseline captures the trigger button only. Closes #1169 Closes #1170 Closes #1171 Closes #1172