Skip to content

fix(streaming-widget): finalize bounty coverage and evidence#38

Open
edehvictor wants to merge 9 commits into
GoodDollar:copilot/create-streaming-widgetfrom
edehvictor:fix/finalize-streaming-widget-36
Open

fix(streaming-widget): finalize bounty coverage and evidence#38
edehvictor wants to merge 9 commits into
GoodDollar:copilot/create-streaming-widgetfrom
edehvictor:fix/finalize-streaming-widget-36

Conversation

@edehvictor

Copy link
Copy Markdown
Collaborator

Refs #26
Refs #28
Refs #36
Builds on AI PR #31.

Summary

Finalizes the streaming widget bounty work on top of the existing AI PR branch by filling the missing runtime states, deterministic Storybook coverage, Playwright coverage, and screenshot evidence needed for human review.

Changes

  • Kept the widget shell on WidgetTabs for Streams, Pools, and Balances.
  • Added deterministic StreamingWidgetPreview wiring for Storybook and Playwright while preserving the live StreamingWidget adapter path.
  • Added stream history state to the runtime contract, adapter, and UI, including loading, empty, error, populated, and Show more states.
  • Added live GDA pool claim support by reading getClaimableNow from pool contracts and submitting claimAll, with separate pending/success/error state.
  • Strengthened create/update stream UI for invalid input, disconnected wallet, wrong chain, pending, success, and failure states.
  • Preserved injected wallet and custodial fixture stories, and added deterministic stories for loading, empty, error, create/update, pool claim, Base SUP balance/reserve, and non-Base reserve-disabled states.
  • Replaced the streaming-widget Playwright smoke tests with deterministic coverage for tab navigation, stream filters/history, create/update success/failure, pool claim state, reserve visibility by chain, and mobile/desktop layout evidence.
  • Added current screenshot evidence under tests/widgets/streaming-widget/test-results/.
  • Fixed small pre-existing lint blockers outside the widget package so the required full pnpm lint command passes.

Verification

  • pnpm install: Passed
  • pnpm build: Passed
  • pnpm lint: Passed with existing repo warnings only
  • pnpm test:demo tests/widgets/streaming-widget: Passed, 13/13 tests
  • git diff --check HEAD~2..HEAD: Passed, no whitespace errors

Evidence

Playwright screenshots are committed in tests/widgets/streaming-widget/test-results/ and cover:

  • disconnected wallet
  • tab navigation
  • wrong chain
  • loading, empty, error, and populated stream/history states
  • create/update invalid, pending, success, and failure states
  • pool claim idle, pending, success, and error states
  • Base SUP reserve and non-Base reserve-disabled states
  • mobile and desktop populated layouts

Notes

This PR intentionally targets copilot/create-streaming-widget so AI PR #31 can be updated before maintainer review.

@edehvictor

Copy link
Copy Markdown
Collaborator Author

Hi @L03TJ3, PR #38 is ready for review.

I followed the assigned bounty flow, built on top of the existing AI branch, posted the handoff on #36, and included verification/evidence.

Verified:

  • pnpm install
  • pnpm build
  • pnpm lint
  • pnpm test:demo tests/widgets/streaming-widget passing, 13/13

Thanks again for assigning this.

@L03TJ3 L03TJ3 linked an issue May 22, 2026 that may be closed by this pull request
12 tasks
@L03TJ3 L03TJ3 removed this from GoodBounties May 22, 2026
@L03TJ3 L03TJ3 requested review from a team, L03TJ3 and pheobeayo and removed request for a team and L03TJ3 May 22, 2026 12:23
setStreams(result.map((s) => toStreamListItem(s, address as Address)))
const normalizedStreams = result.map((s) => toStreamListItem(s, address as Address))
setStreams(normalizedStreams)
setStreamHistory(normalizedStreams)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work wiring up the history state machine; the loading/empty/error/populated states all render correctly. One thing worth flagging for a follow-up: setStreamHistory here is receiving the same result as setStreams, because getActiveStreams only returns currently active (non-zero flowRate) streams. Terminated streams would need a separate subgraph query, something like a getPastStreams or getStreamHistory call, to populate a true history list. As it stands, the history section in the UI is showing the same data as the active streams list, and the "Show more" pagination is working against that same pool. Not blocking for this bounty since the UI states are all covered, but worth picking up as a follow-up so the history tab reflects actual past streams.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, agreed. I kept this as a follow-up because the current SDK path only exposes active streams here, and added a fixture note so the Storybook history data is easy to split once a dedicated past-stream query exists.

connect,
switchChain,
refreshStreams: fetchStreams,
refreshStreamHistory: fetchStreams,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small note to go alongside the history comment above, refreshStreamHistory is pointing to fetchStreams here, so both refresh actions hit the same underlying call. When a dedicated history fetch is added, this line will need to point to the new callback. Easy to update then

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Leaving refreshStreamHistory pointed at the current SDK-backed fetch for now; when a dedicated history callback lands this should switch to that separate fetch.

Comment on lines +325 to +338
const poolsWithClaimable = await Promise.all(
normalizedPools.map(async (pool) => {
try {
const [claimableAmount] = await viemClients.publicClient.readContract({
address: pool.poolId,
abi: GDA_POOL_CLAIM_ABI,
functionName: 'getClaimableNow',
args: [address as Address],
})

return {
...pool,
claimableAmount: claimableAmount > 0n ? claimableAmount : 0n,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getClaimableNow read looks good, and the claimableAmount > 0n guard correctly handles the signed int256 return. One thing to watch: if this readContract call throws (RPC timeout, contract revert, etc.), the catch silently returns the pool with claimableAmount still at 0n from the toPoolMembershipItem fallback. In the UI, that makes the Claim button appear disabled with no explanation. A user with a real claimable balance would just see a greyed-out button and have no way to tell whether it's a data error or genuinely nothing to claim. Could we add a claimableAmountError flag to PoolMembershipItem and surface a small "Could not load claimable amount, tap to retry" note in PoolCard when the read fails?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3494404: PoolMembershipItem now carries claimableAmountError, successful reads clear it, and failed getClaimableNow reads set it instead of silently falling back to an unexplained 0n state.

!state.streamHistoryError &&
recentStreams.length === 0 && (
<EmptyStateCard>
<Text secondary center>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the UI side of the silent-failure issue flagged in the adapter's getClaimableNow catch block. When that read fails, claimableAmount stays at 0n, so canClaim is false and the button renders disabled with no explanation. Pairing this with the adapter fix, surfacing a claimableAmountError on the pool item, would let us show something like "Could not load claimable amount" here instead of just a greyed-out button.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3494404: PoolCard now surfaces the claimable amount load failure with a small inline “Could not load claimable amount. Tap to retry” note wired to refreshPools, while keeping Claim disabled until the amount is known.

// Story shell — renders the widget inside a fixed-width container that mirrors
// the GoodWalletV2 sidebar / bottom-sheet form factor.
// ---------------------------------------------------------------------------
const DEMO_ADDRESS = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is DEMO_ADDRESS intentionally Vitalik's well-known address? That's fine as a fixture, just worth confirming it's deliberate and that this address doesn't have any unexpected behaviour on Celo or Base in the test environment. If it's just a placeholder, a clearly synthetic address like 0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef might be less likely to confuse future contributors.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Replaced the well-known address with a clearly synthetic fixture address in 3494404 to avoid confusing future contributors.

},
]

const sampleStreamHistory: StreamListItem[] = [

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sampleStreamHistory spreads sampleStreams at the top, which is consistent with the current adapter behaviour (both lists come from the same getActiveStreams call). When the adapter is updated to fetch real history separately, this fixture will need updating too. In a proper history model, the two lists should be disjoint, since active streams are ongoing and past streams are terminated. Could we add a comment here noting that the fixture should diverge once the adapter is corrected? Makes the follow-up easier to find.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the fixture comment in 3494404, noting that sampleStreamHistory mirrors the current SDK-backed adapter and should diverge once past-stream history is fetched separately.

@pheobeayo pheobeayo left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Impressive work so far @edehvictor , Kindly check through the comments and fix!

@edehvictor edehvictor requested a review from pheobeayo May 25, 2026 17:01
export const WrongChain: Story = {
render: () => (
<PreviewStoryShell
adapter={createAdapter({

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good story addition, and the UI confirms the error message renders correctly. One follow-up from looking at the UI: "Tap to retry" is currently plain text with no press handler attached. A user in this state has no way to actually trigger a retry , they can see the message, but can't act on it. Could we wire it to actions.refreshPools, either as a pressable Text or by swapping the Claim button label to "Retry" when claimableAmountError is true? The state is surfaced correctly; it just needs the action to complete the UX loop.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9213ba7: the passive retry text is now a real secondary Retry button wired to refreshPools, and the Storybook fixture re-enters the pools loading state when it is clicked.

await saveScreenshot(page, 'sw-15-pool-claim-error')

await gotoStory(page, 'pool-claimable-amount-error')
await expectBodyToContain(page, ['Could not load claimable amount.', 'Tap to retry'])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Playwright assertion correctly verifies the error message renders. Once "Tap to retry" is made pressable, consider adding a page.getByText('Tap to retry').click() step here and asserting the pools section re-enters a loading state, to close the loop on the retry flow end-to-end.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9213ba7: the Playwright flow now clicks Retry and asserts the pools section returns to Loading pool memberships, with a new screenshot at sw-21-pool-claimable-retry-loading.png.

@pheobeayo pheobeayo left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good iteration on the review feedback. The claimableAmountError fix is a well-executed end-to-end; contract, adapter, fixture, story, Playwright test, and screenshot, all consistent. The DEMO_ADDRESS fix and the honest comment on sampleStreamHistory are both the right calls. The one remaining actionable item from this pass: "Tap to retry" in the claimable amount error state needs a press handler wired to refreshPools, otherwise it's text that implies interactivity without delivering it. Happy to approve once that's addressed or confirmed as a known follow-up.

@L03TJ3 L03TJ3 self-requested a review May 28, 2026 10:58

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buttons across all screens are unreasonably large. please verify against production wallet-v2 (https://goodwallet.xyz) and apply fixes. I think this should be fixed in the UI components

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9213ba7: reduced the shared UI Button scale so widget actions align better with the GoodWalletV2 control sizing, and refreshed the affected screenshots across the streaming-widget states.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amounts like this should always be formatted to at most 6 digits.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9213ba7: token amounts now go through a shared formatter capped at 6 significant digits, while preserving compact formatting for larger values.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes more sense to have for history its own tab, does not make sense to have it show up in all states of the main view.
Active streams can be left where it is

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

amounts should always have the token symbol displayed for clarity

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9213ba7: stream history now has its own History tab, active streams stay on Streams, and stream/form amounts now display token symbols for clarity.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

little organization like displaying the buttons on 1 line.
also, amounts should include token symbols.
disconnect could use a distinct color (likely red) vs 'claiming'

if already connected does not make sense to show claim button.

  • disconnected but incoming: can claim/make stream active and update active balance
  • connected: should only show disconnect

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9213ba7: pool actions are organized on one row, claimable and claimed amounts include token symbols, disconnect uses the error color, and connected pools now only show Disconnect while disconnected claimable pools show Claim and Connect.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@edehvictor There is no claim vs connect afaik
what do you base that on?

to the best of my knowledge when there is an incoming stream from certain pools you have to 'accept' or 'connect' to the stream in order to see your active balance updated.

Can you confirm?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@L03TJ3 thanks for checking this, and happy to adjust here to whatever flow you think is best for GoodDollar.

What I based the current split on is the Superfluid GDA member flow docs: connectPool and claimAll are separate protocol actions. My understanding is that connectPool is the path for accepting/connecting the pool so distributions update the active balance automatically going forward, while claimAll manually claims any accumulated claimable balance for a member that is not connected yet.

Reference: https://sdk.superfluid.pro/docs/use-cases/connect-gda-pool

That said, if the preferred UX is to avoid exposing a separate manual Claim action here and only guide users through Connect for incoming pool streams, I can update the widget in that direction. I want this to match the GoodDollar flow rather than overfit the generic protocol examples.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The flag 'staked' cannot be used for the global reserve.
The reserve can hold both staked and unstaked SUP.

can we fetch both using the sdk?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reserve should also show its address and a link to...
explorer.superfluid for now

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9213ba7: the reserve label no longer says staked globally. On Base, the adapter combines subgraph staked SUP with SDK getSuperTokenBalance for unstaked SUP per reserve locker, and the UI shows total reserve, available, staked, locker address, and a Superfluid Explorer link.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apply padding properly, content looks weird if its tight against the sides of the screen

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spacing should be consistently applied throughout a widget. most screens look fine so not sure why this particular screenshot or state shows different then the other ones

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9213ba7: normalized the widget shell padding and Storybook wrapper sizing so mobile content has consistent breathing room across states. The refreshed mobile evidence is in sw-18-mobile-populated.png.

@L03TJ3

L03TJ3 commented May 28, 2026

Copy link
Copy Markdown
Contributor

Reference to the button comment @edehvictor
image

@GoodDollar GoodDollar deleted a comment from nomsoscript May 28, 2026
@edehvictor

Copy link
Copy Markdown
Collaborator Author

Hi @L03TJ3 and @pheobeayo , thanks for the fresh review feedback. I’ve seen the comments and will work through the fixes carefully, then reply directly in each review thread once the updates are pushed.

@edehvictor edehvictor requested review from a team, L03TJ3 and pheobeayo May 29, 2026 09:30
Comment on lines 106 to 124
receiver: '0x7777777777777777777777777777777777777777',
token: DEMO_TOKEN,
flowRate: 3858024691358n,
streamedSoFar: 1400000000000000000n,
createdAtTimestamp: 1766880000,
updatedAtTimestamp: 1766966400,
direction: 'outgoing',
},
]

const samplePools: PoolMembershipItem[] = [
{
poolId: DEMO_POOL,
poolToken: DEMO_TOKEN,
totalUnits: 250000000000000000000n,
claimableAmount: 12500000000000000000n,
claimableAmountError: false,
totalAmountClaimed: 48000000000000000000n,
isConnected: false,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These three stories were changed to isConnected: false but the Claim section in PoolCard only renders when pool.isConnected is true. With this change, the pending spinner, Done badge, and Failed badge won't appear; the lifecycle states these stories exist to demonstrate are hidden. Could we revert isConnected to true for PoolClaimPending, PoolClaimSuccess, and PoolClaimError? PoolClaimState (idle) is fine as false if the intent is to show the claimable amount before connecting.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I see this. I'm checking it against the connected/disconnected pool behavior from the earlier review and will tighten the stories/tests so the pending, success, and error claim lifecycle states are visible without regressing the connected-pool action rules.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, fixed in d7ec12d. PoolClaimPending, PoolClaimSuccess, and PoolClaimError are back to isConnected: true. The connected pool row now renders the claim lifecycle badge while still only exposing Disconnect, so there are no Claim/Connect actions in connected membership states.

Updated the Playwright assertions and refreshed sw-13-pool-claim-pending.png, sw-14-pool-claim-success.png, and sw-15-pool-claim-error.png as evidence. Verification: corepack pnpm test:demo tests/widgets/streaming-widget (13/13), corepack pnpm lint, and corepack pnpm build.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it

@pheobeayo pheobeayo left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The interactive retry story, the History tab separation in Playwright, the SUP reserve locker breakdown, and the layout fix are all solid. One required change before approving: isConnected: false on PoolClaimPending, PoolClaimSuccess, and PoolClaimError is a regression introduced; the Claim section won't render in those stories, so the lifecycle badges they're meant to cover are invisible. Please revert those three to isConnected: true.

The UI reviews by @L03TJ3 have also been addressed

@edehvictor

Copy link
Copy Markdown
Collaborator Author

@pheobeayo thanks for the latest review summary. Fixed the required change in d7ec12d: PoolClaimPending, PoolClaimSuccess, and PoolClaimError are back to isConnected: true.

I also kept the connected-pool action rule intact: those connected lifecycle states show the Pending / Done / Failed badge plus only the Disconnect action, with no Claim or Connect action rendered. Refreshed evidence: sw-13-pool-claim-pending.png, sw-14-pool-claim-success.png, and sw-15-pool-claim-error.png.

Verification: corepack pnpm test:demo tests/widgets/streaming-widget (13/13), corepack pnpm lint, and corepack pnpm build.

@edehvictor edehvictor requested a review from pheobeayo May 29, 2026 12:58
Comment on lines +513 to +514
// Claim lifecycle stories stay connected to match the review fixture contract while
// keeping connected memberships limited to the Disconnect action.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is a bit ambiguous: "keeping connected memberships limited to the Disconnect action" doesn't quite match the current behaviour since connected pools now show both Disconnect and Claim. Something like // Claim lifecycle stories use isConnected: true so the claim section and write status badges render correctly. would be clearer.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, good call. Updated this in ec3aa93 to use the clearer wording:

// Claim lifecycle stories use isConnected: true so write status badges render correctly.

No behavior change in this commit, just the Storybook comment clarification.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, perfect

@edehvictor edehvictor requested a review from pheobeayo May 30, 2026 12:40
@pheobeayo

Copy link
Copy Markdown

@L03TJ3 Kindly review changes

@github-project-automation github-project-automation Bot moved this to In Progress in GoodBounties Jun 12, 2026
@L03TJ3 L03TJ3 moved this from In Progress to In Review in GoodBounties Jun 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert unrelated changes

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in ba49980. I restored this file to upstream/main, so the citizen-claim changes are no longer part of the PR diff.

// Auto-refresh claim status whenever wallet connection or chain changes
useEffect(() => {
void loadClaimStatus()
// eslint-disable-next-line react-hooks/exhaustive-deps

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert unrelated changes

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in ba49980. This was reverted with the rest of the unrelated citizen-claim changes so the final PR diff stays scoped to the streaming widget work.

useEffect(() => {
const id = setInterval(() => setTimeLeft(getTimeLeft()), 1000)
return () => clearInterval(id)
// eslint-disable-next-line react-hooks/exhaustive-deps

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert unrelated changes

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in ba49980. This unrelated citizen-claim edit has been restored out of the PR; no claim-widget lint-only changes remain in the final diff.

@@ -30,6 +30,26 @@ import type {
WriteStatus,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separation of concerns, something AI is not by default very good at and tends to just dump everything in one file.

Some pointers to clean this up and make it more maintainable:

  1. Split the adapter by responsibility, not by UI tab.

    • Right now adapter.ts mixes wallet/chain wiring, SDK construction, fetch flows, write flows, validation, and state composition.
    • separate internal hooks or modules such as:
      • useStreamingClients
      • useStreamingData
      • useStreamingWrites
      • useStreamingForm
  2. Move domain transforms and validation out of the hook.

    • toStreamListItem, toPoolMembershipItem, validateSetStreamForm, and humanReadableError can be isolated into small domain helpers.
  3. Replace the many separate useState calls with structured state.

    • The adapter currently manages a lot of tightly related flags:
      • streams/loading/error
      • history/loading/error
      • pools/loading/error
      • balance/loading/error
      • reserve/loading/error
      • set-stream status/error/hash
      • per-pool connect/claim status/error
    • Look into useReducer
  4. Introduce a small async-resource pattern.

    • The repeated loading -> try -> set data -> catch -> set error -> finally pattern in adapter.ts through adapter.ts is a big readability drag.
    • A helper like runResourceAction() or createAsyncResource() can standardize:
      • loading state
      • error mapping
      • stale-result protection
      • optional success side effects
  5. Separate “fetch orchestration” from “mutation orchestration.”

    • Fetches:
      • streams
      • pools
      • balance
      • reserve
    • Mutations:
      • set stream
      • connect/disconnect pool
      • claim pool
    • These have different lifecycles and should not live in one flat region of the file.
  6. Should not use one effect as the “reset-and-fetch everything” gate.

    • The wallet/chain effect in adapter.ts currently clears state and triggers all fetches.
    • That is hard to review because it couples three behaviors:
      • disconnect cleanup
      • chain validity gating
      • initial refetching
    • Better pattern:
      • one effect for reset on disconnect/wrong chain
      • one effect for fetch-on-ready
      • optionally a single refreshAll() helper used by both
    • This makes lifecycle behavior easier to verify.
  7. Reduce the adapter’s surface area by moving UI-local state back into the UI.

    • StreamsTab keeps direction and showForm locally, which is good.
    • That same principle should apply more broadly: if something is only used to render a tab, keep it in the tab component, not in the adapter.
    • The adapter should expose data and commands, not UI unless they’re truly shared.
  8. Extract tab-specific view components into separate files.

    • StreamingWidget.tsx is also doing a lot:
      • streams tab
      • history tab
      • pools tab
      • balances tab
      • all row/card subcomponents
    • A good cleanup would be:
      • components/StreamsTab.tsx
      • components/HistoryTab.tsx
      • components/PoolsTab.tsx
      • components/BalancesTab.tsx
      • components/cards.tsx or per-row components
  9. Introduce a simple state contract per tab.

    • Each tab should accept a minimal props object, not the entire adapter state.
    • Example direction:
      • Streams tab gets streams, loading, error, refresh, setStreamForm, submitSetStream
      • Pools tab gets pools, pool mutation handlers, and pool-specific status maps

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Lewis, done in ba49980. The adapter is now split by responsibility under packages/streaming-widget/src/adapter: clients, data/resource fetching, set-stream form/writes, pool writes, domain helpers, and chain helpers. Fetch and write state now use reducers plus small async-resource helpers, and the reset-on-disconnect/wrong-chain effect is separate from fetch-on-ready.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. sup reserve balance 'only on base', but no way to switch chain (using metamask it does not use the active chain)
    -- seems to be it only supports the chain that is selected during connection? it should request connection for all supported chains.

but seeing this now:
since G$ is exclusively on Celo for streaming, SUP is the only thing on base. It might not make sense to have someone switch to base in order to see it. we only want to display their main balance and reserve balance anyway

  • the balance can just use a read-only client for base, to display the reserve and sup token balance (including potentially incoming streams).
    We are not going to support SUP at this stage for interaction so:
  • SUP Balance should have an indicator/external link 'To see your active SUP streams visit: app.superfluid.org'. maybe as smaller subtitle to the balance? with a clear external link icon and the copy.
  • SUP Reserve balance can have the same indicator. to redirect a user to view their sup-reserve you can use the superfluid link like: https://app.superfluid.org/?view="their-reserve-address-here"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in ba49980. SUP balance and reserve now use read-only Base clients, so the widget no longer requires switching the active wallet chain just to display SUP data. I also added the Superfluid app link on the SUP balance and the reserve link with the reserve locker address when present.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Just a cleaner approach, can you chop up StreamingWidget.tsx into smaller/modularized files: hooks/utils/separate components.
    I also think we can remove a couple of unneccesary wrappers. (Eg: streamingwidgetinner/streamingwidgetview flow)

It can be inline with the refactor adapater.
Logically it seems alright, its really just about splitting into smaller dedicated components.
also in line with the adapter comment: some state that is in the adapter can be perfectly handled in the UI and does not need to be shared by a global adapter

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Lewis, done across ba49980 and the small follow-up fcab07b. StreamingWidget.tsx is now mostly the provider/runtime shell, while the tabs and cards live in focused component files with narrow props. UI-local state stays in the relevant tab where possible.

While re-checking the screenshots after the refactor, I also caught and fixed a dark-theme contrast issue in fcab07b, then refreshed the streaming-widget screenshot evidence.

Verification on the final state: pnpm install --frozen-lockfile, pnpm build, @goodwidget/streaming-widget lint/build, and pnpm test:demo tests/widgets/streaming-widget all pass. Full repo lint still stops on existing non-streaming core/citizen lint issues, so I left those out of this scoped PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. period selection should only show per month, per day, per year.
    -- the 'About' indication should be per day if month or year is selected

  2. injected wallet story input fields are not editable

  3. SUP is a production token, the story should use production environment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in ba49980. The period selector is limited to per day, per month, and per year; the About helper normalizes the estimate to per-day; the injected-wallet fixture form is editable; and the SUP story now uses the production environment.

initialStreamsFormOpen?: boolean
}

export function StreamingWidgetPreview({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. what is the purpose of streamingwidgetpreview vs the regular streamingwidget export?
    I think the original purpose was for just supporting the 'fixed-state' previews, but not sure this additional 'preview' rapper is actually needed?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I kept StreamingWidgetPreview, but narrowed its purpose: it is only the deterministic fixed-state render path for Storybook and Playwright fixtures. StreamingWidget remains the provider/SDK-backed export used by real integrations. I added a short code comment so that distinction is clear, and I am happy to collapse it later if you prefer the fixtures to use a different pattern.

@github-project-automation github-project-automation Bot moved this from In Review to In Progress in GoodBounties Jun 12, 2026
@L03TJ3 L03TJ3 moved this from In Progress to In Review in GoodBounties Jun 12, 2026
@L03TJ3 L03TJ3 removed this from GoodBounties Jun 12, 2026
@edehvictor

Copy link
Copy Markdown
Collaborator Author

Thanks @L03TJ3

I pushed the cleanup pass in ba49980, plus a small final visual QA fix in fcab07b.

Main updates:

  • Reverted unrelated non-streaming changes so the PR diff is scoped again.
  • Split the streaming adapter into smaller responsibility-focused modules.
  • Split the widget UI into focused tab/card components with narrower props.
  • Updated SUP/Base handling to use read-only Base data with Superfluid links.
  • Tightened the create/update form period options and Storybook fixtures.
  • Fixed a dark-theme contrast issue found during final screenshot QA.
  • Refreshed the streaming-widget Playwright screenshot evidence.

Verification:

  • pnpm install --frozen-lockfile: Passed
  • pnpm build: Passed
  • pnpm --filter @goodwidget/streaming-widget lint: Passed
  • pnpm --filter @goodwidget/streaming-widget build: Passed
  • pnpm test:demo tests/widgets/streaming-widget: Passed, 13/13

One note: full repo pnpm lint still stops on existing non-streaming core/citizen lint issues after the unrelated changes were reverted, so I left those out of this scoped PR.

Happy to adjust anything else to match the preferred GoodDollar flow.

@edehvictor edehvictor requested a review from L03TJ3 June 12, 2026 23:39
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.

[GoodBounty] Finalize Streaming Widget PR

3 participants