Skip to content

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

Open
edehvictor wants to merge 7 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 7 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.

Copy link
Copy Markdown

@pheobeayo pheobeayo left a comment

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.

Copy link
Copy Markdown

@pheobeayo pheobeayo left a comment

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
Copy link
Copy Markdown
Contributor

L03TJ3 commented May 28, 2026

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

Copy link
Copy Markdown

@pheobeayo pheobeayo left a comment

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.

@edehvictor edehvictor requested a review from pheobeayo May 30, 2026 12:40
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