Skip to content

Add Superfluid Campaign GoodWidget - #130

Open
goodbounties-nanoclaw-agent[bot] wants to merge 19 commits into
mainfrom
feat/superfluid-campaign-widget
Open

Add Superfluid Campaign GoodWidget#130
goodbounties-nanoclaw-agent[bot] wants to merge 19 commits into
mainfrom
feat/superfluid-campaign-widget

Conversation

@goodbounties-nanoclaw-agent

Copy link
Copy Markdown

Summary

Implements the Superfluid Ecosystem Rewards campaign widget planned in #129, for parent feature #127.

  • New packages/superfluid-campaign-widget: header, two reward-pool action sections (GoodDollar actions, ecosystem funding actions), a leaderboard view, and the campaign FAQ, rendered against a static mock dataset for this phase.
  • Disconnected-state "Connect wallet" CTA in the content view, gated on useWallet().isConnected, per [Feature]: Add Superfluid Campaign GoodWidget #127 acceptance criteria.
  • Reusable Accordion and ProgressBar added to packages/ui (used by the FAQ and pool-progress sections; assessed as generic enough to belong in the shared package rather than the widget itself).
  • Additive initialTab prop on citizen-claim-widget and additive initialView prop on SuperfluidCampaignWidget, so Storybook/QA fixtures and deep links can land directly on a given view.
  • element.ts/register.ts web-component bridge, mirroring the existing streaming-widget pattern.
  • Storybook coverage under examples/storybook: a manual injected-wallet showcase story, four QA fixture stories (disconnected/connected x content/leaderboard) using the existing custodial-wallet fixture pattern from citizen-claim-widget, and a doc page.

Known gaps flagged for follow-up (not silently worked around)

  • ACTIVITY_ICON_MAP's exact icon names aren't yet in @goodwidget/ui's Icon registry; the closest already-registered glyphs are substituted until the registry is extended.
  • The leaderboard's search box filters the static fixture locally; pagination is a disabled placeholder pending a real leaderboard API.

Test plan

  • pnpm build (turbo) succeeds across all packages/apps
  • eslint clean on all new/changed files in this PR's scope
  • tsc --noEmit clean on all new/changed files in this PR's scope
  • Storybook static build succeeds; 4 QA fixture screenshots captured and manually compared against the two approved [Feature]: Add Superfluid Campaign GoodWidget #127 mockups (disconnected content, connected content, leaderboard) at both narrow (480px) and wide (900px) viewports
  • Reviewer: verify against live [Feature]: Add Superfluid Campaign GoodWidget #127 mockups once more in a running Storybook (pnpm storybook)

🤖 Generated with Claude Code

Implements the widget from #129 (plan for #127): a
reusable GoodWidget presenting the Superfluid Ecosystem Rewards campaign —
header, two reward-pool action sections (GoodDollar actions, ecosystem
funding actions), a leaderboard view, and the campaign FAQ, against a
static mock dataset for this phase.

- New packages/superfluid-campaign-widget: presentational components,
  widgetRuntimeContract, mock dataset, and the element/register web
  component bridge (mirrors the streaming-widget bridge pattern).
- Adds a disconnected-state "Connect wallet" CTA in the content view,
  gated on useWallet().isConnected, matching the #127 acceptance criteria.
- Adds Accordion and ProgressBar to packages/ui (reusable, used by the
  FAQ and pool-progress sections).
- Adds an additive initialTab prop to citizen-claim-widget and an
  additive initialView prop to SuperfluidCampaignWidget, so Storybook/QA
  fixtures and deep links can land directly on a given view.
- Adds Storybook coverage under examples/storybook: a manual injected-
  wallet showcase story, four QA fixture stories (disconnected/connected
  x content/leaderboard) using the existing custodial-wallet fixture
  pattern from citizen-claim-widget, and a doc page that explicitly flags
  two known gaps for this phase (activity icon glyph registry, live
  leaderboard search/pagination).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@goodbounties-nanoclaw-agent
goodbounties-nanoclaw-agent Bot requested review from a team, L03TJ3 and Copilot July 29, 2026 08:12

Copilot AI left a comment

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.

Pull request overview

Note

Copilot could not run the full agentic suite for this review because it was automatically requested on a bot-authored pull request. Request a review from Copilot under Reviewers to retry with the full agentic suite. Improved support for bot-authored pull requests is coming soon.

Adds a new “Superfluid campaign” widget package and introduces shared UI primitives needed to render campaign progress and FAQ content, while extending CitizenClaimWidget to support deep-linking into a specific tab.

Changes:

  • Export new ProgressBar and Accordion components from @goodwidget/ui.
  • Add @goodwidget/superfluid-campaign-widget package (runtime contract, mock data, widget UI, custom element + registration, tsup build config).
  • Add initialTab support to CitizenClaimWidget to open the embedded widget directly on Claim/Invite tabs.

Reviewed changes

Copilot reviewed 29 out of 30 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
packages/ui/src/index.ts Exposes new UI primitives (ProgressBar, Accordion) from the UI package entrypoint.
packages/ui/src/components/ProgressBar.tsx Implements labeled progress bar used by campaign pools/leaderboard summary.
packages/ui/src/components/Accordion.tsx Implements shared accordion primitive used for Superfluid FAQ.
packages/superfluid-campaign-widget/tsup.config.ts Adds build configuration for the new widget package (esm/cjs + dts).
packages/superfluid-campaign-widget/tsconfig.json Adds local TS config + workspace path aliases for development.
packages/superfluid-campaign-widget/tsconfig.build.json Adds build-only TS config used by tsup.
packages/superfluid-campaign-widget/src/widgetRuntimeContract.ts Defines the public runtime contract types/constants for the widget.
packages/superfluid-campaign-widget/src/mockData.ts Provides static mock data fixtures for content + leaderboard + FAQ.
packages/superfluid-campaign-widget/src/index.ts Exports public types/constants/mock data and the widget component.
packages/superfluid-campaign-widget/src/element.ts Wraps the widget as a Custom Element via createMiniAppElement.
packages/superfluid-campaign-widget/src/register.ts Registers a default custom element tag and auto-registers on import.
packages/superfluid-campaign-widget/src/components/* Implements campaign header, reward pools, leaderboard views, FAQ wrapper, and icon rendering.
packages/superfluid-campaign-widget/src/SuperfluidCampaignWidget.tsx Composes the full campaign runtime, view routing, and CTA handling including embedded CitizenClaimWidget.
packages/superfluid-campaign-widget/package.json Declares the new package, build outputs, and exports map.
packages/citizen-claim-widget/src/widgetRuntimeContract.ts Adds CitizenClaimTab and initialTab to the widget props contract.
packages/citizen-claim-widget/src/CitizenClaimWidget.tsx Wires initialTab into internal tab state.
examples/storybook/src/stories/superfluid-campaign-widget/* Adds showcase + QA stories/docs for the new widget.
examples/storybook/src/stories/helpers/superfluidCampaignWidgetStories.tsx Adds helper shells/fixtures to render connected/disconnected states.
examples/storybook/package.json Adds the new widget package as a Storybook dependency.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

if (embeddedClaimTab) {
return (
<YStack gap="$3" width="100%">
<CitizenClaimWidget environment={citizenClaimEnvironment} initialTab={embeddedClaimTab} />
Comment on lines +95 to +107
<AccordionHeader isOpen={isOpen} onPress={() => toggleItem(item.id)}>
<Text flex={1}>{item.title}</Text>
<Icon
name="chevron-down"
size="sm"
color="muted"
style={{
transform: isOpen ? 'rotate(180deg)' : 'rotate(0deg)',
transition: 'transform 150ms ease',
}}
/>
</AccordionHeader>
{isOpen && <AccordionContent>{item.content}</AccordionContent>}
Comment on lines +47 to +57
<YStack width="100%" gap="$1">
{label && (
<XStack justifyContent="space-between" alignItems="center">
<Text variant="label">{label}</Text>
<Text variant="caption">{Math.round(percentage)}%</Text>
</XStack>
)}
<ProgressTrack>
<ProgressFill variant={variant} width={`${percentage}%`} />
</ProgressTrack>
</YStack>
Comment on lines +20 to +27
export function register(tagName: string = DEFAULT_TAG_NAME): string {
if (!customElements.get(tagName)) {
customElements.define(tagName, SuperfluidCampaignWidgetElement)
}
return tagName
}

register()
Comment on lines +8 to +15
const ACTIVITY_ICON_NAME_FALLBACK: Record<CampaignActionMockData['activity'], IconName> = {
'claim-ubi': 'check',
'invite-users': 'user',
'flow-state-vote': 'arrow-up',
'flow-state-funding': 'arrows-left-right',
'gardens-donation': 'wallet',
'gardens-funding': 'refresh',
}
@@ -0,0 +1,59 @@
import React from 'react'
import { Text } from './Text'
import { XStack, YStack } from '../components-test/Stacks'
import type { ReactNode } from 'react'
import { Icon } from './Icon'
import { Text } from './Text'
import { XStack, YStack } from '../components-test/Stacks'
@sirpy

sirpy commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

@copilot address issues found in review. Also why aren't there any tests and screenshots of the widget?

…, provider passthrough, add Playwright tests

Co-authored-by: sirpy <671095+sirpy@users.noreply.github.com>

Copilot AI commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

@copilot address issues found in review. Also why aren't there any tests and screenshots of the widget?

All review issues addressed in commit Address review issues: ARIA, accessibility, SSR guard, dedup fallback, provider passthrough, add Playwright tests:

  1. components-test importsAccordion.tsx and ProgressBar.tsx now import XStack/YStack directly from tamagui (same as Card.ts).
  2. ProgressBar ARIA — added role="progressbar", aria-valuenow, aria-valuemin={0}, aria-valuemax={100}, aria-label on the track element.
  3. Accordion accessibility — header now has role="button", tabIndex={0}, aria-expanded, aria-controls (wired to the content id), and an onKeyDown handler for Enter/Space.
  4. register.ts SSR guard — both the register() function body and the auto-call at module load are now guarded with typeof window !== 'undefined' && 'customElements' in window.
  5. ACTIVITY_ICON_NAME_FALLBACK duplication — extracted to components/activityIconFallback.ts and imported by both ActionCard and ActivityIcons.
  6. CitizenClaimWidget provider propsprovider, config, themeOverrides, and defaultTheme are now forwarded through SuperfluidCampaignRuntimeProps to the embedded CitizenClaimWidget so it shares the same provider context.
  7. Tests and screenshots — added tests/widgets/superfluid-campaign-widget/states.spec.ts with 8 Playwright smoke tests (disconnected/connected × content/leaderboard, FAQ accordion, leaderboard navigation, mobile 480px, wide 900px). All 8 pass and 8 screenshots are committed to tests/widgets/superfluid-campaign-widget/test-results/.

Copilot finished work on behalf of sirpy July 29, 2026 10:57
Copilot AI requested a review from sirpy July 29, 2026 10:57
@sirpy

sirpy commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

@L03TJ3 "components-test imports — Accordion.tsx and ProgressBar.tsx now import XStack/YStack directly from tamagui (same as Card.ts)."
What did you end up doing regarding xstack/ystack components?

…e compaction

- Add prominent "Claim SUP rewards" CTA opening claim.superfluid.org in a
  new tab, per Meri's change request.
- Add useAirdropStatus hook that fetches the connected wallet's live status
  from the Superfluid airdrop worker endpoint on connect/load/address-change,
  with loading and error states. Adds an airdropStatusAdapter DI seam
  (mirrors the ai-credits-widget adapterFactory convention) so Storybook/
  Playwright fixtures stay deterministic instead of hitting the live network.
- Mobile compaction pass: FAQ restructured into one collapsible section
  wrapping per-question accordion toggles, ProgressBar gains
  hidePercentageOnMobile to drop the redundant percentage where the bar
  already communicates it, ActionCard description truncates to one line on
  mobile, CampaignHeader title sized down on small viewports.
- Extend the Playwright spec with the new CTA/airdrop-status states and
  regenerate all baseline screenshots (scw-01..13) against the updated UI.

Leaderboard split (606/614) and campaign-level SUP totals remain blocked on
cms.superfluid.pro connection registration (task #20).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@L03TJ3

L03TJ3 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

@sirpy Nothing directly on the components. they are still re-exported from the UI package without any changes.
They are unstyled by default. We do this so that all consumers/widgets can and should only use the ui package as a dependency (import YStack from goodwidget/ui instead of YStack from tamagui)
Effectively the same component, just an unstyled container (at the moment, see below note)

In this PR, it really does not change anything if it imports from tamagui or from the local file 'Stacks.ts'

The only change based on our last conversation was made in the provider: https://github.com/GoodDollar/GoodWidget/blob/main/packages/core/src/provider.tsx
All widgets ship with a design-system-aligned background by default.

I have a bounty in preparation for doing a sweep of all the UI components and resolving 'align with design system' so that we can move away from components vs components-test.
Outside the scope of this PR, the current implementation decision is fine. the copilot update was basically unneccessary

BountyCoder and others added 5 commits July 30, 2026 05:54
- Apply compactButtonProps sizing to every Button across the widget
- Fix mobile copy wrapping in ActionCard/CampaignHeader body text
- Restructure ActionCard layout (icon/copy/points-pill/CTA column)
- Reclassify Text tone usage (secondary/soft) per design system
- Swap activity icons to design reference via new @goodwidget/ui
  ActivityIcons re-export of @tamagui/lucide-icons (keeps the direct
  lucide-icons dependency out of the widget package itself)
- Move "Connect wallet" CTA to the header's top-right slot
- Extend the Storybook react-native-svg web shim with named Svg plus
  Path/Line/Rect/Polygon/Polyline/Ellipse passthroughs, needed because
  @tamagui/lucide-icons' barrel export pulls in every icon module during
  the web build, not just the ones actually imported
- Regenerate all 13 Playwright baseline screenshots
Implements the widget from #129 (plan for #127): a
reusable GoodWidget presenting the Superfluid Ecosystem Rewards campaign —
header, two reward-pool action sections (GoodDollar actions, ecosystem
funding actions), a leaderboard view, and the campaign FAQ, against a
static mock dataset for this phase.

- New packages/superfluid-campaign-widget: presentational components,
  widgetRuntimeContract, mock dataset, and the element/register web
  component bridge (mirrors the streaming-widget bridge pattern).
- Adds a disconnected-state "Connect wallet" CTA in the content view,
  gated on useWallet().isConnected, matching the #127 acceptance criteria.
- Adds Accordion and ProgressBar to packages/ui (reusable, used by the
  FAQ and pool-progress sections).
- Adds an additive initialTab prop to citizen-claim-widget and an
  additive initialView prop to SuperfluidCampaignWidget, so Storybook/QA
  fixtures and deep links can land directly on a given view.
- Adds Storybook coverage under examples/storybook: a manual injected-
  wallet showcase story, four QA fixture stories (disconnected/connected
  x content/leaderboard) using the existing custodial-wallet fixture
  pattern from citizen-claim-widget, and a doc page that explicitly flags
  two known gaps for this phase (activity icon glyph registry, live
  leaderboard search/pagination).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…, provider passthrough, add Playwright tests

Co-authored-by: sirpy <671095+sirpy@users.noreply.github.com>
…e compaction

- Add prominent "Claim SUP rewards" CTA opening claim.superfluid.org in a
  new tab, per Meri's change request.
- Add useAirdropStatus hook that fetches the connected wallet's live status
  from the Superfluid airdrop worker endpoint on connect/load/address-change,
  with loading and error states. Adds an airdropStatusAdapter DI seam
  (mirrors the ai-credits-widget adapterFactory convention) so Storybook/
  Playwright fixtures stay deterministic instead of hitting the live network.
- Mobile compaction pass: FAQ restructured into one collapsible section
  wrapping per-question accordion toggles, ProgressBar gains
  hidePercentageOnMobile to drop the redundant percentage where the bar
  already communicates it, ActionCard description truncates to one line on
  mobile, CampaignHeader title sized down on small viewports.
- Extend the Playwright spec with the new CTA/airdrop-status states and
  regenerate all baseline screenshots (scw-01..13) against the updated UI.

Leaderboard split (606/614) and campaign-level SUP totals remain blocked on
cms.superfluid.pro connection registration (task #20).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Apply compactButtonProps sizing to every Button across the widget
- Fix mobile copy wrapping in ActionCard/CampaignHeader body text
- Restructure ActionCard layout (icon/copy/points-pill/CTA column)
- Reclassify Text tone usage (secondary/soft) per design system
- Swap activity icons to design reference via new @goodwidget/ui
  ActivityIcons re-export of @tamagui/lucide-icons (keeps the direct
  lucide-icons dependency out of the widget package itself)
- Move "Connect wallet" CTA to the header's top-right slot
- Extend the Storybook react-native-svg web shim with named Svg plus
  Path/Line/Rect/Polygon/Polyline/Ellipse passthroughs, needed because
  @tamagui/lucide-icons' barrel export pulls in every icon module during
  the web build, not just the ones actually imported
- Regenerate all 13 Playwright baseline screenshots
@goodbounties-nanoclaw-agent
goodbounties-nanoclaw-agent Bot force-pushed the feat/superfluid-campaign-widget branch from 98699ea to a4040c6 Compare July 30, 2026 08:08
@sirpy

sirpy commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

@copilot use real data adapters for the superfluidcampaign widget in the WIDGET storybook section. (not the QA)

Co-authored-by: sirpy <671095+sirpy@users.noreply.github.com>

Copilot AI commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

@copilot use real data adapters for the superfluidcampaign widget in the WIDGET storybook section. (not the QA)

Done in commit Update Showcase stories to use real live data adapters.

The Widgets/SuperfluidCampaignWidget/Showcase stories now use live APIs — no adapter overrides are passed, so all three data sources (airdrop status, leaderboard, SUP totals) hit the real endpoints.

Changes:

  • InjectedWalletStory now renders SuperfluidCampaignWidget directly (without the QA shell's fixed supTotalsAdapter default), and accepts defaultTheme/themeOverrides/initialView controls
  • New LiveDataNoWalletStory for the disconnected showcase with live data
  • Showcase story file gains a NoWallet story and Storybook controls (defaultTheme, brandPreset, initialView) — matching the pattern from CitizenClaim/Streaming showcase stories

QA fixture stories are unchanged.

@sirpy

sirpy commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

@copilot looks like the https://claim.superfluid.org/api/programs endpoint is CORS protected.
Find a different way to query the SUP distribution status using the superfluid skill

Co-authored-by: sirpy <671095+sirpy@users.noreply.github.com>

Copilot AI commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

@copilot looks like the https://claim.superfluid.org/api/programs endpoint is CORS protected.
Find a different way to query the SUP distribution status using the superfluid skill

Done in commit Replace CORS-protected programs API with Superfluid Base subgraph query.

What changeduseProgramSupTotals no longer calls claim.superfluid.org/api/programs. It now POSTs a GraphQL query directly to the public Superfluid protocol-v1 subgraph on Base (https://base-mainnet.subgraph.x.superfluid.dev/, no CORS restriction), querying the pool entity for totalAmountDistributedUntilUpdatedAt, flowRate, and updatedAtTimestamp.

Data mapping:

  • totalClaimed = pool.totalAmountDistributedUntilUpdatedAt + (flowRate × elapsed_seconds) — gives a live approximation for streaming pools without an extra RPC call
  • totalAllocated = static campaign budget from the new campaignPoolConfig.ts (217,700 SUP for campaign 606; 404,300 for 614)

Pool addresses: The CAMPAIGN_GDA_POOL_CONFIG constant in campaignPoolConfig.ts currently has poolAddress: null for both campaigns — the research confirmed these addresses are only resolvable server-side via the CORS-protected programs API, so they require confirmation from the Superfluid team. The file documents three lookup methods (subgraph query, BaseScan, Superfluid team). When poolAddress is null, the hook returns null and RewardPoolSection falls back to mock data, same as before.

Note on subgraph vs programs API semantics: The research confirmed the subgraph's Pool entity has no pool-level totalClaimed aggregate — per-member PoolMember.totalAmountClaimed requires full member iteration. totalAmountDistributedUntilUpdatedAt is the closest pool-level equivalent (total distributed from pool to members), which is the correct value for the campaign progress bar.

Copilot finished work on behalf of sirpy July 30, 2026 13:15
- Rename "Ecosystem funding actions" to "Ecosystem actions" in user-facing
  copy (pool label, FAQ, comments, test assertion), leaving internal
  identifiers (pool id, Storybook fixture slug) unchanged.
- Tighten action card description copy (Claim UBI, one-time donation,
  stream-to-pool) and abbreviate points-pill labels (POINTS->PTS,
  POINT->PT) to reduce description wrapping, plus a minor $sm gap tweak.
- Make each ActionCard's whole card a click target for its CTA action
  (mouse + keyboard), while keeping the CTA button visible and
  preventing the button click from double-firing the action.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…st 6)

- Widget top description now names the three eligible sources
  (GoodDollar, Flow State, Gardens) instead of listing individual actions.
- "How to participate" description now reflects points-based SUP share
  and points users to Claim SUP rewards to create/update their stream.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…request 7)

CampaignHeader's top row and ActionCard's pill/button column were rigid
(no flexWrap), so at sub-480px widths the disconnected-state CTA and the
longest pill labels were pushed past the card's edge and silently clipped
by the card's own rounded-corner overflow:hidden — invisible to a
scrollWidth-only check since the clipped content never grew the document.

Fix: flexWrap="wrap" on both rows so they drop to their own line instead
of overflowing; marginLeft="auto" keeps the action-card's pill/button
column right-aligned whether inline or wrapped.

Adds a permanent Playwright regression suite for 320/360/375/390/412px
(min-supported through larger-Android breakpoints) asserting both no
page-level horizontal overflow and no button clipped by an ancestor's
overflow:hidden, plus refreshed baseline screenshots.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.

5 participants