Skip to content

Add @goodwidget/goodreserve-widget with reserve swap runtime contract, quote/tx state machine, and Storybook/demo coverage#39

Merged
L03TJ3 merged 10 commits into
mainfrom
copilot/build-goodreserve-widget
Jul 8, 2026
Merged

Add @goodwidget/goodreserve-widget with reserve swap runtime contract, quote/tx state machine, and Storybook/demo coverage#39
L03TJ3 merged 10 commits into
mainfrom
copilot/build-goodreserve-widget

Conversation

Copilot AI commented May 27, 2026

Copy link
Copy Markdown
Contributor

This PR introduces the first packages/goodreserve-widget package to prove GoodWidget supports a quote-driven reserve swap flow (buy/sell) with reserve-specific state handling and reusable widget packaging.
It maps the swap UX structure from GoodWalletV2 into a widget-local implementation and adds deterministic story/demo coverage for all required states.

  • Widget package scaffold + runtime contract

    • Added @goodwidget/goodreserve-widget package scaffold (package.json, tsconfig/tsup, React + element/register entrypoints).
    • Added widgetRuntimeContract.ts + integration.ts defining reserve states/events/dependencies (no_provider, unsupported_chain, quote_*, confirm_dialog, swap_*, etc.).
    • Exported widget runtime/integration surface from src/index.ts.
  • Reserve swap UI + local adapter/state

    • Implemented GoodReserveWidget, ReserveSwapView, and useGoodReserveAdapter.
    • Added widget-local state/actions for:
      • direction toggle (buy/sell),
      • amount editing + quote debounce,
      • slippage selection,
      • confirm dialog,
      • pending/success/error tx lifecycle,
      • insufficient balance and reserve warning/error presentation.
    • Wired provider/runtime through existing GoodWidgetProvider path and chain gating for Celo (42220) + XDC (50).
  • SDK wiring and explicit reserve dependency handling

    • Added reserve error mapping utilities and SDK initialization path inside widget-local adapter.
    • Added explicit user-facing state when @goodsdks/good-reserve is unavailable at runtime (reported as a blocker condition, not silently ignored).
  • Storybook coverage (widget-scoped, deterministic)

    • Added examples/storybook/src/stories/goodreserve-widget/GoodReserveWidget.stories.tsx.
    • Added reserve-specific deterministic fixtures in examples/storybook/src/fixtures/goodReserveWidgetMock.ts.
    • Included required visual states: disconnected, unsupported chain, quote loading/success/error, insufficient balance, slippage sheet, confirm, pending/success/error, buy+sell ready states.
  • Demo verification coverage

    • Added tests/widgets/goodreserve-widget/states.spec.ts with deterministic Playwright checks mapped to widget stories.
  • Workspace/docs wiring

    • Added Storybook dependency on @goodwidget/goodreserve-widget.
    • Updated top-level package listings/architecture docs to include goodreserve-widget.
// packages/goodreserve-widget/src/integration.ts
export const goodReserveWidgetIntegration = {
  id: 'goodreserve-swap',
  sdk: '@goodsdks/good-reserve',
  chains: [42220, 50],
  states: [
    'no_provider',
    'unsupported_chain',
    'sdk_initializing',
    'quote_ready',
    'confirm_dialog',
    'swap_pending',
    'swap_success',
    'swap_error',
  ],
  events: ['swap-success', 'swap-error'],
} as const

Copilot AI changed the title [WIP] Build GoodReserve widget backed by GoodReserve SDK Add @goodwidget/goodreserve-widget with reserve swap runtime contract, quote/tx state machine, and Storybook/demo coverage May 27, 2026
Copilot AI requested a review from L03TJ3 May 27, 2026 15:17
Copilot finished work on behalf of L03TJ3 May 27, 2026 15:17
@L03TJ3

L03TJ3 commented May 28, 2026

Copy link
Copy Markdown
Contributor

@copilot the skills folder should not be committed so can be removed.

can you also produce initial screenshots of the story examples

Copilot AI commented May 28, 2026

Copy link
Copy Markdown
Contributor Author

