diff --git a/examples/storybook/src/stories/streaming-widget/StreamingWidget.stories.tsx b/examples/storybook/src/stories/streaming-widget/StreamingWidget.stories.tsx
index 12520e8..04aff7f 100644
--- a/examples/storybook/src/stories/streaming-widget/StreamingWidget.stories.tsx
+++ b/examples/storybook/src/stories/streaming-widget/StreamingWidget.stories.tsx
@@ -1,6 +1,17 @@
 import React from 'react'
 import type { Meta, StoryObj } from '@storybook/react'
-import { StreamingWidget } from '@goodwidget/streaming-widget'
+import {
+  STREAMING_CHAINS,
+  StreamingWidget,
+  StreamingWidgetPreview,
+  type PoolMembershipItem,
+  type SetStreamFormState,
+  type StreamingWidgetAdapterActions,
+  type StreamingWidgetAdapterResult,
+  type StreamingWidgetAdapterState,
+  type StreamingWidgetTab,
+  type StreamListItem,
+} from '@goodwidget/streaming-widget'
 import { YStack } from '@goodwidget/ui'
 import {
   getInjectedEip1193Provider,
@@ -8,10 +19,199 @@ import {
 } from '../../fixtures/injectedEip1193'
 import { createCustodialEip1193Provider } from '../../fixtures/custodialEip1193'
 
-// ---------------------------------------------------------------------------
-// Story shell — renders the widget inside a fixed-width container that mirrors
-// the GoodWalletV2 sidebar / bottom-sheet form factor.
-// ---------------------------------------------------------------------------
+const DEMO_ADDRESS = '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef'
+const DEMO_RECEIVER = '0x1111111111111111111111111111111111111111'
+const DEMO_SENDER = '0x2222222222222222222222222222222222222222'
+const DEMO_TOKEN = '0x3333333333333333333333333333333333333333'
+const DEMO_POOL = '0x4444444444444444444444444444444444444444'
+const DEMO_RESERVE_LOCKER = '0x8888888888888888888888888888888888888888'
+
+const defaultForm: SetStreamFormState = {
+  receiver: '',
+  amount: '',
+  timeUnit: 'month',
+  flowRate: null,
+  validationError: null,
+}
+
+const validForm: SetStreamFormState = {
+  receiver: DEMO_RECEIVER,
+  amount: '42',
+  timeUnit: 'month',
+  flowRate: 16203703703703n,
+  validationError: null,
+}
+
+const invalidForm: SetStreamFormState = {
+  receiver: '0x123',
+  amount: '0',
+  timeUnit: 'month',
+  flowRate: null,
+  validationError: 'Recipient must be a valid Ethereum address (0x...).',
+}
+
+const sampleStreams: StreamListItem[] = [
+  {
+    id: 'outgoing-demo-stream',
+    sender: DEMO_ADDRESS,
+    receiver: DEMO_RECEIVER,
+    token: DEMO_TOKEN,
+    flowRate: 38580246913580n,
+    streamedSoFar: 15000000000000000000n,
+    createdAtTimestamp: 1767225600,
+    updatedAtTimestamp: 1767312000,
+    direction: 'outgoing',
+  },
+  {
+    id: 'incoming-demo-stream',
+    sender: DEMO_SENDER,
+    receiver: DEMO_ADDRESS,
+    token: DEMO_TOKEN,
+    flowRate: 19290123456790n,
+    streamedSoFar: 7800000000000000000n,
+    createdAtTimestamp: 1767139200,
+    updatedAtTimestamp: 1767312000,
+    direction: 'incoming',
+  },
+]
+
+// Mirrors the current SDK-backed adapter; diverge this once past-stream history is fetched separately.
+const sampleStreamHistory: StreamListItem[] = [
+  ...sampleStreams,
+  {
+    id: 'history-outgoing-demo-stream-2',
+    sender: DEMO_ADDRESS,
+    receiver: '0x5555555555555555555555555555555555555555',
+    token: DEMO_TOKEN,
+    flowRate: 9645061728395n,
+    streamedSoFar: 4300000000000000000n,
+    createdAtTimestamp: 1767052800,
+    updatedAtTimestamp: 1767139200,
+    direction: 'outgoing',
+  },
+  {
+    id: 'history-incoming-demo-stream-2',
+    sender: '0x6666666666666666666666666666666666666666',
+    receiver: DEMO_ADDRESS,
+    token: DEMO_TOKEN,
+    flowRate: 5787037037037n,
+    streamedSoFar: 2200000000000000000n,
+    createdAtTimestamp: 1766966400,
+    updatedAtTimestamp: 1767052800,
+    direction: 'incoming',
+  },
+  {
+    id: 'history-outgoing-demo-stream-3',
+    sender: DEMO_ADDRESS,
+    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,
+  },
+]
+
+function createAdapter(
+  stateOverrides: Partial<StreamingWidgetAdapterState> = {},
+  actionOverrides: Partial<StreamingWidgetAdapterActions> = {},
+): StreamingWidgetAdapterResult {
+  const baseState: StreamingWidgetAdapterState = {
+    isConnected: true,
+    address: DEMO_ADDRESS,
+    chainId: STREAMING_CHAINS.CELO,
+    isWrongChain: false,
+    streams: sampleStreams,
+    streamsLoading: false,
+    streamsError: null,
+    streamHistory: sampleStreamHistory,
+    streamHistoryLoading: false,
+    streamHistoryError: null,
+    pools: samplePools,
+    poolsLoading: false,
+    poolsError: null,
+    superTokenBalance: '128.50',
+    balanceLoading: false,
+    balanceError: null,
+    supReserveBalance: null,
+    supReserveLockers: [],
+    supReserveLoading: false,
+    supReserveError: null,
+    setStreamForm: defaultForm,
+    setStreamStatus: 'idle',
+    setStreamError: null,
+    setStreamTxHash: null,
+    poolConnectStatus: {},
+    poolConnectError: {},
+    poolClaimStatus: {},
+    poolClaimError: {},
+  }
+
+  const actions: StreamingWidgetAdapterActions = {
+    connect: async () => {},
+    switchChain: async () => {},
+    refreshStreams: async () => {},
+    refreshStreamHistory: async () => {},
+    refreshPools: async () => {},
+    refreshBalance: async () => {},
+    updateSetStreamForm: () => {},
+    submitSetStream: async () => {},
+    resetSetStream: () => {},
+    connectToPool: async () => {},
+    disconnectFromPool: async () => {},
+    claimFromPool: async () => {},
+    ...actionOverrides,
+  }
+
+  return {
+    state: { ...baseState, ...stateOverrides },
+    actions,
+  }
+}
+
+function PreviewStoryShell({
+  adapter,
+  dataTestId,
+  initialTab = 'streams',
+  initialStreamsFormOpen = false,
+}: {
+  adapter: StreamingWidgetAdapterResult
+  dataTestId: string
+  initialTab?: StreamingWidgetTab
+  initialStreamsFormOpen?: boolean
+}) {
+  return (
+    <YStack
+      data-testid={dataTestId}
+      style={{
+        width: '100%',
+        maxWidth: 400,
+        minHeight: '100vh',
+        boxSizing: 'border-box',
+      }}
+    >
+      <StreamingWidgetPreview
+        adapter={adapter}
+        initialTab={initialTab}
+        initialStreamsFormOpen={initialStreamsFormOpen}
+      />
+    </YStack>
+  )
+}
+
 function StreamingWidgetStoryShell({
   provider,
   dataTestId,
@@ -20,7 +220,15 @@ function StreamingWidgetStoryShell({
   dataTestId: string
 }) {
   return (
-    <YStack data-testid={dataTestId} style={{ width: 400, minHeight: '100vh' }}>
+    <YStack
+      data-testid={dataTestId}
+      style={{
+        width: '100%',
+        maxWidth: 400,
+        minHeight: '100vh',
+        boxSizing: 'border-box',
+      }}
+    >
       <StreamingWidget provider={provider} environment="development" />
     </YStack>
   )
@@ -36,22 +244,24 @@ const meta: Meta<typeof StreamingWidget> = {
 export default meta
 type Story = StoryObj<typeof meta>
 
-// ---------------------------------------------------------------------------
-// Injected wallet story — uses window.ethereum if present in the browser
-// ---------------------------------------------------------------------------
 function InjectedWalletStory() {
   const injectedProvider = getInjectedEip1193Provider()
   const usableProvider = isInjectedProviderUsable(injectedProvider)
 
   if (!usableProvider) {
     return (
-      <YStack data-testid="StreamingWidget-no-wallet" style={{ width: 400 }} gap="$3">
-        <strong>No injected wallet found</strong>
-        <span>
-          Install/enable MetaMask (or another EIP-1193 wallet) in this browser, then refresh
-          Storybook. The widget supports Celo (G$) and Base (SUP).
-        </span>
-      </YStack>
+      <PreviewStoryShell
+        adapter={createAdapter({
+          isConnected: false,
+          address: null,
+          chainId: null,
+          streams: [],
+          streamHistory: [],
+          pools: [],
+          superTokenBalance: null,
+        })}
+        dataTestId="StreamingWidget-no-injected-wallet"
+      />
     )
   }
 
@@ -63,9 +273,6 @@ function InjectedWalletStory() {
   )
 }
 
-// ---------------------------------------------------------------------------
-// Custodial fixture story — uses the pre-configured test wallet from the fixture
-// ---------------------------------------------------------------------------
 function CustodialLocalFixtureStory() {
   try {
     const provider = createCustodialEip1193Provider()
@@ -77,7 +284,10 @@ function CustodialLocalFixtureStory() {
     )
   } catch (error: unknown) {
     return (
-      <YStack data-testid="StreamingWidget-custodial-config-error" style={{ width: 400 }}>
+      <YStack
+        data-testid="StreamingWidget-custodial-config-error"
+        style={{ width: 'min(400px, 100vw)' }}
+      >
         <strong>Custodial fixture not configured</strong>
         <span>
           {error instanceof Error
@@ -89,37 +299,294 @@ function CustodialLocalFixtureStory() {
   }
 }
 
-// ---------------------------------------------------------------------------
-// No-wallet story — demonstrates the connect-prompt state
-// ---------------------------------------------------------------------------
-function NoWalletStory() {
+function PoolClaimableAmountErrorStory() {
+  const [retrying, setRetrying] = React.useState(false)
+
   return (
-    <StreamingWidgetStoryShell
-      provider={undefined}
-      dataTestId="StreamingWidget-no-provider"
+    <PreviewStoryShell
+      adapter={createAdapter(
+        {
+          pools: retrying
+            ? []
+            : [
+                {
+                  ...samplePools[0],
+                  isConnected: false,
+                  claimableAmount: 0n,
+                  claimableAmountError: true,
+                },
+              ],
+          poolsLoading: retrying,
+        },
+        {
+          refreshPools: async () => {
+            setRetrying(true)
+          },
+        },
+      )}
+      dataTestId="StreamingWidget-pool-claimable-amount-error"
+      initialTab="pools"
     />
   )
 }
 
-// ---------------------------------------------------------------------------
-// Story exports
-// ---------------------------------------------------------------------------
-
-/** Uses window.ethereum if available — shows the full connected experience. */
 export const InjectedWallet: Story = {
   render: () => <InjectedWalletStory />,
 }
 
-/**
- * Uses a pre-configured custodial test wallet backed by a local private key.
- * Starts on Celo (chain 42220) and uses the development environment.
- * The test key has no on-chain streaming history, so streams/pools lists will be empty.
- */
 export const CustodialLocalFixture: Story = {
   render: () => <CustodialLocalFixtureStory />,
 }
 
-/** No provider — shows the wallet-connection prompt for both Streams and Pools tabs. */
 export const NoWallet: Story = {
-  render: () => <NoWalletStory />,
+  render: () => (
+    <PreviewStoryShell
+      adapter={createAdapter({
+        isConnected: false,
+        address: null,
+        chainId: null,
+        streams: [],
+        streamHistory: [],
+        pools: [],
+        superTokenBalance: null,
+      })}
+      dataTestId="StreamingWidget-no-wallet"
+    />
+  ),
+}
+
+export const WrongChain: Story = {
+  render: () => (
+    <PreviewStoryShell
+      adapter={createAdapter({
+        chainId: 1,
+        isWrongChain: true,
+      })}
+      dataTestId="StreamingWidget-wrong-chain"
+    />
+  ),
+}
+
+export const LoadingState: Story = {
+  render: () => (
+    <PreviewStoryShell
+      adapter={createAdapter({
+        streams: [],
+        streamsLoading: true,
+        streamHistory: [],
+        streamHistoryLoading: true,
+        pools: [],
+        poolsLoading: true,
+        balanceLoading: true,
+      })}
+      dataTestId="StreamingWidget-loading-state"
+    />
+  ),
+}
+
+export const EmptyState: Story = {
+  render: () => (
+    <PreviewStoryShell
+      adapter={createAdapter({
+        streams: [],
+        streamHistory: [],
+        pools: [],
+        superTokenBalance: '0',
+      })}
+      dataTestId="StreamingWidget-empty-state"
+    />
+  ),
+}
+
+export const ErrorState: Story = {
+  render: () => (
+    <PreviewStoryShell
+      adapter={createAdapter({
+        streams: [],
+        streamsError: 'Unable to reach the network. Check your connection and try again.',
+        streamHistory: [],
+        streamHistoryError: 'Unable to load stream history.',
+        pools: [],
+        poolsError: 'Unable to load pool memberships.',
+        balanceError: 'Unable to load token balance.',
+      })}
+      dataTestId="StreamingWidget-error-state"
+    />
+  ),
+}
+
+export const PopulatedState: Story = {
+  render: () => (
+    <PreviewStoryShell
+      adapter={createAdapter()}
+      dataTestId="StreamingWidget-populated-state"
+    />
+  ),
+}
+
+export const CreateUpdateForm: Story = {
+  render: () => (
+    <PreviewStoryShell
+      adapter={createAdapter({ setStreamForm: validForm })}
+      dataTestId="StreamingWidget-create-update-form"
+      initialStreamsFormOpen
+    />
+  ),
+}
+
+export const CreateUpdateInvalidInput: Story = {
+  render: () => (
+    <PreviewStoryShell
+      adapter={createAdapter({ setStreamForm: invalidForm })}
+      dataTestId="StreamingWidget-create-update-invalid"
+      initialStreamsFormOpen
+    />
+  ),
+}
+
+export const CreateUpdatePending: Story = {
+  render: () => (
+    <PreviewStoryShell
+      adapter={createAdapter({
+        setStreamForm: validForm,
+        setStreamStatus: 'pending',
+      })}
+      dataTestId="StreamingWidget-create-update-pending"
+      initialStreamsFormOpen
+    />
+  ),
+}
+
+export const CreateUpdateSuccess: Story = {
+  render: () => (
+    <PreviewStoryShell
+      adapter={createAdapter({
+        setStreamForm: validForm,
+        setStreamStatus: 'success',
+        setStreamTxHash:
+          '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
+      })}
+      dataTestId="StreamingWidget-create-update-success"
+      initialStreamsFormOpen
+    />
+  ),
+}
+
+export const CreateUpdateFailure: Story = {
+  render: () => (
+    <PreviewStoryShell
+      adapter={createAdapter({
+        setStreamForm: validForm,
+        setStreamStatus: 'error',
+        setStreamError: 'Transaction cancelled by wallet.',
+      })}
+      dataTestId="StreamingWidget-create-update-failure"
+      initialStreamsFormOpen
+    />
+  ),
+}
+
+export const PoolClaimState: Story = {
+  render: () => (
+    <PreviewStoryShell
+      adapter={createAdapter({
+        pools: [{ ...samplePools[0], isConnected: false }],
+      })}
+      dataTestId="StreamingWidget-pool-claim"
+      initialTab="pools"
+    />
+  ),
+}
+
+export const PoolConnectedState: Story = {
+  render: () => (
+    <PreviewStoryShell
+      adapter={createAdapter({
+        pools: [{ ...samplePools[0], isConnected: true }],
+      })}
+      dataTestId="StreamingWidget-pool-connected"
+      initialTab="pools"
+    />
+  ),
+}
+
+// Claim lifecycle stories use isConnected: true so write status badges render correctly.
+export const PoolClaimPending: Story = {
+  render: () => (
+    <PreviewStoryShell
+      adapter={createAdapter({
+        pools: [{ ...samplePools[0], isConnected: true }],
+        poolClaimStatus: { [DEMO_POOL]: 'pending' },
+      })}
+      dataTestId="StreamingWidget-pool-claim-pending"
+      initialTab="pools"
+    />
+  ),
+}
+
+export const PoolClaimSuccess: Story = {
+  render: () => (
+    <PreviewStoryShell
+      adapter={createAdapter({
+        pools: [{ ...samplePools[0], isConnected: true }],
+        poolClaimStatus: { [DEMO_POOL]: 'success' },
+      })}
+      dataTestId="StreamingWidget-pool-claim-success"
+      initialTab="pools"
+    />
+  ),
+}
+
+export const PoolClaimError: Story = {
+  render: () => (
+    <PreviewStoryShell
+      adapter={createAdapter({
+        pools: [{ ...samplePools[0], isConnected: true }],
+        poolClaimStatus: { [DEMO_POOL]: 'error' },
+        poolClaimError: { [DEMO_POOL]: 'Pool claim failed. Please retry.' },
+      })}
+      dataTestId="StreamingWidget-pool-claim-error"
+      initialTab="pools"
+    />
+  ),
+}
+
+export const PoolClaimableAmountError: Story = {
+  render: () => <PoolClaimableAmountErrorStory />,
+}
+
+export const BaseSupBalanceAndReserve: Story = {
+  render: () => (
+    <PreviewStoryShell
+      adapter={createAdapter({
+        chainId: STREAMING_CHAINS.BASE,
+        superTokenBalance: '712.10',
+        supReserveBalance: '112.75',
+        supReserveLockers: [
+          {
+            address: DEMO_RESERVE_LOCKER,
+            stakedBalance: 95250000000000000000n,
+            unstakedBalance: 17500000000000000000n,
+            totalBalance: 112750000000000000000n,
+          },
+        ],
+      })}
+      dataTestId="StreamingWidget-base-sup-reserve"
+      initialTab="balances"
+    />
+  ),
+}
+
+export const NonBaseSupReserveDisabled: Story = {
+  render: () => (
+    <PreviewStoryShell
+      adapter={createAdapter({
+        chainId: STREAMING_CHAINS.CELO,
+        superTokenBalance: '128.50',
+        supReserveBalance: null,
+      })}
+      dataTestId="StreamingWidget-non-base-reserve"
+      initialTab="balances"
+    />
+  ),
 }
diff --git a/packages/citizen-claim-widget/src/CitizenClaimWidget.tsx b/packages/citizen-claim-widget/src/CitizenClaimWidget.tsx
index 783f5a0..424a41c 100644
--- a/packages/citizen-claim-widget/src/CitizenClaimWidget.tsx
+++ b/packages/citizen-claim-widget/src/CitizenClaimWidget.tsx
@@ -172,7 +172,6 @@ function Countdown({ nextClaim }: { nextClaim: Date }) {
   useEffect(() => {
     const id = setInterval(() => setTimeLeft(getTimeLeft()), 1000)
     return () => clearInterval(id)
-    // eslint-disable-next-line react-hooks/exhaustive-deps
   }, [nextClaim])
 
   const h = Math.floor(timeLeft / 3600)
diff --git a/packages/citizen-claim-widget/src/adapter.ts b/packages/citizen-claim-widget/src/adapter.ts
index 4a0b7c3..1b326f3 100644
--- a/packages/citizen-claim-widget/src/adapter.ts
+++ b/packages/citizen-claim-widget/src/adapter.ts
@@ -420,7 +420,6 @@ export function useCitizenClaimAdapter(
   // Auto-refresh claim status whenever wallet connection or chain changes
   useEffect(() => {
     void loadClaimStatus()
-    // eslint-disable-next-line react-hooks/exhaustive-deps
   }, [isConnected, address, chainId])
 
   // ---------------------------------------------------------------------------
diff --git a/packages/core/src/provider.tsx b/packages/core/src/provider.tsx
index 0aa38f2..21d5ed5 100644
--- a/packages/core/src/provider.tsx
+++ b/packages/core/src/provider.tsx
@@ -24,7 +24,7 @@ export interface WalletContextValue extends WalletState {
   connect: () => Promise<void>
 }
 
-export interface HostContextValue extends HostState {}
+export type HostContextValue = HostState
 
 export interface GoodWidgetContextValue extends GoodWidgetState {
   connect: () => Promise<void>
diff --git a/packages/streaming-widget/src/StreamingWidget.tsx b/packages/streaming-widget/src/StreamingWidget.tsx
index f837009..8439633 100644
--- a/packages/streaming-widget/src/StreamingWidget.tsx
+++ b/packages/streaming-widget/src/StreamingWidget.tsx
@@ -6,6 +6,7 @@ import {
   Card,
   Heading,
   Text,
+  Anchor,
   Button,
   ButtonText,
   Spinner,
@@ -20,6 +21,7 @@ import {
   AddressDisplay,
   TokenAmount,
   WidgetTabs,
+  formatDisplayAmount,
 } from '@goodwidget/ui'
 import type { Address } from 'viem'
 import { formatUnits } from 'viem'
@@ -31,10 +33,14 @@ import type {
   StreamListItem,
   PoolMembershipItem,
   StreamTimeUnit,
+  StreamingWidgetAdapterResult,
   WriteStatus,
 } from './widgetRuntimeContract'
 import { STREAMING_CHAINS } from './widgetRuntimeContract'
 
+type StreamingWidgetAdapterState = StreamingWidgetAdapterResult['state']
+type StreamingWidgetAdapterActions = StreamingWidgetAdapterResult['actions']
+
 // ---------------------------------------------------------------------------
 // Named styled sub-components — participate in the component sub-theme system.
 // Integrators can override via themeOverrides.
@@ -111,12 +117,12 @@ function formatFlowRateDisplay(flowRate: bigint, decimals = 18): string {
   if (flowRate === 0n) return '0'
   // Convert wei/s → per-month amount for display
   const perMonth = flowRate * BigInt(30 * 24 * 60 * 60)
-  return formatUnits(perMonth, decimals)
+  return formatDisplayAmount(formatUnits(perMonth, decimals))
 }
 
 /** Formats a unix timestamp (seconds) to a short locale date string */
 function formatTimestamp(unixSeconds: number): string {
-  if (!unixSeconds) return '—'
+  if (!unixSeconds) return 'N/A'
   return new Date(unixSeconds * 1000).toLocaleDateString('en-US', {
     month: 'short',
     day: 'numeric',
@@ -131,12 +137,30 @@ function chainName(chainId: number): string {
   return `Chain ${chainId}`
 }
 
+function tokenSymbol(chainId: number | null): 'G$' | 'SUP' {
+  return chainId === STREAMING_CHAINS.BASE ? 'SUP' : 'G$'
+}
+
+function formatWeiAmount(amount: bigint): string {
+  return formatDisplayAmount(formatUnits(amount, 18))
+}
+
+function superfluidExplorerAccountUrl(chainId: number | null, address: Address): string {
+  const networkSlug = chainId === STREAMING_CHAINS.BASE ? 'base-mainnet' : 'celo'
+  return `https://explorer.superfluid.org/${networkSlug}/accounts/${address}`
+}
+
 // ---------------------------------------------------------------------------
 // Write-status badge helper
 // ---------------------------------------------------------------------------
 function WriteStatusBadge({ status }: { status: WriteStatus }) {
   if (status === 'idle') return null
-  if (status === 'pending') return <Spinner size="sm" />
+  if (status === 'pending')
+    return (
+      <Badge type="warning">
+        <BadgeText>Pending</BadgeText>
+      </Badge>
+    )
   if (status === 'success')
     return (
       <Badge type="success">
@@ -209,6 +233,7 @@ function WalletGate({
 // ---------------------------------------------------------------------------
 function SetStreamForm({
   form,
+  token,
   status,
   error,
   txHash,
@@ -216,11 +241,12 @@ function SetStreamForm({
   onSubmit,
   onReset,
 }: {
-  form: ReturnType<typeof useStreamingAdapter>['state']['setStreamForm']
+  form: StreamingWidgetAdapterState['setStreamForm']
+  token: 'G$' | 'SUP'
   status: WriteStatus
   error: string | null
   txHash: string | null
-  onUpdate: (partial: Partial<typeof form>) => void
+  onUpdate: (partial: Partial<StreamingWidgetAdapterState['setStreamForm']>) => void
   onSubmit: () => void
   onReset: () => void
 }) {
@@ -228,13 +254,13 @@ function SetStreamForm({
 
   return (
     <SetStreamFormCard>
-      <Heading level={4}>{form.receiver ? 'Update Stream' : 'Create Stream'}</Heading>
+      <Heading level={4}>Create / Update Stream</Heading>
 
       {/* Recipient address */}
       <YStack gap="$1">
         <Text variant="label">Recipient address</Text>
         <Input
-          placeholder="0x…"
+          placeholder="0x..."
           value={form.receiver}
           onChangeText={(v: string) => onUpdate({ receiver: v })}
           editable={!isSubmitting}
@@ -244,7 +270,7 @@ function SetStreamForm({
       {/* Amount + time unit */}
       <XStack gap="$2" alignItems="flex-end">
         <YStack flex={1} gap="$1">
-          <Text variant="label">Amount</Text>
+          <Text variant="label">Amount ({token})</Text>
           <Input
             placeholder="100"
             value={form.amount}
@@ -268,7 +294,7 @@ function SetStreamForm({
       {form.flowRate !== null && form.flowRate > 0n && (
         <XStack gap="$2" alignItems="center">
           <Text variant="caption" secondary>
-            ≈ {formatUnits(form.flowRate, 18)} tokens/s
+            About {formatWeiAmount(form.flowRate)} {token}/s
           </Text>
         </XStack>
       )}
@@ -284,9 +310,14 @@ function SetStreamForm({
           {error}
         </Text>
       )}
+      {status === 'pending' && (
+        <Text variant="caption" secondary>
+          Transaction pending...
+        </Text>
+      )}
       {status === 'success' && txHash && (
         <Text color="$success" variant="caption">
-          Stream set! Tx: {txHash.slice(0, 10)}…
+          Stream set! Tx: {txHash.slice(0, 10)}...
         </Text>
       )}
 
@@ -312,7 +343,7 @@ function SetStreamForm({
 // ---------------------------------------------------------------------------
 // Single stream row
 // ---------------------------------------------------------------------------
-function StreamCard({ stream }: { stream: StreamListItem }) {
+function StreamCard({ stream, token }: { stream: StreamListItem; token: 'G$' | 'SUP' }) {
   const counterparty =
     stream.direction === 'outgoing' ? stream.receiver : stream.sender
   const flowPerMonth = formatFlowRateDisplay(stream.flowRate)
@@ -321,7 +352,7 @@ function StreamCard({ stream }: { stream: StreamListItem }) {
     <StreamRow>
       <XStack justifyContent="space-between" alignItems="center">
         <Badge type={stream.direction === 'incoming' ? 'success' : 'info'}>
-          <BadgeText>{stream.direction === 'incoming' ? '↓ Incoming' : '↑ Outgoing'}</BadgeText>
+          <BadgeText>{stream.direction === 'incoming' ? 'Incoming' : 'Outgoing'}</BadgeText>
         </Badge>
         <Text variant="caption" secondary>
           Since {formatTimestamp(stream.createdAtTimestamp)}
@@ -334,9 +365,12 @@ function StreamCard({ stream }: { stream: StreamListItem }) {
         <Text variant="caption" secondary>
           Flow rate
         </Text>
-        <Text fontWeight="600">
-          {flowPerMonth}/mo
-        </Text>
+        <XStack gap="$1" alignItems="baseline">
+          <TokenAmount token={token} amount={flowPerMonth} size="sm" />
+          <Text variant="caption" secondary>
+            /mo
+          </Text>
+        </XStack>
       </XStack>
 
       {stream.streamedSoFar > 0n && (
@@ -344,7 +378,7 @@ function StreamCard({ stream }: { stream: StreamListItem }) {
           <Text variant="caption" secondary>
             Streamed so far
           </Text>
-          <Text>{formatUnits(stream.streamedSoFar, 18)}</Text>
+          <TokenAmount token={token} amount={formatWeiAmount(stream.streamedSoFar)} size="sm" />
         </XStack>
       )}
     </StreamRow>
@@ -360,16 +394,21 @@ const DIRECTION_LABELS: Record<StreamDirection, string> = {
 function StreamsTab({
   state,
   actions,
+  initialFormOpen = false,
 }: {
-  state: ReturnType<typeof useStreamingAdapter>['state']
-  actions: ReturnType<typeof useStreamingAdapter>['actions']
+  state: StreamingWidgetAdapterState
+  actions: StreamingWidgetAdapterActions
+  initialFormOpen?: boolean
 }) {
   const [direction, setDirection] = useState<StreamDirection>('all')
-  const [showForm, setShowForm] = useState(false)
+  const [showForm, setShowForm] = useState(initialFormOpen)
 
   const filteredStreams = state.streams.filter(
     (s) => direction === 'all' || s.direction === direction,
   )
+  const emptyStreamsMessage =
+    direction === 'all' ? 'No streams found.' : `No ${direction} streams found.`
+  const activeToken = tokenSymbol(state.chainId)
 
   return (
     <StreamingTabContent>
@@ -383,6 +422,7 @@ function StreamsTab({
       {showForm && (
         <SetStreamForm
           form={state.setStreamForm}
+          token={activeToken}
           status={state.setStreamStatus}
           error={state.setStreamError}
           txHash={state.setStreamTxHash}
@@ -397,6 +437,8 @@ function StreamsTab({
 
       <Separator />
 
+      <Heading level={4}>Active streams</Heading>
+
       {/* Direction filter */}
       <XStack gap="$2">
         {(['all', 'incoming', 'outgoing'] as StreamDirection[]).map((d) => (
@@ -414,7 +456,7 @@ function StreamsTab({
       {state.streamsLoading && (
         <YStack alignItems="center" paddingVertical="$4">
           <Spinner size="lg" />
-          <Text secondary>Loading streams…</Text>
+          <Text secondary>Loading streams...</Text>
         </YStack>
       )}
 
@@ -430,7 +472,7 @@ function StreamsTab({
       {!state.streamsLoading && !state.streamsError && filteredStreams.length === 0 && (
         <EmptyStateCard>
           <Text secondary center>
-            No {direction === 'all' ? '' : direction} streams found.
+            {emptyStreamsMessage}
           </Text>
           <Button onPress={actions.refreshStreams}>
             <ButtonText>Refresh</ButtonText>
@@ -440,7 +482,74 @@ function StreamsTab({
 
       {!state.streamsLoading &&
         !state.streamsError &&
-        filteredStreams.map((stream) => <StreamCard key={stream.id} stream={stream} />)}
+        filteredStreams.map((stream) => (
+          <StreamCard key={stream.id} stream={stream} token={activeToken} />
+        ))}
+    </StreamingTabContent>
+  )
+}
+
+// ---------------------------------------------------------------------------
+// Stream history tab
+// ---------------------------------------------------------------------------
+function HistoryTab({
+  state,
+  actions,
+}: {
+  state: StreamingWidgetAdapterState
+  actions: StreamingWidgetAdapterActions
+}) {
+  const [historyLimit, setHistoryLimit] = useState(4)
+  const recentStreams = state.streamHistory.slice(0, historyLimit)
+  const hasMoreHistory = state.streamHistory.length > historyLimit
+  const activeToken = tokenSymbol(state.chainId)
+
+  return (
+    <StreamingTabContent>
+      <XStack justifyContent="space-between" alignItems="center">
+        <Heading level={4}>Stream history</Heading>
+        <Button variant="secondary" onPress={actions.refreshStreamHistory}>
+          <ButtonText>Refresh</ButtonText>
+        </Button>
+      </XStack>
+
+      {state.streamHistoryLoading && (
+        <YStack alignItems="center" paddingVertical="$4">
+          <Spinner size="lg" />
+          <Text secondary>Loading stream history...</Text>
+        </YStack>
+      )}
+
+      {!state.streamHistoryLoading && state.streamHistoryError && (
+        <ErrorStateCard>
+          <Text color="$error">{state.streamHistoryError}</Text>
+          <Button onPress={actions.refreshStreamHistory}>
+            <ButtonText>Retry</ButtonText>
+          </Button>
+        </ErrorStateCard>
+      )}
+
+      {!state.streamHistoryLoading &&
+        !state.streamHistoryError &&
+        recentStreams.length === 0 && (
+          <EmptyStateCard>
+            <Text secondary center>
+              No stream history found.
+            </Text>
+          </EmptyStateCard>
+        )}
+
+      {!state.streamHistoryLoading &&
+        !state.streamHistoryError &&
+        recentStreams.map((stream) => (
+          <StreamCard key={`history-${stream.id}`} stream={stream} token={activeToken} />
+        ))}
+
+      {!state.streamHistoryLoading && !state.streamHistoryError && hasMoreHistory && (
+        <Button variant="secondary" onPress={() => setHistoryLimit((count) => count + 4)}>
+          <ButtonText>Show more</ButtonText>
+        </Button>
+      )}
     </StreamingTabContent>
   )
 }
@@ -450,18 +559,30 @@ function StreamsTab({
 // ---------------------------------------------------------------------------
 function PoolCard({
   pool,
+  token,
   connectStatus,
   connectError,
+  claimStatus,
+  claimError,
   onConnect,
   onDisconnect,
+  onClaim,
+  onRetryClaimable,
 }: {
   pool: PoolMembershipItem
+  token: 'G$' | 'SUP'
   connectStatus: WriteStatus
   connectError: string | null
+  claimStatus: WriteStatus
+  claimError: string | null
   onConnect: (poolAddress: Address) => void
   onDisconnect: (poolAddress: Address) => void
+  onClaim: (poolAddress: Address) => void
+  onRetryClaimable: () => void
 }) {
-  const isPending = connectStatus === 'pending'
+  const isConnectPending = connectStatus === 'pending'
+  const isClaimPending = claimStatus === 'pending'
+  const canClaim = !pool.isConnected && pool.claimableAmount > 0n && !pool.claimableAmountError
 
   return (
     <PoolRow>
@@ -472,11 +593,18 @@ function PoolCard({
         </Badge>
       </XStack>
 
+      <XStack justifyContent="space-between" alignItems="center">
+        <Text variant="caption" secondary>
+          Claimable
+        </Text>
+        <TokenAmount token={token} amount={formatWeiAmount(pool.claimableAmount)} size="sm" />
+      </XStack>
+
       <XStack justifyContent="space-between" alignItems="center">
         <Text variant="caption" secondary>
           Total claimed
         </Text>
-        <Text>{formatUnits(pool.totalAmountClaimed, 18)}</Text>
+        <TokenAmount token={token} amount={formatWeiAmount(pool.totalAmountClaimed)} size="sm" />
       </XStack>
 
       {connectError && (
@@ -484,17 +612,57 @@ function PoolCard({
           {connectError}
         </Text>
       )}
+      {claimError && (
+        <Text color="$error" variant="caption">
+          {claimError}
+        </Text>
+      )}
+      {pool.claimableAmountError && (
+        <XStack gap="$1" alignItems="center" flexWrap="wrap">
+          <Text color="$error" variant="caption">
+            Could not load claimable amount.
+          </Text>
+          <Button variant="secondary" size="sm" onPress={onRetryClaimable}>
+            <ButtonText>Retry</ButtonText>
+          </Button>
+        </XStack>
+      )}
 
-      <XStack gap="$2" alignItems="center">
-        <WriteStatusBadge status={connectStatus} />
+      <XStack gap="$2" alignItems="center" flexWrap="wrap">
         {pool.isConnected ? (
-          <Button disabled={isPending} onPress={() => onDisconnect(pool.poolId)}>
-            {isPending ? <Spinner size="sm" /> : <ButtonText>Disconnect</ButtonText>}
-          </Button>
+          <>
+            <WriteStatusBadge status={claimStatus} />
+            <Button
+              variant="secondary"
+              disabled={isConnectPending}
+              borderColor="$error"
+              onPress={() => onDisconnect(pool.poolId)}
+            >
+              {isConnectPending ? (
+                <Spinner size="sm" />
+              ) : (
+                <ButtonText color="$error">Disconnect</ButtonText>
+              )}
+            </Button>
+          </>
         ) : (
-          <Button disabled={isPending} onPress={() => onConnect(pool.poolId)}>
-            {isPending ? <Spinner size="sm" /> : <ButtonText>Connect</ButtonText>}
-          </Button>
+          <>
+            <WriteStatusBadge status={claimStatus} />
+            <Button
+              disabled={!canClaim || isClaimPending || claimStatus === 'success'}
+              onPress={() => onClaim(pool.poolId)}
+            >
+              {isClaimPending ? <Spinner size="sm" /> : <ButtonText>Claim</ButtonText>}
+            </Button>
+            <WriteStatusBadge status={connectStatus} />
+            <Button
+              variant="secondary"
+              disabled={isConnectPending}
+              onPress={() => onConnect(pool.poolId)}
+            >
+              {isConnectPending ? <Spinner size="sm" /> : <ButtonText>Connect</ButtonText>}
+            </Button>
+          </>
         )}
       </XStack>
     </PoolRow>
@@ -508,15 +676,17 @@ function PoolsTab({
   state,
   actions,
 }: {
-  state: ReturnType<typeof useStreamingAdapter>['state']
-  actions: ReturnType<typeof useStreamingAdapter>['actions']
+  state: StreamingWidgetAdapterState
+  actions: StreamingWidgetAdapterActions
 }) {
+  const activeToken = tokenSymbol(state.chainId)
+
   return (
     <StreamingTabContent>
       {state.poolsLoading && (
         <YStack alignItems="center" paddingVertical="$4">
           <Spinner size="lg" />
-          <Text secondary>Loading pool memberships…</Text>
+          <Text secondary>Loading pool memberships...</Text>
         </YStack>
       )}
 
@@ -546,10 +716,15 @@ function PoolsTab({
           <PoolCard
             key={pool.poolId}
             pool={pool}
+            token={activeToken}
             connectStatus={state.poolConnectStatus[pool.poolId] ?? 'idle'}
             connectError={state.poolConnectError[pool.poolId] ?? null}
+            claimStatus={state.poolClaimStatus[pool.poolId] ?? 'idle'}
+            claimError={state.poolClaimError[pool.poolId] ?? null}
             onConnect={actions.connectToPool}
             onDisconnect={actions.disconnectFromPool}
+            onClaim={actions.claimFromPool}
+            onRetryClaimable={actions.refreshPools}
           />
         ))}
     </StreamingTabContent>
@@ -563,10 +738,11 @@ function BalancesTab({
   state,
   actions,
 }: {
-  state: ReturnType<typeof useStreamingAdapter>['state']
-  actions: ReturnType<typeof useStreamingAdapter>['actions']
+  state: StreamingWidgetAdapterState
+  actions: StreamingWidgetAdapterActions
 }) {
   const isOnBase = state.chainId === STREAMING_CHAINS.BASE
+  const activeToken = tokenSymbol(state.chainId)
 
   return (
     <StreamingTabContent>
@@ -575,7 +751,7 @@ function BalancesTab({
         <XStack justifyContent="space-between" alignItems="center">
           <Text variant="label">Super Token Balance</Text>
           <Button onPress={actions.refreshBalance}>
-            <ButtonText>↺ Refresh</ButtonText>
+            <ButtonText>Refresh</ButtonText>
           </Button>
         </XStack>
 
@@ -589,7 +765,7 @@ function BalancesTab({
 
         {!state.balanceLoading && !state.balanceError && state.superTokenBalance !== null && (
           <TokenAmount
-            token={state.chainId === STREAMING_CHAINS.BASE ? 'SUP' : 'G$'}
+            token={activeToken}
             amount={state.superTokenBalance}
             size="xl"
           />
@@ -606,7 +782,7 @@ function BalancesTab({
       {isOnBase ? (
         <BalanceCard>
           <XStack justifyContent="space-between" alignItems="center">
-            <Text variant="label">SUP Reserve (Staked)</Text>
+            <Text variant="label">SUP Reserve</Text>
             {state.supReserveLoading && <Spinner size="sm" />}
           </XStack>
 
@@ -621,8 +797,44 @@ function BalancesTab({
           )}
 
           <Text variant="caption" secondary>
-            SUP tokens locked as reserve on Base.
+            SUP held in reserve lockers on Base.
           </Text>
+
+          {!state.supReserveLoading &&
+            !state.supReserveError &&
+            state.supReserveLockers.map((locker) => (
+              <YStack key={locker.address} gap="$2">
+                <XStack justifyContent="space-between" alignItems="center">
+                  <Text variant="caption" secondary>
+                    Reserve locker
+                  </Text>
+                  <AddressDisplay address={locker.address} size="sm" />
+                </XStack>
+                <XStack justifyContent="space-between" alignItems="center">
+                  <Text variant="caption" secondary>
+                    Available
+                  </Text>
+                  <TokenAmount
+                    token="SUP"
+                    amount={formatWeiAmount(locker.unstakedBalance)}
+                    size="sm"
+                  />
+                </XStack>
+                <XStack justifyContent="space-between" alignItems="center">
+                  <Text variant="caption" secondary>
+                    Staked
+                  </Text>
+                  <TokenAmount
+                    token="SUP"
+                    amount={formatWeiAmount(locker.stakedBalance)}
+                    size="sm"
+                  />
+                </XStack>
+                <Anchor href={superfluidExplorerAccountUrl(state.chainId, locker.address)}>
+                  Open in Superfluid Explorer
+                </Anchor>
+              </YStack>
+            ))}
         </BalanceCard>
       ) : (
         <BalanceCard>
@@ -648,8 +860,24 @@ function StreamingWidgetInner({
   environment: StreamingWidgetProps['environment']
   apiKey?: string
 }) {
-  const { state, actions } = useStreamingAdapter({ environment, apiKey })
-  const [activeTab, setActiveTab] = useState<StreamingWidgetTab>('streams')
+  const adapter = useStreamingAdapter({ environment, apiKey })
+
+  return <StreamingWidgetView adapter={adapter} />
+}
+
+interface StreamingWidgetViewProps {
+  adapter: StreamingWidgetAdapterResult
+  initialTab?: StreamingWidgetTab
+  initialStreamsFormOpen?: boolean
+}
+
+function StreamingWidgetView({
+  adapter,
+  initialTab = 'streams',
+  initialStreamsFormOpen = false,
+}: StreamingWidgetViewProps) {
+  const { state, actions } = adapter
+  const [activeTab, setActiveTab] = useState<StreamingWidgetTab>(initialTab)
 
   const walletGate = (
     <WalletGate
@@ -662,17 +890,25 @@ function StreamingWidgetInner({
 
   const tabContent = !state.isConnected || state.isWrongChain ? walletGate : (
     <>
-      {activeTab === 'streams' && <StreamsTab state={state} actions={actions} />}
+      {activeTab === 'streams' && (
+        <StreamsTab
+          state={state}
+          actions={actions}
+          initialFormOpen={initialStreamsFormOpen}
+        />
+      )}
+      {activeTab === 'history' && <HistoryTab state={state} actions={actions} />}
       {activeTab === 'pools' && <PoolsTab state={state} actions={actions} />}
       {activeTab === 'balances' && <BalancesTab state={state} actions={actions} />}
     </>
   )
 
   return (
-    <YStack gap="$3" padding="$4">
+    <YStack gap="$3" padding="$5" width="100%" style={{ boxSizing: 'border-box' }}>
       <WidgetTabs
         tabs={[
           { id: 'streams', label: 'Streams' },
+          { id: 'history', label: 'History' },
           { id: 'pools', label: 'Pools' },
           { id: 'balances', label: 'Balances' },
         ]}
@@ -685,6 +921,37 @@ function StreamingWidgetInner({
   )
 }
 
+export interface StreamingWidgetPreviewProps
+  extends Pick<StreamingWidgetProps, 'themeOverrides' | 'config' | 'defaultTheme'> {
+  adapter: StreamingWidgetAdapterResult
+  initialTab?: StreamingWidgetTab
+  initialStreamsFormOpen?: boolean
+}
+
+export function StreamingWidgetPreview({
+  adapter,
+  initialTab,
+  initialStreamsFormOpen,
+  themeOverrides,
+  config,
+  defaultTheme = 'light',
+}: StreamingWidgetPreviewProps) {
+  return (
+    <GoodWidgetProvider
+      config={config}
+      themeOverrides={themeOverrides}
+      defaultTheme={defaultTheme}
+    >
+      <StreamingWidgetView
+        adapter={adapter}
+        initialTab={initialTab}
+        initialStreamsFormOpen={initialStreamsFormOpen}
+      />
+      <ToastContainer />
+    </GoodWidgetProvider>
+  )
+}
+
 // ---------------------------------------------------------------------------
 // Public component
 // ---------------------------------------------------------------------------
diff --git a/packages/streaming-widget/src/adapter.ts b/packages/streaming-widget/src/adapter.ts
index 313ce59..9517152 100644
--- a/packages/streaming-widget/src/adapter.ts
+++ b/packages/streaming-widget/src/adapter.ts
@@ -30,6 +30,26 @@ import type {
   WriteStatus,
 } from './widgetRuntimeContract'
 
+const GDA_POOL_CLAIM_ABI = [
+  {
+    type: 'function',
+    name: 'getClaimableNow',
+    inputs: [{ name: 'memberAddr', type: 'address' }],
+    outputs: [
+      { name: 'claimableBalance', type: 'int256' },
+      { name: 'timestamp', type: 'uint256' },
+    ],
+    stateMutability: 'view',
+  },
+  {
+    type: 'function',
+    name: 'claimAll',
+    inputs: [],
+    outputs: [{ name: '', type: 'bool' }],
+    stateMutability: 'nonpayable',
+  },
+] as const
+
 // ---------------------------------------------------------------------------
 // Chain descriptors for Superfluid-supported chains (Celo and Base)
 // ---------------------------------------------------------------------------
@@ -124,10 +144,13 @@ function toStreamListItem(stream: StreamInfo, address: Address): StreamListItem
 // Derive PoolMembershipItem from the SDK GDAPool
 // ---------------------------------------------------------------------------
 function toPoolMembershipItem(pool: GDAPool): PoolMembershipItem {
+  const poolWithClaimable = pool as GDAPool & { claimableAmount?: bigint }
   return {
     poolId: pool.id,
     poolToken: pool.token,
     totalUnits: pool.totalUnits,
+    claimableAmount: poolWithClaimable.claimableAmount ?? 0n,
+    claimableAmountError: false,
     totalAmountClaimed: pool.totalAmountClaimed,
     isConnected: pool.isConnected ?? false,
   }
@@ -148,7 +171,7 @@ function validateSetStreamForm(form: SetStreamFormState): SetStreamFormState {
     return {
       ...form,
       flowRate: null,
-      validationError: 'Recipient must be a valid Ethereum address (0x…).',
+      validationError: 'Recipient must be a valid Ethereum address (0x...).',
     }
   }
 
@@ -178,6 +201,9 @@ export function useStreamingAdapter({
   const [streams, setStreams] = useState<StreamListItem[]>([])
   const [streamsLoading, setStreamsLoading] = useState(false)
   const [streamsError, setStreamsError] = useState<string | null>(null)
+  const [streamHistory, setStreamHistory] = useState<StreamListItem[]>([])
+  const [streamHistoryLoading, setStreamHistoryLoading] = useState(false)
+  const [streamHistoryError, setStreamHistoryError] = useState<string | null>(null)
 
   // --- pools state ---
   const [pools, setPools] = useState<PoolMembershipItem[]>([])
@@ -191,6 +217,9 @@ export function useStreamingAdapter({
 
   // --- SUP reserve state (Base only) ---
   const [supReserveBalance, setSupReserveBalance] = useState<string | null>(null)
+  const [supReserveLockers, setSupReserveLockers] = useState<
+    StreamingWidgetAdapterState['supReserveLockers']
+  >([])
   const [supReserveLoading, setSupReserveLoading] = useState(false)
   const [supReserveError, setSupReserveError] = useState<string | null>(null)
 
@@ -203,6 +232,8 @@ export function useStreamingAdapter({
   // --- pool connect/disconnect state keyed by pool address ---
   const [poolConnectStatus, setPoolConnectStatus] = useState<Record<string, WriteStatus>>({})
   const [poolConnectError, setPoolConnectError] = useState<Record<string, string | null>>({})
+  const [poolClaimStatus, setPoolClaimStatus] = useState<Record<string, WriteStatus>>({})
+  const [poolClaimError, setPoolClaimError] = useState<Record<string, string | null>>({})
 
   // Chain validity
   const isWrongChain = !!chainId && !isSupportedChain(chainId)
@@ -259,16 +290,23 @@ export function useStreamingAdapter({
 
     setStreamsLoading(true)
     setStreamsError(null)
+    setStreamHistoryLoading(true)
+    setStreamHistoryError(null)
     try {
       const result = await streamingSDK.getActiveStreams({
         account: address as Address,
         direction: 'all',
       })
-      setStreams(result.map((s) => toStreamListItem(s, address as Address)))
+      const normalizedStreams = result.map((s) => toStreamListItem(s, address as Address))
+      setStreams(normalizedStreams)
+      setStreamHistory(normalizedStreams)
     } catch (err) {
-      setStreamsError(humanReadableError(err))
+      const message = humanReadableError(err)
+      setStreamsError(message)
+      setStreamHistoryError(message)
     } finally {
       setStreamsLoading(false)
+      setStreamHistoryLoading(false)
     }
   }, [streamingSDK, address])
 
@@ -282,13 +320,39 @@ export function useStreamingAdapter({
     setPoolsError(null)
     try {
       const result = await gdaSDK.getDistributionPools(address as Address)
-      setPools(result.map(toPoolMembershipItem))
+      const normalizedPools = result.map(toPoolMembershipItem)
+      if (!viemClients) {
+        setPools(normalizedPools)
+        return
+      }
+
+      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,
+              claimableAmountError: false,
+            }
+          } catch {
+            return { ...pool, claimableAmountError: true }
+          }
+        }),
+      )
+      setPools(poolsWithClaimable)
     } catch (err) {
       setPoolsError(humanReadableError(err))
     } finally {
       setPoolsLoading(false)
     }
-  }, [gdaSDK, address])
+  }, [gdaSDK, address, viemClients])
 
   // ---------------------------------------------------------------------------
   // Fetch Super Token balance
@@ -312,8 +376,9 @@ export function useStreamingAdapter({
   // Fetch SUP reserve balance (Base only)
   // ---------------------------------------------------------------------------
   const fetchSupReserve = useCallback(async () => {
-    if (!subgraphClient || !address || chainId !== SupportedChains.BASE) {
+    if (!subgraphClient || !streamingSDK || !address || chainId !== SupportedChains.BASE) {
       setSupReserveBalance(null)
+      setSupReserveLockers([])
       return
     }
 
@@ -321,14 +386,31 @@ export function useStreamingAdapter({
     setSupReserveError(null)
     try {
       const lockers = await subgraphClient.querySUPReserves(address as Address)
-      const total = lockers.reduce((sum, l) => sum + l.stakedBalance, 0n)
+      const lockersWithBalances = await Promise.all(
+        lockers.map(async (locker) => {
+          const unstakedBalance = await streamingSDK.getSuperTokenBalance(
+            locker.id as Address,
+            'SUP',
+          )
+          return {
+            address: locker.id as Address,
+            stakedBalance: locker.stakedBalance,
+            unstakedBalance,
+            totalBalance: locker.stakedBalance + unstakedBalance,
+          }
+        }),
+      )
+      const total = lockersWithBalances.reduce((sum, locker) => sum + locker.totalBalance, 0n)
+      setSupReserveLockers(lockersWithBalances)
       setSupReserveBalance(formatUnits(total, 18))
     } catch (err) {
+      setSupReserveBalance(null)
+      setSupReserveLockers([])
       setSupReserveError(humanReadableError(err))
     } finally {
       setSupReserveLoading(false)
     }
-  }, [subgraphClient, address, chainId])
+  }, [subgraphClient, streamingSDK, address, chainId])
 
   // ---------------------------------------------------------------------------
   // Auto-fetch on wallet/chain change
@@ -344,9 +426,15 @@ export function useStreamingAdapter({
 
     if (!isConnected || !address || isWrongChain) {
       setStreams([])
+      setStreamHistory([])
       setPools([])
       setSuperTokenBalance(null)
       setSupReserveBalance(null)
+      setSupReserveLockers([])
+      setPoolConnectStatus({})
+      setPoolConnectError({})
+      setPoolClaimStatus({})
+      setPoolClaimError({})
       return
     }
 
@@ -453,6 +541,34 @@ export function useStreamingAdapter({
     [gdaSDK, fetchPools],
   )
 
+  const claimFromPool = useCallback(
+    async (poolAddress: Address) => {
+      if (!viemClients || !address) return
+
+      setPoolClaimStatus((prev) => ({ ...prev, [poolAddress]: 'pending' }))
+      setPoolClaimError((prev) => ({ ...prev, [poolAddress]: null }))
+
+      try {
+        const hash = await viemClients.walletClient.writeContract({
+          account: address as Address,
+          address: poolAddress,
+          abi: GDA_POOL_CLAIM_ABI,
+          functionName: 'claimAll',
+        })
+        await viemClients.publicClient.waitForTransactionReceipt({ hash })
+        setPoolClaimStatus((prev) => ({ ...prev, [poolAddress]: 'success' }))
+        void fetchPools()
+      } catch (err) {
+        setPoolClaimStatus((prev) => ({ ...prev, [poolAddress]: 'error' }))
+        setPoolClaimError((prev) => ({
+          ...prev,
+          [poolAddress]: humanReadableError(err),
+        }))
+      }
+    },
+    [viemClients, address, fetchPools],
+  )
+
   // ---------------------------------------------------------------------------
   // Chain switch via EIP-1193
   // ---------------------------------------------------------------------------
@@ -480,6 +596,9 @@ export function useStreamingAdapter({
       streams,
       streamsLoading,
       streamsError,
+      streamHistory,
+      streamHistoryLoading,
+      streamHistoryError,
       pools,
       poolsLoading,
       poolsError,
@@ -487,6 +606,7 @@ export function useStreamingAdapter({
       balanceLoading,
       balanceError,
       supReserveBalance,
+      supReserveLockers,
       supReserveLoading,
       supReserveError,
       setStreamForm,
@@ -495,6 +615,8 @@ export function useStreamingAdapter({
       setStreamTxHash,
       poolConnectStatus,
       poolConnectError,
+      poolClaimStatus,
+      poolClaimError,
     }),
     [
       isConnected,
@@ -504,6 +626,9 @@ export function useStreamingAdapter({
       streams,
       streamsLoading,
       streamsError,
+      streamHistory,
+      streamHistoryLoading,
+      streamHistoryError,
       pools,
       poolsLoading,
       poolsError,
@@ -511,6 +636,7 @@ export function useStreamingAdapter({
       balanceLoading,
       balanceError,
       supReserveBalance,
+      supReserveLockers,
       supReserveLoading,
       supReserveError,
       setStreamForm,
@@ -519,6 +645,8 @@ export function useStreamingAdapter({
       setStreamTxHash,
       poolConnectStatus,
       poolConnectError,
+      poolClaimStatus,
+      poolClaimError,
     ],
   )
 
@@ -527,6 +655,7 @@ export function useStreamingAdapter({
       connect,
       switchChain,
       refreshStreams: fetchStreams,
+      refreshStreamHistory: fetchStreams,
       refreshPools: fetchPools,
       refreshBalance: fetchBalance,
       updateSetStreamForm,
@@ -534,6 +663,7 @@ export function useStreamingAdapter({
       resetSetStream,
       connectToPool,
       disconnectFromPool,
+      claimFromPool,
     }),
     [
       connect,
@@ -546,6 +676,7 @@ export function useStreamingAdapter({
       resetSetStream,
       connectToPool,
       disconnectFromPool,
+      claimFromPool,
     ],
   )
 
diff --git a/packages/streaming-widget/src/index.ts b/packages/streaming-widget/src/index.ts
index 77266d2..2981ad4 100644
--- a/packages/streaming-widget/src/index.ts
+++ b/packages/streaming-widget/src/index.ts
@@ -7,6 +7,7 @@ export type {
   StreamTimeUnit,
   StreamListItem,
   PoolMembershipItem,
+  SupReserveLockerItem,
   SetStreamFormState,
   WriteStatus,
   StreamingWidgetAdapterState,
@@ -20,4 +21,5 @@ export { useStreamingAdapter } from './adapter'
 export type { UseStreamingAdapterOptions } from './adapter'
 
 // Widget component
-export { StreamingWidget } from './StreamingWidget'
+export { StreamingWidget, StreamingWidgetPreview } from './StreamingWidget'
+export type { StreamingWidgetPreviewProps } from './StreamingWidget'
diff --git a/packages/streaming-widget/src/widgetRuntimeContract.ts b/packages/streaming-widget/src/widgetRuntimeContract.ts
index 9f1900c..f281390 100644
--- a/packages/streaming-widget/src/widgetRuntimeContract.ts
+++ b/packages/streaming-widget/src/widgetRuntimeContract.ts
@@ -29,7 +29,7 @@ export type StreamTimeUnit = 'second' | 'minute' | 'hour' | 'day' | 'week' | 'mo
 // ---------------------------------------------------------------------------
 // Widget tab IDs
 // ---------------------------------------------------------------------------
-export type StreamingWidgetTab = 'streams' | 'pools' | 'balances'
+export type StreamingWidgetTab = 'streams' | 'history' | 'pools' | 'balances'
 
 // ---------------------------------------------------------------------------
 // Operation lifecycle status for write actions
@@ -62,11 +62,25 @@ export interface PoolMembershipItem {
   poolId: Address
   poolToken: Address
   totalUnits: bigint
+  /** Claimable incoming distribution amount in wei, when exposed by the data source */
+  claimableAmount: bigint
+  /** True when the claimable amount read fails and should be retried */
+  claimableAmountError: boolean
   totalAmountClaimed: bigint
   /** Whether this account has actively connected to the pool distribution */
   isConnected: boolean
 }
 
+// ---------------------------------------------------------------------------
+// SUP reserve locker displayed on Base
+// ---------------------------------------------------------------------------
+export interface SupReserveLockerItem {
+  address: Address
+  stakedBalance: bigint
+  unstakedBalance: bigint
+  totalBalance: bigint
+}
+
 // ---------------------------------------------------------------------------
 // Form state for create/update stream
 // ---------------------------------------------------------------------------
@@ -96,6 +110,9 @@ export interface StreamingWidgetAdapterState {
   streams: StreamListItem[]
   streamsLoading: boolean
   streamsError: string | null
+  streamHistory: StreamListItem[]
+  streamHistoryLoading: boolean
+  streamHistoryError: string | null
 
   /** GDA pool memberships for the connected address */
   pools: PoolMembershipItem[]
@@ -109,6 +126,7 @@ export interface StreamingWidgetAdapterState {
 
   /** SUP reserve data — only populated on Base */
   supReserveBalance: string | null
+  supReserveLockers: SupReserveLockerItem[]
   supReserveLoading: boolean
   supReserveError: string | null
 
@@ -121,6 +139,9 @@ export interface StreamingWidgetAdapterState {
   /** Pool connect/disconnect write status keyed by pool address */
   poolConnectStatus: Record<string, WriteStatus>
   poolConnectError: Record<string, string | null>
+  /** Pool claim write status keyed by pool address */
+  poolClaimStatus: Record<string, WriteStatus>
+  poolClaimError: Record<string, string | null>
 }
 
 // ---------------------------------------------------------------------------
@@ -130,6 +151,7 @@ export interface StreamingWidgetAdapterActions {
   connect: () => Promise<void>
   switchChain: (chainId: number) => Promise<void>
   refreshStreams: () => Promise<void>
+  refreshStreamHistory: () => Promise<void>
   refreshPools: () => Promise<void>
   refreshBalance: () => Promise<void>
 
@@ -144,6 +166,8 @@ export interface StreamingWidgetAdapterActions {
   connectToPool: (poolAddress: Address) => Promise<void>
   /** Disconnect wallet from a GDA pool */
   disconnectFromPool: (poolAddress: Address) => Promise<void>
+  /** Claim all currently claimable distributions from a GDA pool */
+  claimFromPool: (poolAddress: Address) => Promise<void>
 }
 
 export interface StreamingWidgetAdapterResult {
diff --git a/packages/ui/src/components/Button.tsx b/packages/ui/src/components/Button.tsx
index 09fafe4..24874ed 100644
--- a/packages/ui/src/components/Button.tsx
+++ b/packages/ui/src/components/Button.tsx
@@ -32,8 +32,8 @@ export const ButtonFrame = createComponent(Stack, {
   backgroundColor: '$background',
   // GoodWalletV2 brand: solid buttons use pill radius by default
   borderRadius: '$full',
-  paddingHorizontal: '$4',
-  height: '$10',
+  paddingHorizontal: '$3',
+  height: '$7',
   gap: '$2',
   cursor: 'pointer',
   borderWidth: 0,
@@ -109,9 +109,9 @@ export const ButtonFrame = createComponent(Stack, {
 
     // Standard interactive sizes
     size: {
-      sm: { height: '$8', paddingHorizontal: '$3', gap: '$1' },
-      md: { height: '$10', paddingHorizontal: '$4', gap: '$2' },
-      lg: { height: '$11', paddingHorizontal: '$5', gap: '$2' },
+      sm: { height: '$6', paddingHorizontal: '$2', gap: '$1' },
+      md: { height: '$7', paddingHorizontal: '$3', gap: '$2' },
+      lg: { height: '$8', paddingHorizontal: '$4', gap: '$2' },
     },
 
     // Icon-only sizes (transparent bg, square, no padding)
diff --git a/packages/ui/src/components/TokenAmount.tsx b/packages/ui/src/components/TokenAmount.tsx
index fd6ebe3..4326128 100644
--- a/packages/ui/src/components/TokenAmount.tsx
+++ b/packages/ui/src/components/TokenAmount.tsx
@@ -51,19 +51,31 @@ interface TokenAmountProps {
   decimals?: number
   variant?: 'secondary'
   useAbbreviations?: boolean
+  maxSignificantDigits?: number
 }
 
-const getDecimals = (value: number): number => {
-  const absValue = Math.abs(value)
-  if (absValue >= 1 || absValue === 0) return 2
-  if (absValue >= 0.1) return 3
-  if (absValue >= 0.01) return 4
-  if (absValue >= 0.001) return 5
-  if (absValue >= 0.0001) return 6
-  if (absValue >= 0.00001) return 7
-  if (absValue >= 0.000001) return 8
-  if (absValue >= 0.0000001) return 9
-  return 10
+export function formatDisplayAmount(
+  amount: string | number,
+  {
+    decimals = 2,
+    useAbbreviations = true,
+    maxSignificantDigits = 6,
+  }: {
+    decimals?: number
+    useAbbreviations?: boolean
+    maxSignificantDigits?: number
+  } = {},
+): string {
+  const amountNumber = typeof amount === 'number' ? amount : Number(amount)
+  if (!Number.isFinite(amountNumber)) return String(amount)
+  if (!useAbbreviations) return amountNumber.toFixed(decimals)
+
+  return new Intl.NumberFormat('en-US', {
+    maximumSignificantDigits: maxSignificantDigits,
+    minimumFractionDigits: 0,
+    notation: Math.abs(amountNumber) >= 1_000_000 ? 'compact' : 'standard',
+    useGrouping: true,
+  }).format(amountNumber)
 }
 
 export function TokenAmount({
@@ -73,19 +85,15 @@ export function TokenAmount({
   decimals = 2,
   variant,
   useAbbreviations = true,
+  maxSignificantDigits = 6,
 }: TokenAmountProps) {
   const fontSize = { sm: '$3', md: '$5', lg: '$7', xl: '$9' } as const
 
-  const amountNumber = typeof amount === 'number' ? amount : parseFloat(amount)
-  const formatted = useAbbreviations
-    ? new Intl.NumberFormat('en-US', {
-        style: 'decimal',
-        minimumFractionDigits: 0,
-        maximumFractionDigits: getDecimals(amountNumber),
-        useGrouping: true,
-        notation: 'compact',
-      }).format(amountNumber)
-    : amountNumber.toFixed(decimals)
+  const formatted = formatDisplayAmount(amount, {
+    decimals,
+    useAbbreviations,
+    maxSignificantDigits,
+  })
 
   return (
     <TokenAmountFrame size={size}>
diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
index 27a33dc..32225c5 100644
--- a/packages/ui/src/index.ts
+++ b/packages/ui/src/index.ts
@@ -86,7 +86,7 @@ export type { DialogConfig, DialogStatus } from './components/Dialog'
 
 // Web3
 export { AddressDisplay } from './components-test/AddressDisplay'
-export { TokenAmount } from './components/TokenAmount'
+export { TokenAmount, formatDisplayAmount } from './components/TokenAmount'
 export { TransactionButton } from './components-test/TransactionButton'
 export { ChainBadge } from './components-test/ChainBadge'
 export { WalletInfo } from './components-test/WalletInfo'
diff --git a/tests/widgets/streaming-widget/states.spec.ts b/tests/widgets/streaming-widget/states.spec.ts
index ea576b5..c22fed2 100644
--- a/tests/widgets/streaming-widget/states.spec.ts
+++ b/tests/widgets/streaming-widget/states.spec.ts
@@ -1,285 +1,227 @@
-/**
- * states.spec.ts — Playwright smoke tests for the StreamingWidget.
- *
- * Tests use the NoWallet story (no provider) to verify the widget shell renders and
- * surfaces the connect-wallet prompt, and the CustodialLocalFixture story to verify
- * the connected flow on Celo with an empty-history test wallet.
- *
- * Story URLs:
- *   /iframe.html?id=widgets-streamingwidget--no-wallet&viewMode=story
- *   /iframe.html?id=widgets-streamingwidget--custodial-local-fixture&viewMode=story
- *
- * Browser flags (set globally in playwright.config.ts):
- *   --disable-web-security     : allows viem fetch calls from localhost to external HTTPS RPC
- *   --ignore-certificate-errors: allows Chromium to accept RPC endpoint TLS certs
- *
- * Running:
- *   pnpm storybook          (in one terminal)
- *   pnpm test:demo          (in another terminal)
- *
- * Artifact output:
- *   tests/widgets/streaming-widget/test-results/   (widget screenshot evidence)
- *   test-results/                                  (Playwright traces/videos/attachments)
- */
 import { test, expect, type Page } from '@playwright/test'
 
-const NO_WALLET_STORY_URL =
-  '/iframe.html?id=widgets-streamingwidget--no-wallet&viewMode=story'
+const STORY_PREFIX = '/iframe.html?id=widgets-streamingwidget--'
 
-const CUSTODIAL_STORY_URL =
-  '/iframe.html?id=widgets-streamingwidget--custodial-local-fixture&viewMode=story'
+function storyUrl(storyId: string): string {
+  return `${STORY_PREFIX}${storyId}&viewMode=story`
+}
 
-/** Navigate directly to the story iframe (bypasses Storybook shell for speed). */
-async function gotoStory(page: Page, url: string): Promise<void> {
-  await page.goto(url)
+async function gotoStory(page: Page, storyId: string): Promise<void> {
+  await page.goto(storyUrl(storyId))
   await page.waitForLoadState('domcontentloaded')
+  await page.waitForFunction(() => document.body.innerText.trim().length > 0)
+}
+
+async function bodyText(page: Page): Promise<string> {
+  return page.evaluate(() => document.body.innerText)
 }
 
-/** Poll the page until any of the given text patterns appears in the body. */
-async function waitForText(
-  page: Page,
-  patterns: string[],
-  timeoutMs = 30_000,
-): Promise<string> {
-  const deadline = Date.now() + timeoutMs
-  while (Date.now() < deadline) {
-    const text = await page.evaluate(() => document.body.innerText)
-    for (const p of patterns) {
-      if (text.includes(p)) return p
+async function expectBodyToContain(page: Page, patterns: Array<string | RegExp>) {
+  const text = await bodyText(page)
+  for (const pattern of patterns) {
+    if (typeof pattern === 'string') {
+      expect(text).toContain(pattern)
+    } else {
+      expect(text).toMatch(pattern)
     }
-    await page.waitForTimeout(500)
   }
-  return ''
 }
 
-// ─── no-wallet state ─────────────────────────────────────────────────────────
-test('StreamingWidget shows connect-wallet prompt when no provider is given', async ({ page }) => {
-  await gotoStory(page, NO_WALLET_STORY_URL)
-
-  // The widget should render the tab bar and the connect-wallet prompt
-  const matched = await waitForText(page, ['Connect Wallet', 'not connected', 'Streams'], 20_000)
-  expect(matched, 'Expected connect-wallet prompt').toBeTruthy()
-
-  const bodyText = await page.evaluate(() => document.body.innerText)
-  expect(bodyText).toMatch(/Connect Wallet|not connected/i)
-
+async function saveScreenshot(page: Page, name: string) {
   await page.screenshot({
-    path: 'tests/widgets/streaming-widget/test-results/sw-01-no-wallet.png',
+    path: `tests/widgets/streaming-widget/test-results/${name}.png`,
     fullPage: true,
   })
-})
-
-// ─── tab navigation ───────────────────────────────────────────────────────────
-test('StreamingWidget renders Streams, Pools, Balances tabs', async ({ page }) => {
-  await gotoStory(page, NO_WALLET_STORY_URL)
-
-  const matched = await waitForText(page, ['Streams', 'Pools', 'Balances'], 20_000)
-  expect(matched, 'Tab bar must render').toBeTruthy()
+}
 
-  const bodyText = await page.evaluate(() => document.body.innerText)
-  expect(bodyText).toContain('Streams')
-  expect(bodyText).toContain('Pools')
-  expect(bodyText).toContain('Balances')
+test('StreamingWidget shows the disconnected wallet gate', async ({ page }) => {
+  await gotoStory(page, 'no-wallet')
 
-  await page.screenshot({
-    path: 'tests/widgets/streaming-widget/test-results/sw-02-tabs-visible.png',
-    fullPage: true,
-  })
+  await expectBodyToContain(page, ['Wallet not connected', 'Connect Wallet'])
+  await saveScreenshot(page, 'sw-01-no-wallet')
 })
 
-// ─── wrong-chain state ────────────────────────────────────────────────────────
-test('StreamingWidget shows wrong-chain prompt when wallet is on unsupported chain', async ({
-  page,
-}) => {
-  // Inject a minimal EIP-1193 provider that reports an unsupported chain (chain 1 = Ethereum mainnet)
-  await gotoStory(page, NO_WALLET_STORY_URL)
-
-  // Evaluate a mock provider in the page context to simulate wrong chain
-  await page.evaluate(() => {
-    const mockProvider = {
-      on: () => {},
-      removeListener: () => {},
-      request: async ({ method }: { method: string }) => {
-        if (method === 'eth_accounts' || method === 'eth_requestAccounts')
-          return ['0x1234567890123456789012345678901234567890']
-        if (method === 'eth_chainId') return '0x1' // Ethereum mainnet (unsupported)
-        if (method === 'net_version') return '1'
-        return null
-      },
-    }
-    ;(window as unknown as Record<string, unknown>).__mockProvider = mockProvider
-  })
+test('StreamingWidget renders tab navigation and switches views', async ({ page }) => {
+  await gotoStory(page, 'populated-state')
 
-  // The story renders with no provider so we can't easily inject — just verify tab bar renders
-  const matched = await waitForText(page, ['Streams', 'Pools', 'Balances'], 15_000)
-  expect(matched).toBeTruthy()
+  await expectBodyToContain(page, ['Streams', 'History', 'Pools', 'Balances', 'Active streams'])
 
-  await page.screenshot({
-    path: 'tests/widgets/streaming-widget/test-results/sw-03-no-wallet-tabs.png',
-    fullPage: true,
-  })
-})
+  await page.getByText('History').first().click()
+  await expectBodyToContain(page, ['Stream history', 'Show more'])
 
-// ─── custodial: loading + empty states ────────────────────────────────────────
-test('StreamingWidget custodial fixture — Streams tab shows loading then empty state', async ({
-  page,
-  browserName,
-}) => {
-  test.skip(
-    browserName !== 'chromium',
-    'Live RPC test requires --disable-web-security / --ignore-certificate-errors',
-  )
-
-  // Route subgraph calls to never respond — intentionally hangs to keep the
-  // widget in the loading state indefinitely so we can screenshot that state.
-  await page.route('https://subgraph-gateway.superfluid.finance/**', () => {
-    /* intentional hang — never fulfill, never abort */
-  })
-  await page.route('https://gateway-arbitrum.network.thegraph.com/**', () => {
-    /* intentional hang — never fulfill, never abort */
-  })
+  await page.getByText('Pools').first().click()
+  await expectBodyToContain(page, ['Claimable', 'Claim', 'Connect'])
 
-  await gotoStory(page, CUSTODIAL_STORY_URL)
+  await page.getByText('Balances').first().click()
+  await expectBodyToContain(page, ['Super Token Balance', 'SUP Reserve'])
 
-  // Tab bar should render first
-  const tabsVisible = await waitForText(page, ['Streams', 'Pools', 'Balances'], 30_000)
-  expect(tabsVisible).toBeTruthy()
-
-  await page.screenshot({
-    path: 'tests/widgets/streaming-widget/test-results/sw-04-loading.png',
-    fullPage: true,
-  })
+  await saveScreenshot(page, 'sw-02-tab-navigation')
 })
 
-// ─── custodial: RPC blocked → error state ────────────────────────────────────
-test('StreamingWidget custodial fixture — shows error state when RPC is blocked', async ({
-  page,
-  browserName,
-}) => {
-  test.skip(
-    browserName !== 'chromium',
-    'Live RPC test requires --disable-web-security / --ignore-certificate-errors',
-  )
+test('StreamingWidget shows the unsupported network prompt', async ({ page }) => {
+  await gotoStory(page, 'wrong-chain')
 
-  // Block all Celo RPC and subgraph endpoints to force error state
-  await page.route('https://forno.celo.org/**', (route) => route.abort())
-  await page.route('https://subgraph-gateway.superfluid.finance/**', (route) => route.abort())
-  await page.route('https://gateway-arbitrum.network.thegraph.com/**', (route) => route.abort())
+  await expectBodyToContain(page, [
+    'Unsupported network',
+    'Switch to Celo',
+    'Switch to Base',
+  ])
+  await saveScreenshot(page, 'sw-03-wrong-chain')
+})
 
-  await gotoStory(page, CUSTODIAL_STORY_URL)
+test('StreamingWidget shows loading states for streams and history', async ({ page }) => {
+  await gotoStory(page, 'loading-state')
 
-  // After RPCs abort, adapter should surface error state with Retry button
-  const matched = await waitForText(page, ['Retry', 'error', 'reach'], 25_000)
-  expect(matched, 'Expected error state after RPC abort').toBeTruthy()
+  await expectBodyToContain(page, ['Loading streams'])
 
-  await page.screenshot({
-    path: 'tests/widgets/streaming-widget/test-results/sw-05-error.png',
-    fullPage: true,
-  })
+  await page.getByText('History').first().click()
+  await expectBodyToContain(page, ['Loading stream history'])
+  await saveScreenshot(page, 'sw-04-loading-state')
 })
 
-// ─── custodial: pools tab navigation ─────────────────────────────────────────
-test('StreamingWidget custodial fixture — clicking Pools tab changes view', async ({
-  page,
-  browserName,
-}) => {
-  test.skip(
-    browserName !== 'chromium',
-    'Live RPC test requires --disable-web-security / --ignore-certificate-errors',
-  )
+test('StreamingWidget shows empty states for streams and history', async ({ page }) => {
+  await gotoStory(page, 'empty-state')
 
-  // Block external calls to keep the test deterministic
-  await page.route('https://forno.celo.org/**', (route) => route.abort())
-  await page.route('https://subgraph-gateway.superfluid.finance/**', (route) => route.abort())
+  await expectBodyToContain(page, ['No streams found.'])
 
-  await gotoStory(page, CUSTODIAL_STORY_URL)
-
-  // Wait for tab bar to render
-  await waitForText(page, ['Streams', 'Pools', 'Balances'], 20_000)
-
-  // Click the Pools tab
-  const poolsTab = page.getByText('Pools').first()
-  await expect(poolsTab).toBeVisible()
-  await poolsTab.click()
+  await page.getByText('History').first().click()
+  await expectBodyToContain(page, ['No stream history found.'])
+  await saveScreenshot(page, 'sw-05-empty-state')
+})
 
-  await page.waitForTimeout(500)
+test('StreamingWidget shows error states for streams and history', async ({ page }) => {
+  await gotoStory(page, 'error-state')
 
-  await page.screenshot({
-    path: 'tests/widgets/streaming-widget/test-results/sw-06-pools-tab.png',
-    fullPage: true,
-  })
+  await expectBodyToContain(page, ['Unable to reach the network', 'Retry'])
 
-  // Verify the tab content changed (either loading, error, or empty state for pools)
-  const bodyText = await page.evaluate(() => document.body.innerText)
-  expect(bodyText).toContain('Pools')
+  await page.getByText('History').first().click()
+  await expectBodyToContain(page, ['Unable to load stream history.', 'Retry'])
+  await saveScreenshot(page, 'sw-06-error-state')
 })
 
-// ─── custodial: balances tab navigation ──────────────────────────────────────
-test('StreamingWidget custodial fixture — clicking Balances tab shows balance section', async ({
-  page,
-  browserName,
-}) => {
-  test.skip(
-    browserName !== 'chromium',
-    'Live RPC test requires --disable-web-security / --ignore-certificate-errors',
-  )
+test('StreamingWidget shows populated incoming and outgoing stream views', async ({ page }) => {
+  await gotoStory(page, 'populated-state')
 
-  // Block external calls
-  await page.route('https://forno.celo.org/**', (route) => route.abort())
-  await page.route('https://subgraph-gateway.superfluid.finance/**', (route) => route.abort())
+  await expectBodyToContain(page, [
+    'Active streams',
+    'Incoming',
+    'Outgoing',
+    /100\s+G\$\s*\/mo/,
+  ])
 
-  await gotoStory(page, CUSTODIAL_STORY_URL)
+  await page.getByText('Incoming').first().click()
+  await expectBodyToContain(page, ['Incoming'])
 
-  await waitForText(page, ['Streams', 'Pools', 'Balances'], 20_000)
+  await page.getByText('Outgoing').first().click()
+  await expectBodyToContain(page, ['Outgoing'])
 
-  // Click Balances tab
-  const balancesTab = page.getByText('Balances').first()
-  await expect(balancesTab).toBeVisible()
-  await balancesTab.click()
+  await saveScreenshot(page, 'sw-07-populated-streams')
 
-  await page.waitForTimeout(500)
+  await page.getByText('History').first().click()
+  await expectBodyToContain(page, ['Stream history', 'Show more'])
+  await saveScreenshot(page, 'sw-22-stream-history-tab')
+})
 
-  await page.screenshot({
-    path: 'tests/widgets/streaming-widget/test-results/sw-07-balances-tab.png',
-    fullPage: true,
-  })
+test('StreamingWidget renders usable mobile and desktop layouts', async ({ page }) => {
+  await page.setViewportSize({ width: 390, height: 844 })
+  await gotoStory(page, 'populated-state')
+  await expectBodyToContain(page, ['Streams', 'History', 'Active streams'])
+  await saveScreenshot(page, 'sw-18-mobile-populated')
 
-  // Balances tab shows Super Token Balance section and the SUP reserve disabled notice for non-Base
-  const bodyText = await page.evaluate(() => document.body.innerText)
-  expect(bodyText).toMatch(/Balance|Refresh|Super Token/i)
+  await page.setViewportSize({ width: 1280, height: 900 })
+  await gotoStory(page, 'populated-state')
+  await expectBodyToContain(page, ['Streams', 'History', 'Pools', 'Balances', 'Active streams'])
+  await saveScreenshot(page, 'sw-19-desktop-populated')
 })
 
-// ─── custodial: create-stream form toggle ────────────────────────────────────
-test('StreamingWidget custodial fixture — New Stream button toggles form', async ({
-  page,
-  browserName,
-}) => {
-  test.skip(
-    browserName !== 'chromium',
-    'Live RPC test requires --disable-web-security / --ignore-certificate-errors',
-  )
+test('StreamingWidget create/update form shows invalid input feedback', async ({ page }) => {
+  await gotoStory(page, 'create-update-invalid-input')
 
-  // Block external calls for determinism
-  await page.route('https://forno.celo.org/**', (route) => route.abort())
-  await page.route('https://subgraph-gateway.superfluid.finance/**', (route) => route.abort())
+  await expectBodyToContain(page, [
+    'Create / Update Stream',
+    'Recipient must be a valid Ethereum address',
+  ])
+  await saveScreenshot(page, 'sw-08-create-update-invalid')
+})
 
-  await gotoStory(page, CUSTODIAL_STORY_URL)
+test('StreamingWidget create/update form shows pending and success states', async ({ page }) => {
+  await gotoStory(page, 'create-update-pending')
+  await expectBodyToContain(page, ['Create / Update Stream', 'Transaction pending...'])
+  await saveScreenshot(page, 'sw-09-create-update-pending')
 
-  // Wait for Streams tab to render with the New Stream button
-  await waitForText(page, ['New Stream'], 20_000)
+  await gotoStory(page, 'create-update-success')
+  await expectBodyToContain(page, ['Create / Update Stream', 'Stream set! Tx:'])
+  await saveScreenshot(page, 'sw-10-create-update-success')
+})
 
-  const newStreamBtn = page.getByText('+ New Stream').first()
-  await expect(newStreamBtn).toBeVisible()
-  await newStreamBtn.click()
+test('StreamingWidget create/update form shows failure state', async ({ page }) => {
+  await gotoStory(page, 'create-update-failure')
 
-  // Form should appear with recipient and amount fields
-  await waitForText(page, ['Recipient address', 'Amount', 'Set Stream'], 5_000)
+  await expectBodyToContain(page, ['Create / Update Stream', 'Transaction cancelled by wallet.'])
+  await saveScreenshot(page, 'sw-11-create-update-failure')
+})
 
-  const bodyText = await page.evaluate(() => document.body.innerText)
-  expect(bodyText).toMatch(/Recipient|Amount|Set Stream/i)
+test('StreamingWidget shows pool claim amount and lifecycle states', async ({ page }) => {
+  await gotoStory(page, 'pool-claim-state')
+  await expectBodyToContain(page, ['Claimable', '12.5', 'Claim'])
+  await saveScreenshot(page, 'sw-12-pool-claim')
+
+  await gotoStory(page, 'pool-connected-state')
+  await expectBodyToContain(page, ['Connected', 'Claimable', 'Disconnect'])
+  await expect(page.getByText('Claim', { exact: true })).toHaveCount(0)
+  await expect(page.getByText('Connect', { exact: true })).toHaveCount(0)
+  await saveScreenshot(page, 'sw-23-pool-connected')
+
+  await gotoStory(page, 'pool-claim-pending')
+  await expectBodyToContain(page, ['Connected', 'Claimable', '12.5', 'Pending', 'Disconnect'])
+  await expect(page.getByText('Claim', { exact: true })).toHaveCount(0)
+  await expect(page.getByText('Connect', { exact: true })).toHaveCount(0)
+  await saveScreenshot(page, 'sw-13-pool-claim-pending')
+
+  await gotoStory(page, 'pool-claim-success')
+  await expectBodyToContain(page, ['Connected', 'Claimable', 'Done', 'Disconnect'])
+  await expect(page.getByText('Claim', { exact: true })).toHaveCount(0)
+  await expect(page.getByText('Connect', { exact: true })).toHaveCount(0)
+  await saveScreenshot(page, 'sw-14-pool-claim-success')
+
+  await gotoStory(page, 'pool-claim-error')
+  await expectBodyToContain(page, [
+    'Connected',
+    'Pool claim failed. Please retry.',
+    'Failed',
+    'Disconnect',
+  ])
+  await expect(page.getByText('Claim', { exact: true })).toHaveCount(0)
+  await expect(page.getByText('Connect', { exact: true })).toHaveCount(0)
+  await saveScreenshot(page, 'sw-15-pool-claim-error')
+
+  await gotoStory(page, 'pool-claimable-amount-error')
+  await expectBodyToContain(page, ['Could not load claimable amount.', 'Retry'])
+  await saveScreenshot(page, 'sw-20-pool-claimable-amount-error')
+
+  await page.getByText('Retry').first().click()
+  await expectBodyToContain(page, ['Loading pool memberships'])
+  await saveScreenshot(page, 'sw-21-pool-claimable-retry-loading')
+})
 
-  await page.screenshot({
-    path: 'tests/widgets/streaming-widget/test-results/sw-08-create-stream-form.png',
-    fullPage: true,
-  })
+test('StreamingWidget shows Base SUP reserve and disables reserve off Base', async ({ page }) => {
+  await gotoStory(page, 'base-sup-balance-and-reserve')
+  await expectBodyToContain(page, [
+    'Super Token Balance',
+    'SUP Reserve',
+    '112.75',
+    'Reserve locker',
+    'Available',
+    'Staked',
+    'Open in Superfluid Explorer',
+  ])
+  await saveScreenshot(page, 'sw-16-base-sup-reserve')
+
+  await gotoStory(page, 'non-base-sup-reserve-disabled')
+  await expectBodyToContain(page, [
+    'Super Token Balance',
+    'SUP Reserve',
+    'Reserve data is only available on Base',
+  ])
+  await saveScreenshot(page, 'sw-17-non-base-reserve-disabled')
 })
diff --git a/tests/widgets/streaming-widget/test-results/sw-01-no-wallet.png b/tests/widgets/streaming-widget/test-results/sw-01-no-wallet.png
new file mode 100644
index 0000000..2160ecb
Binary files /dev/null and b/tests/widgets/streaming-widget/test-results/sw-01-no-wallet.png differ
diff --git a/tests/widgets/streaming-widget/test-results/sw-02-tab-navigation.png b/tests/widgets/streaming-widget/test-results/sw-02-tab-navigation.png
new file mode 100644
index 0000000..66eaf30
Binary files /dev/null and b/tests/widgets/streaming-widget/test-results/sw-02-tab-navigation.png differ
diff --git a/tests/widgets/streaming-widget/test-results/sw-03-wrong-chain.png b/tests/widgets/streaming-widget/test-results/sw-03-wrong-chain.png
new file mode 100644
index 0000000..312c1b6
Binary files /dev/null and b/tests/widgets/streaming-widget/test-results/sw-03-wrong-chain.png differ
diff --git a/tests/widgets/streaming-widget/test-results/sw-04-loading-state.png b/tests/widgets/streaming-widget/test-results/sw-04-loading-state.png
new file mode 100644
index 0000000..796aecd
Binary files /dev/null and b/tests/widgets/streaming-widget/test-results/sw-04-loading-state.png differ
diff --git a/tests/widgets/streaming-widget/test-results/sw-05-empty-state.png b/tests/widgets/streaming-widget/test-results/sw-05-empty-state.png
new file mode 100644
index 0000000..86ef300
Binary files /dev/null and b/tests/widgets/streaming-widget/test-results/sw-05-empty-state.png differ
diff --git a/tests/widgets/streaming-widget/test-results/sw-06-error-state.png b/tests/widgets/streaming-widget/test-results/sw-06-error-state.png
new file mode 100644
index 0000000..b3ad019
Binary files /dev/null and b/tests/widgets/streaming-widget/test-results/sw-06-error-state.png differ
diff --git a/tests/widgets/streaming-widget/test-results/sw-07-populated-streams.png b/tests/widgets/streaming-widget/test-results/sw-07-populated-streams.png
new file mode 100644
index 0000000..308ad6c
Binary files /dev/null and b/tests/widgets/streaming-widget/test-results/sw-07-populated-streams.png differ
diff --git a/tests/widgets/streaming-widget/test-results/sw-08-create-update-invalid.png b/tests/widgets/streaming-widget/test-results/sw-08-create-update-invalid.png
new file mode 100644
index 0000000..3b91e4d
Binary files /dev/null and b/tests/widgets/streaming-widget/test-results/sw-08-create-update-invalid.png differ
diff --git a/tests/widgets/streaming-widget/test-results/sw-09-create-update-pending.png b/tests/widgets/streaming-widget/test-results/sw-09-create-update-pending.png
new file mode 100644
index 0000000..3e18f6d
Binary files /dev/null and b/tests/widgets/streaming-widget/test-results/sw-09-create-update-pending.png differ
diff --git a/tests/widgets/streaming-widget/test-results/sw-10-create-update-success.png b/tests/widgets/streaming-widget/test-results/sw-10-create-update-success.png
new file mode 100644
index 0000000..6843024
Binary files /dev/null and b/tests/widgets/streaming-widget/test-results/sw-10-create-update-success.png differ
diff --git a/tests/widgets/streaming-widget/test-results/sw-11-create-update-failure.png b/tests/widgets/streaming-widget/test-results/sw-11-create-update-failure.png
new file mode 100644
index 0000000..7d340bb
Binary files /dev/null and b/tests/widgets/streaming-widget/test-results/sw-11-create-update-failure.png differ
diff --git a/tests/widgets/streaming-widget/test-results/sw-12-pool-claim.png b/tests/widgets/streaming-widget/test-results/sw-12-pool-claim.png
new file mode 100644
index 0000000..f77055a
Binary files /dev/null and b/tests/widgets/streaming-widget/test-results/sw-12-pool-claim.png differ
diff --git a/tests/widgets/streaming-widget/test-results/sw-13-pool-claim-pending.png b/tests/widgets/streaming-widget/test-results/sw-13-pool-claim-pending.png
new file mode 100644
index 0000000..e9fb1a7
Binary files /dev/null and b/tests/widgets/streaming-widget/test-results/sw-13-pool-claim-pending.png differ
diff --git a/tests/widgets/streaming-widget/test-results/sw-14-pool-claim-success.png b/tests/widgets/streaming-widget/test-results/sw-14-pool-claim-success.png
new file mode 100644
index 0000000..be9d030
Binary files /dev/null and b/tests/widgets/streaming-widget/test-results/sw-14-pool-claim-success.png differ
diff --git a/tests/widgets/streaming-widget/test-results/sw-15-pool-claim-error.png b/tests/widgets/streaming-widget/test-results/sw-15-pool-claim-error.png
new file mode 100644
index 0000000..c75842d
Binary files /dev/null and b/tests/widgets/streaming-widget/test-results/sw-15-pool-claim-error.png differ
diff --git a/tests/widgets/streaming-widget/test-results/sw-16-base-sup-reserve.png b/tests/widgets/streaming-widget/test-results/sw-16-base-sup-reserve.png
new file mode 100644
index 0000000..bb85df8
Binary files /dev/null and b/tests/widgets/streaming-widget/test-results/sw-16-base-sup-reserve.png differ
diff --git a/tests/widgets/streaming-widget/test-results/sw-17-non-base-reserve-disabled.png b/tests/widgets/streaming-widget/test-results/sw-17-non-base-reserve-disabled.png
new file mode 100644
index 0000000..66eaf30
Binary files /dev/null and b/tests/widgets/streaming-widget/test-results/sw-17-non-base-reserve-disabled.png differ
diff --git a/tests/widgets/streaming-widget/test-results/sw-18-mobile-populated.png b/tests/widgets/streaming-widget/test-results/sw-18-mobile-populated.png
new file mode 100644
index 0000000..742cc15
Binary files /dev/null and b/tests/widgets/streaming-widget/test-results/sw-18-mobile-populated.png differ
diff --git a/tests/widgets/streaming-widget/test-results/sw-19-desktop-populated.png b/tests/widgets/streaming-widget/test-results/sw-19-desktop-populated.png
new file mode 100644
index 0000000..0ea1111
Binary files /dev/null and b/tests/widgets/streaming-widget/test-results/sw-19-desktop-populated.png differ
diff --git a/tests/widgets/streaming-widget/test-results/sw-20-pool-claimable-amount-error.png b/tests/widgets/streaming-widget/test-results/sw-20-pool-claimable-amount-error.png
new file mode 100644
index 0000000..a2c2fb8
Binary files /dev/null and b/tests/widgets/streaming-widget/test-results/sw-20-pool-claimable-amount-error.png differ
diff --git a/tests/widgets/streaming-widget/test-results/sw-21-pool-claimable-retry-loading.png b/tests/widgets/streaming-widget/test-results/sw-21-pool-claimable-retry-loading.png
new file mode 100644
index 0000000..5088ac4
Binary files /dev/null and b/tests/widgets/streaming-widget/test-results/sw-21-pool-claimable-retry-loading.png differ
diff --git a/tests/widgets/streaming-widget/test-results/sw-22-stream-history-tab.png b/tests/widgets/streaming-widget/test-results/sw-22-stream-history-tab.png
new file mode 100644
index 0000000..c4a40fc
Binary files /dev/null and b/tests/widgets/streaming-widget/test-results/sw-22-stream-history-tab.png differ
diff --git a/tests/widgets/streaming-widget/test-results/sw-23-pool-connected.png b/tests/widgets/streaming-widget/test-results/sw-23-pool-connected.png
new file mode 100644
index 0000000..7ac8124
Binary files /dev/null and b/tests/widgets/streaming-widget/test-results/sw-23-pool-connected.png differ
