Skip to content

feat(react): add Modal primitive, network switcher, env validation and strict tooling - #1267

Open
akargi wants to merge 12 commits into
BCPathway:mainfrom
akargi:feature/strict-tooling-modal-and-network-switcher
Open

akargi wants to merge 12 commits into
BCPathway:mainfrom
akargi:feature/strict-tooling-modal-and-network-switcher

Conversation

@akargi

@akargi akargi commented Oct 15, 2026

Copy link
Copy Markdown

Summary Four frontend/tooling foundations for the dApp. - Modal dialog primitive (#1149) ΓÇö portal-rendered ole="dialog" with �ria-modal, focus trapping across Tab / Shift+Tab, Escape and backdrop dismissal, blurred backdrop, refcounted body scroll lock, focus restoration to the trigger on close, and topmost-by-DOM-order arbitration so stacked dialogs do not fight. SSR-safe. - Multi-network switcher (#1148) ΓÇö useNetwork() returns { network, networkId, networks, rpcUrl, networkPassphrase, setNetwork, isSwitching, error } across Stellar Testnet, Futurenet and a local standalone node. Switching rebuilds �cForgeClient (and VaultClient) so every hook keyed on the client refetches, and pushes the new passphrase into the context that WalletProvider validates against. Unknown ids surface as error, not an exception. - Environment validation (#1146) ΓÇö eact/demo/src/config.ts validates VITE_RPC_URL, VITE_NETWORK_PASSPHRASE and VITE_CONTRACT_ID with zod on boot and returns a discriminated result instead of throwing. Unset variables keep the existing testnet defaults, so a fresh clone still runs; a variable that is set but malformed is reported with the received value, what is expected, and a remediation hint on a dedicated configuration error screen. - Strict TypeScript and ESLint (#1145) ΓÇö enables oUncheckedIndexedAccess, oImplicitOverride, oFallthroughCasesInSwitch, oUnusedLocals, oUnusedParameters and isolatedModules; promotes the eact-hooks rules and o-unused-vars to errors and adds o-console. Every resulting error is fixed by narrowing ΓÇö no �ny was introduced and no type was loosened. ## Notes for the reviewer - Backwards compatibility is structural, not incidental: when no network is selected, effectiveConfig returns the caller's config by identity, so client keeps a stable reference and existing consumers behave exactly as before. context.test.tsx and ConnectWallet.test.tsx are unaffected, and a new context.network.test.tsx covers the regression where an adapter on testnet must report wrong-network after switching to futurenet. - Two flags were deliberately left off: - exactOptionalPropertyTypes ΓÇö breaks existing call sites that pass explicit undefined, for zero runtime benefit. - �erbatimModuleSyntax ΓÇö eact/jest.config.js merges eact/tsconfig.json with an inline { module: 'commonjs' }, which would emit TS1286 on every ESM file and fail the whole test suite. - oImplicitReturns is also off: Dropdown's handleKeyDown intentionally falls through its switch. - A new pm run typecheck script runs sc --noEmit. - contractIds on BcForgeProvider is additive scope beyond the issue text ΓÇö a switcher that keeps one contract id across three chains is not useful in practice. ## Verification Nothing here has been machine-verified ΓÇö sc, eslint, jest and the demo build were not run, as requested. ΓÜá∩╕Å ** pm install is required before CI will pass.** zod@^4.4.3 was added to eact/demo/package.json but package-lock.json was intentionally left untouched, so pm ci fails on the out-of-sync lockfile. ΓÜá∩╕Å ** pm run test:visual:update --workspace @bc-forge/react** on the arm64 Playwright image ΓÇö the four new Primitives/Modal stories have no committed baselines. Closes #1145 Closes #1146 Closes #1148 Closes #1149

…d strict tooling

## Summary

Four frontend/tooling foundations for the dApp.

- **Modal dialog primitive** (BCPathway#1149) — portal-rendered 
ole="dialog" with �ria-modal, focus trapping across Tab / Shift+Tab, Escape and backdrop dismissal, blurred backdrop, refcounted body scroll lock, focus restoration to the trigger on close, and topmost-by-DOM-order arbitration so stacked dialogs do not fight. SSR-safe.
- **Multi-network switcher** (BCPathway#1148) — useNetwork() returns { network, networkId, networks, rpcUrl, networkPassphrase, setNetwork, isSwitching, error } across Stellar Testnet, Futurenet and a local standalone node. Switching rebuilds �cForgeClient (and VaultClient) so every hook keyed on the client refetches, and pushes the new passphrase into the context that WalletProvider validates against. Unknown ids surface as error, not an exception.
- **Environment validation** (BCPathway#1146) — 
eact/demo/src/config.ts validates VITE_RPC_URL, VITE_NETWORK_PASSPHRASE and VITE_CONTRACT_ID with zod on boot and returns a discriminated result instead of throwing. Unset variables keep the existing testnet defaults, so a fresh clone still runs; a variable that is *set but malformed* is reported with the received value, what is expected, and a remediation hint on a dedicated configuration error screen.
- **Strict TypeScript and ESLint** (BCPathway#1145) — enables
oUncheckedIndexedAccess,
oImplicitOverride,
oFallthroughCasesInSwitch,
oUnusedLocals,
oUnusedParameters and isolatedModules; promotes the 
eact-hooks rules and
o-unused-vars to errors and adds
o-console. Every resulting error is fixed by narrowing — no �ny was introduced and no type was loosened.

## Notes for the reviewer

- **Backwards compatibility is structural, not incidental:** when no network is selected, effectiveConfig returns the caller's config *by identity*, so client keeps a stable reference and existing consumers behave exactly as before. context.test.tsx and ConnectWallet.test.tsx are unaffected, and a new context.network.test.tsx covers the regression where an adapter on testnet must report wrong-network after switching to futurenet.
- Two flags were deliberately **left off**:
  - exactOptionalPropertyTypes — breaks existing call sites that pass explicit undefined, for zero runtime benefit.
  - �erbatimModuleSyntax — 
eact/jest.config.js merges 
eact/tsconfig.json with an inline { module: 'commonjs' }, which would emit TS1286 on every ESM file and fail the whole test suite.
-
oImplicitReturns is also off: Dropdown's handleKeyDown intentionally falls through its switch.
- A new
pm run typecheck script runs 	sc --noEmit.
- contractIds on BcForgeProvider is additive scope beyond the issue text — a switcher that keeps one contract id across three chains is not useful in practice.

## Verification

Nothing here has been machine-verified — 	sc, eslint, jest and the demo build were not run, as requested.

⚠️ **
pm install is required before CI will pass.** zod@^4.4.3 was added to 
eact/demo/package.json but package-lock.json was intentionally left untouched, so
pm ci fails on the out-of-sync lockfile.

⚠️ **
pm run test:visual:update --workspace @bc-forge/react** on the arm64 Playwright image — the four new Primitives/Modal stories have no committed baselines.

Closes BCPathway#1145
Closes BCPathway#1146
Closes BCPathway#1148
Closes BCPathway#1149
@drips-wave

drips-wave Bot commented Oct 15, 2026

Copy link
Copy Markdown

@akargi 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

akargi and others added 7 commits October 15, 2026 19:51
oUnusedLocals and friends left the project without an explicit 
ootDir, and
TypeScript 6 rejects that with TS5011 for every ts-jest compile — which failed
all 16 suites before a single test ran. 
ootDir is now pinned to ./src,
matching where tsup emits declarations.

Stacked dialogs resolved the topmost the wrong way round. Nested portals are
inserted *child-first*, so two dialogs both portalled to document.body landed
in the DOM as inner-then-outer, 
oots[roots.length - 1] picked the outer one,
and Escape closed the wrong dialog. A nested Modal now portals into its
parent's backdrop instead of document.body, which makes document order agree
with the visual stacking the user actually sees.

oUnusedLocals also flagged six test files importing React only as a
side-effect import; the default binding is unused under 
eact-jsx.

Closes BCPathway#1145
Closes BCPathway#1146
Closes BCPathway#1148
Closes BCPathway#1149
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>
\
pm install\ resolves optional and peer dependencies for the current
platform, so a lockfile generated on Windows drops entries that \
pm ci\ on
the Linux runner then reports as missing. Restoring the upstream lockfile and
regenerating it on Linux keeps the two in step.

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>
…tories

The base \
ootDir\ that fixed TS5011 for ts-jest also broke
\	sconfig.storybook.json\, which extends it but compiles \stories\, \�isual\
and \.storybook\ — all outside \src\ — with TS6059. The storybook project now
widens \
ootDir\ to match the tree it actually compiles.

The Modal stories called \useState\ directly inside Storybook's \
ender\,
which is not a component: that breaks the rules of hooks and fails to
typecheck against \StoryFnRender\. The state now lives in harness components.

\ModalProps.onClose\ is required, so Storybook inferred \�rgs\ as required on
every story; it is supplied once in the shared meta \�rgs\ instead.

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>
@akargi
akargi force-pushed the feature/strict-tooling-modal-and-network-switcher branch from 982486b to 860ff24 Compare October 16, 2026 01:02
akargi and others added 3 commits October 17, 2026 06:58
…n merge

Merging main into this branch left the same bad conflict resolution in
react/src/components/index.ts that it produced on the wallet branch: the
Dropdown and ConnectWallet export blocks were pasted back in on top of
the copies main already carries, so both esbuild and tsc reject the
file (TS2300 duplicate identifier / "Multiple exports with the same
name"). React Package, both React peer-compat jobs and the visual job
all died there, before a test or screenshot ran.

Restore the barrel to main's version and re-add only the two lines this
branch actually needs: Modal and its prop/size types.

Two things the strict-TypeScript work (BCPathway#1145) left behind:

- ConfirmationDialog's focus trap still guarded with
  `focusable.length === 0`, which no longer narrows under
  `noUncheckedIndexedAccess`. Narrow `first`/`last` instead, matching
  what Modal.tsx already does - the old guard happened to behave
  correctly at runtime, so this keeps the behaviour identical.
- Three test files still `import React` without using it, which
  `noUnusedLocals` now rejects. The repo builds with the automatic JSX
  runtime, so the imports are dead weight; drop them. Without this the
  branch's own new `npm run typecheck` script fails on the branch it
  ships in.

The merge had also reformatted ConnectionStatusBar, CopyToClipboard,
TransactionToast and utils.ts - quote style and trailing commas only,
zero semantic difference, none of it related to this PR. Revert those
to main so the diff is just the Modal / network-switcher / env-validation
/ tooling work.

Verified locally on Node 22: `npm run typecheck`, `npm test` (308
passing), `npm run build`, `typecheck:storybook`, `lint:storybook`,
`tsc -p react/demo/tsconfig.json`, and a Storybook build with the four
new Primitives/Modal stories rendering with no console errors.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant