Feat/finalize goodreserve widget#51
Open
Ryjen1 wants to merge 21 commits into
Open
Conversation
Align the GoodReserve widget with the issue GoodDollar#15 references and fix concrete implementation gaps in PR GoodDollar#39. Widget: - Map raw stable/G$ balances onto direction-aware in/out slots so sell-mode shows the correct "from" balance and MAX target. - Derive token symbols and the network label dynamically for Celo (42220) and XDC (50); show an explicit unsupported-chain label. - Key swap success/error host callbacks to discrete lifecycle fields instead of the whole state object to stop spurious re-fires on balance/quote updates. - Rework ReserveSwapView to the Figma structure: header pill + title, token-badge amount cards, transaction-detail rows, dynamic CTA labels, redesigned confirm drawer with a press-to-confirm button, and a dedicated success screen. Coverage: - Add XDC quote-ready fixture/story and align existing fixtures. - Rewrite Playwright states spec to assert rendered text, add confirm/slippage/ XDC coverage, retry cold-start navigation, and emit committed screenshots. - Regenerate Storybook story screenshots. Repo blockers (out of widget scope, required to pass verification commands): - core: replace empty HostContextValue interface with a type alias (lint error). - citizen-claim-widget: drop stale react-hooks/exhaustive-deps disables for a rule the shared eslint config does not register (lint error). - ui Drawer: use Sheet.Overlay/Frame/Handle directly to preserve Sheet ref forwarding (fixes "Function components cannot be given refs") and remove a debug console.log; fix the Drawer story to query the sheet portal.
- 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.
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".
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).
…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.
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.
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.
…uote 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".
…te-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.
…ror 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.
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.
…ollision 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.
… 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 GoodDollar#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.
…ng 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.
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.
Merges the upstream 'add: extend goodwallet-v2 preset with light theme' commit into the PR branch. Conflict resolution (packages/ui/src/presets.ts): - Kept both the GoodReserve swap widget palette tokens (reserve*) introduced on this branch AND the new Governance light-mode tokens (governance*) introduced upstream. Both token namespaces are non-overlapping and required. Follow-up fix (tests/design-system/smoke.spec.ts): - The 3 ClaimWidget/CobaltBrand, ClaimWidget/TealBrand, and ClaimWidget/Default test cases in smoke.spec.ts referenced story IDs that no longer exist after the upstream commit renamed the story meta title from 'Theme/ClaimWidgetThemeDemo' to 'Theme/ClaimWidgetThemeDemo-Light' and removed the Cobalt/Teal brand export variants. - Updated ClaimWidget/Default to navigate to the correct story ID (theme-claimwidgetthemedemo-light--default) and removed the now-deleted Cobalt/Teal brand test cases, leaving the spec at 6 tests (all pass). Updated test screenshots from the post-merge run to reflect the merged light-theme changes visible in the design-system stories.
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.
Fixed:
@goodsdks/good-reservelibrary by creating a clean typed seam (sdk.ts), keeping direct SDK/blockchain interactions decoupled from the adapter state machine.sdk-initializingandquote-ready-xdcstates.Verified:
pnpm build&pnpm lint).Evidence:
grw-01togrw-16) in the Playwright snapshot suite undertests/widgets/goodreserve-widget/test-results/.examples/storybook/src/stories/goodreserve-widget/screenshots/.Remaining risks:
@goodsdks/good-reservedependency is imported dynamically as it hasn't been published to npm yet. Playwright runs use a mocked provider; full end-to-end network validation should be performed in staging once the SDK is released