@copilot the skills folder should not be committed so can be removed.

can you also produce initial screenshots of the story examples

Implemented in commit f31eb12.

  • Removed committed skills artifacts (.agents/skills/** and skills-lock.json).
  • Added initial GoodReserve Storybook screenshots under:
    examples/storybook/src/stories/goodreserve-widget/screenshots/

Copilot finished work on behalf of L03TJ3 May 28, 2026 10:53
@Ryjen1 Ryjen1 mentioned this pull request Jun 6, 2026
17 tasks
@Ryjen1 Ryjen1 mentioned this pull request Jun 17, 2026
3 tasks
* fix(goodreserve-widget): align success screen and sizing with Figma

- Rebuild the swap-success screen to the Figma success frame order: glowing
  blue check hero, title, summary card, "View on Explorer" link, and a
  full-width pill "Do another swap" CTA.
- Make the widget responsive: fill the host container up to a 390px max width
  (mobile-frame width) instead of a fixed 360px column.
- Raise primary CTAs to the Figma 54px height.
- Widen the Storybook story frame to 390px and regenerate story + Playwright
  screenshot evidence to reflect the updated layout.

* fix(goodreserve-widget): make the amount input editable on web

The @goodwidget/ui Input is a Tamagui `tag:'input'` Stack, which does not
translate React Native's onChangeText to a DOM change event on web. As a
result the controlled amount field rejected all typed input and the entire
type-amount -> quote -> confirm -> swap path was unreachable in a browser
(every Storybook state is pre-seeded via mockState, so existing tests missed it).

- Wire the native onChange handler and inputMode="decimal" instead of the
  RN-only onChangeText/keyboardType props.
- Sanitize input to a single decimal number at the boundary so values are
  always safe for viem parseUnits (rejects "1.2.3", separators, letters).
- Bump the amount font to 34px to match the Figma reference.
- Add a live-adapter Interactive story and a Playwright regression test that
  types into the input and asserts both editing and sanitization.

Verified empirically: before, typing left the DOM input value empty; after,
the value updates and "1.2.3x" sanitizes to "1.23".

* fix(goodreserve-widget): make swap math and execution safe

Correctness and safety fixes in the reserve adapter:

- Derive minimumReceived in BigInt from the SDK quote and carry the exact
  minReturn (minReturnRaw) into executeSwap, so the floor shown to the user is
  precisely the floor submitted on-chain. Previously the display used float
  math (Number * (1 - slippage)) while execution used BigInt, allowing the two
  to diverge on small trades.
- Guard executeSwap against double submission while a swap is pending.
- Re-validate chain support inside executeSwap so switching to an unsupported
  chain after opening the confirm dialog is caught before signing.
- Set swap_success before refreshing balances and make the refresh best-effort,
  so an RPC blip can no longer turn a confirmed swap into swap_error.
- mapReserveError now logs unmatched errors and returns a generic fallback
  instead of surfacing raw viem output (which can leak RPC URLs / addresses).

* refactor(goodreserve-widget): harden adapter state machine and chain handling

- Make the public client chain-aware (RESERVE_CHAINS map) so the GoodReserve
  SDK constructor, which validates publicClient.chain.id, does not throw on a
  chainless client.
- Reset the cached SDK/read client on bootstrap so a Celo<->XDC switch
  re-initializes against the new chain instead of reusing stale clients.
- Read the "from" balance via a ref inside the quote effect and drop it from
  the effect deps, so a post-swap or direction-toggle balance refresh no longer
  restarts the quote debounce.
- Preserve and restore the pre-overlay status when the slippage sheet or
  confirm dialog is dismissed, instead of unconditionally forcing quote_ready.
- Render a dedicated sdk_initializing loading state (with fixture, story, and
  Playwright coverage) rather than a half-populated swap card.

* fix(goodreserve-widget): apply amount input textAlign via DOM style

Use style={{ textAlign: 'right' }} instead of the React Native textAlign prop,
which Tamagui's tag:'input' Stack would otherwise emit as an invalid lowercase
`textalign` DOM attribute (React warning). Behavior unchanged.

* feat(goodreserve-widget): align layout and colors to the Figma reference

Rework the swap view to match the Figma GoodReserve frames (file
xsk5EiF6CvStA9mtdbA9OR), verified structurally against the Figma API:

Structure:
- Move the header (network pill, blue title, subtitle) above the dark card.
- Replace the Buy/Sell tabs with a single circular swap-direction button
  between the amount cards (preserves both buy and sell, matches Figma).
- Render the confirmation as an anchored bottom-sheet Drawer with a token hero,
  a 50px "Minimum Received" highlight, a details table (incl. Network Fee), and
  a close affordance; render slippage selection as a Drawer too.
- Reorder the success screen to title -> summary -> explorer link -> glow icon.
- Make Transaction Details and FAQ collapsible (chevron); add the second FAQ
  item and a bottom settings/slippage icon; add MAX on the swap-to row.

Colors/typography:
- Pin the exact Figma palette in the widget (card #0C0E15, input #252730,
  badge #33343C, soft #8B91A0, secondary #C1C6D6, heading/MAX #4090FF,
  positive #43E350) via widget-scoped named components, since the shared
  GoodWalletV2 preset tokens differ and altering them is out of scope.
- Detail rows now use 16/500 values and 12/600 labels; PRICE uses PER <SYM>;
  success heading 26/700; amount inputs 34/700.

Also add arrow-down/arrow-up icons to the UI Icon registry for the flip button.

* fix(goodreserve-widget): correct success amount, explorer link, and quote math

Fix remaining adapter and view correctness issues:

- Preserve the swapped output as lastSwapOutput before clearing the quote on
  success, and show it on the success screen instead of the wallet balance
  (the two are different numbers). Adds a regression test.
- Make "View on Explorer" a functional Anchor that links to celoscan / xinfin
  for the tx hash, instead of a dead element.
- Replace window.setTimeout/clearTimeout with the bare globals so the quote
  debounce works under SSR and React Native.
- Compare the input against the balance in BigInt (parseUnits) rather than via
  Number(), removing last-decimal float drift in the insufficient-balance gate.
- Read result.hash (canonical) from the swap result.
- Split the bottom settings/slippage control into its own SettingsButton
  component so it no longer shares the swap-direction sub-theme name.
- Open the confirmation drawer at full height so the hero + 50px highlight +
  details table are not clipped; reset cleanly to buy idle on "Do another swap".

* fix(goodreserve-widget): harden adapter state, callbacks, and add state-coverage tests

- bootstrapSdk no longer depends on state.direction (reads a directionRef), so
  toggling buy/sell after connecting no longer re-initializes the SDK.
- Hold onSwapSuccess/onSwapError in refs so inline host callbacks do not re-fire
  the lifecycle effect on unchanged success/error states.
- Surface the real exitContribution from getReserveStats in the quote, and show
  price impact as N/A instead of a misleading hardcoded ~0.01%.
- Sanitize setMaxAmount (and setInputAmount) through a shared amount.ts helper so
  formatted balances are always parseUnits-safe.
- setSlippagePercent restores the previous status instead of forcing idle_buy,
  preserving sell/quote context.
- Make the confirm-drawer Network Fee chain-aware (CELO vs XDC).
- Add Playwright coverage for the amount-editing and quote-loading states.

All 16 widget Playwright tests pass; build and lint clean.

* fix(goodreserve-widget): resolve idle status, quote freshness, and error recovery

Apply correctness, error-recovery, and contract fixes:

- Rename the idle status idle_buy -> idle across the contract, integration
  manifest, and adapter, since direction is already a separate state field; the
  status no longer reports "buy" while in sell mode.
- Align fixtures with the live adapter: priceImpactPercent is N/A everywhere
  (SDK does not expose it) and renders muted rather than green; regenerate all
  story screenshots so committed evidence matches real output, and add the
  previously missing sdk-initializing.png.
- Wire the existing refresh action to a visible Retry CTA in the quote_error /
  swap_error states so users are no longer stuck behind a disabled button.
- Add a preferredChainId prop so the unsupported-chain CTA can target XDC (or
  any chain) instead of always switching to Celo.
- Guard executeSwap against stale quotes: quotes carry a quoteExpiresAt and a
  swap submitted after expiry is rejected with a refresh prompt instead of
  signing a minReturn derived from an outdated price.
- Clear txHash/lastSwapOutput on direction change so a prior swap result cannot
  leak into the next swap.

All 16 widget Playwright tests pass; build and lint clean.

* refactor(goodreserve-widget): move styling onto the theming contract

Apply high-severity theming and UX fixes (SDK-dependent items
remain blocked until @goodsdks/good-reserve is published):

- H1/H2: replace the hardcoded FIGMA hex map with real, host-themable surfaces.
  The named components now resolve their background/color/shadow from registered
  light_/dark_Reserve* component sub-themes in the preset, and cross-cutting text
  colors use new $reserve* palette tokens. No raw hex remains in the JSX.
  Verified the rendered colors are unchanged (#0C0E15 shell, #252730 cards,
  #4090FF heading) while now being overridable through the normal chain.
- H3: the header pill is a network chip (e.g. "CELO") instead of duplicating the
  "Swap on CELO" heading.
- H4: remove the permanently-"N/A" price impact row (SDK exposes no price impact).
- H5: remove the fabricated "~0.001 CELO/XDC" network fee from the confirm sheet.
- M2: relabel "Final amount received" to "Estimated received" (it is the quote).
- M3: on quote expiry, keep the entered amount and re-quote instead of erroring.
- M7: switchChain now catches rejections (e.g. 4902) and surfaces a message.
- L2: explorer link uses explorer.xdc.org; L3: stable-decimals fallback is
  chain-aware (XDC=6); N5: drop the capabilitySource reference to a non-existent
  SDK export.

All 16 widget Playwright tests pass; ui + widget build and lint clean.

* fix(goodreserve-widget): finish theming migration and fix sub-theme collision

Resolve the theming-regression set:

- C4: give the confirm-hero "to" badge its own ConfirmToBadge component and
  light_/dark_ReserveConfirmToBadge sub-theme (flat, no glow), distinct from the
  96x96 glowing ReserveSuccessIcon they previously shared a name with.
- C5: every named surface declares color: '$color' and the badge glyphs use
  color="$color", so a host override of a sub-theme moves surface + foreground
  together.
- H7: convert the remaining flat-token surfaces to registered sub-themes
  (ReserveSurface for success/FAQ, ReserveSurfaceInner for the confirm highlight,
  ReserveDetailsTable for the confirm table); no inline $surface/$reserveCard.
- H8: the widget is dark-only — defaultTheme is fixed to 'dark' in the contract
  and documented; light_/dark_ pairs are intentionally identical.
- H6: document the reserve palette as mirrored between theme.ts (token source)
  and the preset color map.

All 16 widget Playwright tests pass; ui + widget build and lint clean.

* feat(goodreserve-widget): integrate the real GoodReserve SDK behind a typed seam

Replace the dynamic Function-based loader with a typed lazy import() of
@goodsdks/good-reserve (declared as an optionalDependency, since it is not yet
published). The seam in sdk.ts mirrors the real PR #35 public surface
(GoodReserveSDK / ReserveStats / ReserveTransactionResult), so every adapter
call site is type-checked against the actual contract instead of a loose shadow
type.

- Wire the onHash callback on buy/sell so swap_pending surfaces the submitted
  tx hash before the receipt resolves; read result.hash.
- Scale exitContribution from parts-per-million (/10_000) per the Mento
  convention, instead of the previous incorrect * 100.
- Re-validate the wallet's current chain via a live eth_chainId read before
  signing, rather than trusting the memoized chain flag.
- Guard the empty-input quote effect so it cannot clobber terminal swap states
  (success/error/pending) back to idle.
- Broaden mapReserveError to cover network/timeout/rejection and sanitized
  revert reasons, matching citizen-claim-widget's coverage.
- Add a deterministic fake SDK + EIP-1193 test provider and an injection seam,
  plus a LiveFakeSdk story and a Playwright test that drives the full real
  adapter flow (quote -> confirm -> buy -> success with tx hash) with no
  published SDK and no live RPC. The harness clears the injected fake on unmount.

All 17 widget Playwright tests pass; build and lint clean.

* fix(goodreserve-widget): correct price label, explorer URL, and theming claims

- Fix the swap-rate display: compute price as output-per-input and render it
  consistently as "1 <tokenIn> = <price> <tokenOut>" in both the details row and
  the confirm sheet (previously the two labels described reciprocal quantities
  with the same number).
- Use the canonical XDC explorer (xdcscan.com/tx/<hash>) instead of an
  unverified host/path; Celo stays celoscan.io/tx/.
- Clamp the unsupported-chain switch target to a supported reserve chain so a
  bad preferredChainId can't route to an unsupported network and bounce back.
- Tokenize the network pill border (use $primaryMuted) so the view contains no
  raw color literals.
- Correct the styled-components header comment: primary surface/text come from
  the sub-theme, while secondary text shades are $reserve* tokens (overridable
  at the token layer) — the comment no longer overstates per-sub-theme control.
- Document the exitContribution scaling against the SDK demo's own convention
  (reserveRatio / 10000) rather than asserting an unverified PPM basis.

All 17 widget Playwright tests pass; build and lint clean.

* test(storybook): raise test-runner timeout to avoid cold-start flakes

Add a Storybook test-runner config that sets jest.setTimeout(60000). The default
15s can be exceeded when the dev server compiles a story on first request,
intermittently failing pnpm test:storybook in CI. Additive config (no prior
test-runner setup existed); benefits the whole story suite.

* chore: update test screenshots from latest test execution

* chore(goodreserve-widget): link SDK seam to PR #35, align XDC, drop test-results/

- sdk.ts: cite the source of truth the typed SDK seam mirrors
  (GoodSDKs PR #35, merge commit 53fa2f56947e10e29864c0ab4f22733ca54d3b3a,
  good-reserve head fcd7b278558db9624f2b67a7cbe22c09f662bd32) and the demo
  file that shows the same types in use. Documents the buy/sell arg-order
  asymmetry that the hand-rolled type previously mis-named.
- ReserveSwapView.tsx: align the XDC explorer host with the SDK demo
  (explorer.xdc.org/tx/<hash>); add a source-of-truth comment.
- .gitignore / AGENTS.md: untrack tests/**/test-results/ PNGs (transient
  Playwright run output); the canonical visual evidence is now the curated
  examples/storybook/src/stories/<widget>/screenshots/ set.
- publish checklist: keep the optionalDependencies "*" pin; track
  promotion to a real version as a follow-up once the SDK is published.

* fix(storybook): restore quote-ready-xdc story export

The QuoteReadyXdc story was dropped during a prior SDK-integration revert
and never re-added. The story was referenced by the test suite (which
asserts the XDC network label and a 216.50 G$ output for the buy direction
on chain 50), causing the "quote-ready on XDC" Playwright test to fail with
SB_PREVIEW_API_0009 (NoStoryMatchError).

Re-add the story export, mirroring the existing quote-ready-buy/sell
renderStory pattern, and regenerate the curated screenshot so the committed
evidence reflects the current state.

* fix(goodreserve-widget): revert XDC explorer to xdcscan.com

The XDC explorer URL is rendered in the success screen's "View on
Explorer" link. Revert from explorer.xdc.org to xdcscan.com, which
viem's own chain definition points at as the canonical XDC explorer
and which is the Etherscan-style actively-maintained surface. The
previous BlocksScan-era host was chosen to match the SDK demo;
xdcscan.com is the better default for end users.

* fix(goodreserve-widget): integrate real SDK, decompose view, fix tests

Material refactor of the goodreserve widget based on the bounty feedback:

- Wire the real @goodsdks/good-reserve SDK (now published as 0.1.0) via a
  static import in useGoodReserveAdapter. Delete the typed seam file (sdk.ts),
  the injection function (__setGoodReserveSdkConstructorForTesting), the fake
  SDK fixture, and the LiveFakeSdk story — none of this scaffolding is
  needed now that the package is installable. The adapter uses the SDK's
  getGDBalance helper and erc20ABI export instead of hand-rolled equivalents.

- Decompose ReserveSwapView.tsx into per-state subcomponents:
  ReserveSwapView is a thin dispatcher that routes to SdkInitializingView,
  SwapSuccessView, or MainSwapView. The main swap shell (with its nested
  slippage / confirm Drawers) lives in MainSwapView. The file no longer
  contains a 450-line nested-conditional return.

- Restructure errors.ts away from a long if-chain into a structured
  RESERVE_ERROR_RULES matcher array (most-specific-first) with an extracted
  extractRevertReason helper for sanitized revert reasons. Coverage mirrors
  citizen-claim-widget's humanReadableError; sharing across packages is a
  future maintainer decision.

- Update fixture prices to match the adapter's output-per-input math.
  The previous fixture values used input/output semantics, which had drifted
  out of sync with the adapter (price = outputNum / inputNum) after commit
  d62cbcc corrected the formula. All 5 quote fixtures now display values
  consistent with the live adapter.

- Revert packages/ui/presets.ts and packages/ui/src/theme.ts to origin/main
  to remove the reserve* palette entries and dark_/light_Reserve* sub-themes
  that previously leaked widget-local styling into the shared design system.
  The widget now owns its palette as a FIGMA constant in ReserveSwapView.tsx
  and applies it via widget-scoped named components.

- Track the per-widget tests/<widget>/test-results/ PNGs as committed UI
  evidence baselines (mirrors the established citizen-claim-widget pattern).
  Remove the broad **/test-results/ .gitignore rule so per-widget folders
  can be tracked; the root /test-results/ stays ignored for transient
  Playwright trace/video output.

- Mark the live-adapter E2E test as test.skip — it requires live Celo/XDC
  RPC connectivity which is not available in CI. The 16 mockState-driven
  state-coverage tests continue to verify the state machine and visual
  layout end-to-end.

- Fix ARCHITECTURE.md merge conflict marker (<<<<<<< HEAD) that was left
  over from an earlier merge, and ensure the file ends with a trailing
  newline.

- Repo-level lint blockers (pre-existing in origin/main, required for
  pnpm lint to pass): convert the empty interface HostContextValue to a
  type alias in packages/core/src/provider.tsx, and remove stale
  react-hooks/exhaustive-deps disable directives in citizen-claim-widget
  whose rule is not registered in the shared eslint config.

All 16 widget Playwright tests pass (1 live test skipped per above).
pnpm build (8/8), pnpm lint (13/13), and pnpm test:demo pass clean.

* fix(goodreserve-widget): source chain ids from SDK, use SDK chain validators

- constants.ts: re-export CELO_CHAIN_ID, XDC_CHAIN_ID, getReserveChainFromId
  from @goodsdks/good-reserve so the SDK is the single source of truth for
  these values. Drop the local SUPPORTED_RESERVE_CHAINS array and the 'as
  never' cast site that needed it.
- integration.ts: build the chains list from the SDK exports instead of
  hardcoded [42220, 50] literals.
- useGoodReserveAdapter.ts: replace SUPPORTED_RESERVE_CHAINS.includes checks
  with getReserveChainFromId and GoodReserveSDK.isChainEnvSupported so the
  adapter respects env-specific chain availability (XDC is development-only).
- Drawer.tsx: use Sheet.Overlay/Frame/Handle directly with inline styles and
  registerComponent instead of createComponent-wrapped parts; the wrappers
  broke Sheet's ref forwarding.
- test-results: re-render slippage/confirm screenshots from latest test run.

* chore: update test screenshots

* Revert "chore: update test screenshots"

This reverts commit 75271b7.

* feat: extract drawers, fix transaction success flow, add live wallet test

- Extract SlippageDrawer and ConfirmDrawer into separate components
- Fix transaction success to show immediately after tx hash (don't wait for receipt)
- Add LiveWallet story for real wallet testing with MetaMask
- Enable live SDK test in Playwright test suite
- Fix font size overflow in Swap to section
- Update screenshots for design-system and widget tests

* fix adapter file  to show success immediately when we get the hash

* fix: address review feedback - wait for receipt before success, gate live test, surface tx hash in pending UI

- Wait for transaction receipt before showing success (fixes correctness regression)
- Use onHash to surface submitted hash during swap_pending for immediate feedback
- Gate live test behind GOODRESERVE_LIVE_TEST env var to prevent CI failures
- Add anchor link to view transaction on explorer when tx is submitted
- Remove 100ms race condition in success flow

* fix: update Drawer story test to search in document body for portal-rendered button

The Drawer component renders content in a portal, so the test needs to search
the entire document body instead of just the canvas element to find the Close button.

* fix: update Drawer test to search in document body for portal content

The Drawer component renders in a portal, so the close button is not
within the canvas element. Updated the test to search in document.body
instead of canvas to find the close button.

Also updated test screenshots to reflect current widget state.

* fix: address review feedback - design tokens, hook decomposition, presets revert, screenshots cleanup

- Delete storybook screenshots from examples/; canonical evidence in tests/
- Fix DEFAULT_STABLE_DECIMALS fallback to 18 (XDC=6 was wrong; reserve not on Fuse)
- Revert packages/ui/src/presets.ts to upstream base (governance tokens removed)
- Restore Drawer.tsx to working inline-style version; add reviewer comment explaining
  the createComponent ref-forwarding issue that prevents using Sheet compound parts
- Replace FIGMA.* hardcoded hex constants with design-system token pattern:
  - Add packages/goodreserve-widget/src/config.ts (follows governance-widget pattern)
  - Wire goodReserveWidgetConfig via mergeThemeOverrides in GoodReserveWidget.tsx
  - ReserveSwapView.tsx uses $reserveCard/$primary/$textColor/etc. tokens
  - Wrap all <Anchor> children in <Text> for React Native compatibility
- Decompose 598-line useGoodReserveAdapter into four focused sub-hooks:
  - useReserveBootstrap: wallet integration, SDK init, chain handling, balances
  - useReserveQuote: debounced quote pipeline, BigInt balance gate, display derivation
  - useReserveSwap: stale-quote guard, buy/sell execution, hash tracking
  - useGoodReserveAdapter: thin orchestrator composing the above

* fix: refactor CTA button state using lookup table pattern

- Replace nested conditional ternary logic for the primary CTA button with a clean react lookup table () and helper function () as requested by L03TJ3.

* fix: correct decimals fallback — G$=18 on all reserve chains, stable=chain-aware (XDC=6, Celo=18)

* fix: update E2E tests, align story selectors, and regenerate snapshots

* fix: address review feedback — createComponent ref forwarding, config cleanup, light/dark support

- createComponent: forward refs through the HOC so Sheet compound parts
  (Overlay/Frame/Handle) work without breaking animation/portal contract
- Drawer.tsx: revert to createComponent pattern now that refs are forwarded
- config.ts: remove redundant light_/dark_ sub-theme pairs (already defined
  in createComponent calls via $ token references)
- widgetRuntimeContract.ts: support both light and dark themes, dark as default
- amount.ts: remove sanitizeAmount — inputMode=decimal handles this already
- stories: add InjectedWallet story using shared fixture, keep LiveWallet
- AGENTS.md: fix test-results path to tests/widgets/<widget-name>/

---------

Co-authored-by: Ryjen1 <ryjen1@users.noreply.github.com>
@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
good-widget-react-web Error Error Jul 8, 2026 9:49am

Request Review

L03TJ3 added 4 commits July 8, 2026 16:55
- Remove redundant localized styling config
- Fix input field height and sizing
- Re-add amount sanatizing, wrongfully removed
@L03TJ3 L03TJ3 marked this pull request as ready for review July 8, 2026 16:10
@L03TJ3 L03TJ3 requested a review from a team July 8, 2026 16:10
@L03TJ3 L03TJ3 merged commit 6ba151e into main Jul 8, 2026
2 checks passed
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.

[Plan]: Build GoodReserve Widget — Reserve swap UI backed by GoodReserve SDK

3 participants