Skip to content

[DRAFT][PLAN] AI Credits analytics data widget #178

Description

@goodbounties-nanoclaw-agent

[DRAFT][PLAN] AI Credits analytics data widget

Type: Task
Parent issue: #176

Plan for #176.

Key constraints (hard requirements, not open to reinterpretation)

  • Base branch: all new work branches from feat/analytics-components (PR feat(ui): add analytics chart components (Scorecard, Pie/Donut, Bar, Line/Area, DataTable) #148 — open, head: feat/analytics-componentsbase: main, unmerged), not main. packages/ui's chart components only exist on this branch today.
  • PR target: the new PR opens against feat/analytics-components (i.e. against PR feat(ui): add analytics chart components (Scorecard, Pie/Donut, Bar, Line/Area, DataTable) #148), not main.
  • No wallet connection anywhere in the new app — this is a read-only internal data-viz tool, unlike ai-credits-web/superfluid-campaign-web which both require wallet connect.
  • Data-fetch/transform layer stays an internal module inside the app, not a new package — revisit only if a real reuse case emerges elsewhere.
  • Deploy workflow is drafted but held back — the file is written as part of this work but must not be pushed/merged until Laurence has explicitly confirmed specifics (gate from Lewis, independent of the rest of the plan's approval).
  • Scope boundary (confirmed by Bounty Lead, 2026-08-18): this PR only touches the new widget/app. Updating the two existing deploy workflows' (ai-credits-web, superfluid-campaign-web) job/environment naming is out of scope here — tracked as a separate follow-up, to be raised with Lewis directly. The new workflow follows the "show which app/preview it deploys" convention from the start.

Reference files mapped (across both repos mentioned)

File Role
packages/ui/src/components/Scorecard.tsx (props L27-39) Single-metric display: value, label, prefix?, suffix?, format? ('compact'|'decimal'|'none'), decimals?, trend?, variant? ('bare'|'card'). Used for the 3 hero metrics.
packages/ui/src/components/LineAreaChart.tsx (props L57-82, data item L25-29, series def L31-36) Multi-series line/area chart: data: LineAreaChartDataItem[] ({x, y, series?}), series?: LineAreaChartSeriesDef[] ({key, label, color?}), showArea?, showDots?. Used for Chart 1 (G$ Volume) and Chart 3 (unique wallets).
packages/ui/src/components/BarChart.tsx (props L35-52, data item L23-26) data: BarChartDataItem[] ({category, value}), layout? ('vertical'|'horizontal'), valueFormatter?. Used for Chart 2 (AI Credits/day).
packages/ui/src/components/DataTable.tsx (props L46-61, column def L29-39) Generic data: TRow[] + columns: DataTableColumnDef<TRow>[] ({key, label, type?, align?, formatter?, sortable?}), built-in sort, striped/compact/stickyHeader. No built-in pagination — confirmed no page/pageSize props exist. Used for the daily summary table; pagination must be app-level (see below).
packages/ui/src/utils/formatMetricValue.ts (L77-90) / resolveThemeColor.ts (L18-37) Shared formatting (K/M/B/T compact numbers) and Tamagui-token→raw-color resolution (needed because SVG fill/stroke props don't understand $tokens). Reused as-is, not reimplemented in the app.
packages/ui/src/index.ts (L98-128) Public export surface — confirms all 5 components + types + the two utilities above are already exported from @goodwidget/ui on this branch.
apps/ai-credits-web/{index.html,main.tsx,App.tsx,vite.config.ts,tsconfig.json,vercel.json,package.json,reactNativeSvgWeb.tsx} Primary scaffold template. App.tsx wraps the page in <TamaguiProvider config={defaultConfig} defaultTheme="dark"> (defaultConfig imported from @goodwidget/ui) — copy this wrapper, omit the DefaultAppKitProvider/wallet wiring (App.tsx L88-114, L271-274) entirely. reactNativeSvgWeb.tsx shim is required verbatim (unmodified) for the SVG-based charts to render on web.
apps/superfluid-campaign-web/App.tsx Confirms the scaffold pattern is consistent across a second app and shows the simpler case (no landing page, single content column) — closer in shape to what this analytics app needs than ai-credits-web's multi-section landing page.
.github/workflows/deploy-ai-credits-web.yml, deploy-superfluid-campaign-web.yml Deploy pattern to mirror: workflow_dispatch + push-to-main path filters + pull_request_target with an authorize-preview gate job, then preview/production jobs, each using its own VERCEL_*_PROJECT_ID secret and concurrency group deploy-<app>-${{ github.ref }}. Job names are the generic preview/production in both existing files — the new workflow fixes this for itself (see Execution plan step 6); the existing two files are not touched in this PR (see scope boundary above).
apps/ai-credits-web/tests/, playwright.config.ts App-level Playwright convention: tests live under apps/<app>/tests/, each app has its own playwright.config.ts (dev port, webServer.command: pnpm --filter @goodwidget/<app> dev --host 127.0.0.1, outputDir: ../../test-results/<app>). This (not the tests/widgets/<name>/ convention used for widget packages) is the pattern to follow, since this is a standalone app, not a widget package.
GoodDollar/data-team @ projects/antseed-analytics/dashboard/app.js (475 lines, full file reviewed) The reference implementation to recreate. Exact response shape confirmed: { days, daily: [{date, gdOneTimeDepositsWei, gdStreamedWei, gdTotalFlowRateWeiPerSecond, aiCreditsUsedWei, uniqueGdBuyers, uniqueCreditUsers, updatedAt, missing}], global: {gdOneTimeDepositsWei, gdStreamedWei, aiCreditsUsedWei, gdTotalFlowRateWeiPerSecond, updatedAt}, lastRun: {currentDate, updatedAt} }. Conversion helpers (weiToGd L77-84, weiToUsd L86-91, flowRateToDaily L93-100), API layer (fetchAnalytics/postRefresh L115-125), state machine (loadData/switchSource/updateToggleState L128-206), and table pagination (renderTable/renderPagination/goToPage L398-465) are the exact logic to port to React state/hooks. One thing intentionally not carried over: the live-unavailable copy in switchSource (L174) hardcodes a stale link to antseed-integration PR #19 — this is specific to the old dashboard's history and must be replaced with generic "endpoint not yet deployed" copy in the new app.

Existing @goodwidget packages to import

  • @goodwidget/uiTamaguiProvider's defaultConfig, Scorecard, LineAreaChart, BarChart, DataTable (+ their prop/data types), formatMetricValue, resolveThemeColor, plus basic primitives already used by other apps (Button, ButtonText, Card, Heading, Text, XStack, YStack) for layout, the live/demo toggle, refresh button, and pagination controls.
  • @goodwidget/core — only if any shared formatting/date utilities already exist there that overlap with weiToGd/weiToUsd/flowRateToDaily; otherwise these three conversion functions are analytics-specific and live in the app's own internal module, not @goodwidget/core.
  • No @goodwidget/embed needed (no wallet connection), unlike both existing apps.

New components assessed

No new components needed in packages/ui. All 5 chart components (Scorecard, LineAreaChart, BarChart, DataTable; PieDonutChart is not used per the issue's proposed solution) plus their supporting formatting utilities already exist and cover every visualization this issue requires — confirmed by reading each component's full prop surface. This issue is pure composition, not new generic UI.

New app-local pieces (all inside apps/ai-credits-analytics-web/src/, not packages/ui) — assessed against "does another widget/app plausibly need this generic primitive" and judged no in each case, since all of them are either analytics-specific business logic or thin one-off compositions:

  • lib/analyticsApi.tsfetchAnalytics()/postRefresh() against the worker endpoints, typed to the response shape above.
  • lib/analyticsConversions.tsweiToGd/weiToUsd/flowRateToDaily, ported verbatim from app.js with the same BigInt-precision-preserving approach (divide before Number() conversion).
  • lib/generateDemoData.ts — the demo-data generator, ported from generateMockData (app.js L25-74), generated once and cached (not regenerated on every fallback).
  • hooks/useAnalyticsData.ts — owns liveData/demoData/isDemo/liveAvailable state, the initial-load → try-live → fall-back-to-demo sequence, switchSource, and the 5-minute auto-refresh interval (app.js L128-206, L468-472) — reimplemented as a React hook with useEffect/useState instead of DOM manipulation.
  • components/DataSourceToggle.tsx — small two-button Live/Demo toggle composed from @goodwidget/ui's Button/XStack; a one-off composition, not a new generic control (no evidence any other GoodWidget app needs a live/demo toggle).
  • components/PaginatedDataTable.tsx — thin wrapper around DataTable that owns currentPage state, slices daily into pages of TABLE_PAGE_SIZE = 10, and renders first/prev/next/last controls (mirroring renderPagination/goToPage, app.js L447-465) — needed because packages/ui's DataTable has no built-in pagination. Flagged in the human-reviewer checklist below as a candidate for promotion to packages/ui if another widget needs paginated tables later, but not promoted now (no second consumer yet).

Required states, flows, and behaviors

  • Initial load → attempt GET {WORKER_URL}/v1/analytics?days=365; on success, show live data with the "Live" toggle active.
  • Live fetch fails/unreachable → fall back to cached-once generated demo data, "Demo" toggle active, demo banner/badge shown.
  • Manual toggle to "Live" while the endpoint is unavailable → toggle button stays visible/clickable (never disabled); chart and table sections instead show an inline "endpoint not yet deployed" empty state, matching switchSource's L156-187 behavior but with the stale PR-[Plan] Build packages/savings-widget: SDK integration, UI mapping, and execution checklist #19 reference removed.
  • Manual toggle to "Demo" → always available, shows the cached demo dataset.
  • Refresh button → POST {WORKER_URL}/v1/analytics/refresh, then after a short delay (matching the reference's 2s) re-fetches via loadData; button shows a loading state for the whole duration, matching triggerRefresh (app.js L225-244).
  • Auto-refresh every 5 minutes via the currently active source (live or demo), matching init() (app.js L468-472).
  • All wei-string fields converted via the documented rules — 18-decimal G$ (weiToGd), 6-decimal USD/credits (weiToUsd), flow-rate wei/sec × 86400 then reduced the same way as G$ (flowRateToDaily) — no precision loss (BigInt division before Number() conversion, never a direct Number(BigInt) cast on the full-precision value).
  • 3 scorecards: Total G$ Spent (weiToGd(deposits) + weiToGd(streamed)), AI Credits Used USD (weiToUsd(global.aiCreditsUsedWei)), G$ Flow Rate/day (flowRateToDaily(global.gdTotalFlowRateWeiPerSecond)).
  • Chart 1 (G$ Volume): LineAreaChart, 2 overlaid area series — "One-time Deposits" and "Streamed" — matching the reference's two independently-filled (not mathematically stacked) area series.
  • Chart 2 (AI Credits Used/day, USD): BarChart, single series, one bar per day.
  • Chart 3 (Unique Wallets/day): LineAreaChart, 2 line series ("G$ Buyers", "Credit Users"), no area fill — matches the reference's fill: false for both.
  • Daily summary table: date, G$ deposited, G$ streamed, total G$, AI credits (USD), wallets (G$), wallets (AI); most-recent-first sort; paginated 10 rows/page with first/prev/next/last controls, matching renderTable/renderPagination.
  • Empty state (daily.length === 0, after filtering out missing: true days) shown distinctly from the loading state, in both the charts area and the table.

Execution plan

  1. Branch setup. From this task's clone (already checked out to feat/analytics-components, confirmed at 557ad15, clean, tracking origin/feat/analytics-components), create a new feature branch off it (e.g. feat/ai-credits-analytics-web) — implementation happens there, not directly on feat/analytics-components.
  2. Scaffold apps/ai-credits-analytics-web by copying the apps/ai-credits-web structure (index.html, main.tsx, vite.config.ts, tsconfig.json, vercel.json, reactNativeSvgWeb.tsx shim verbatim, package.json), stripping all @reown/appkit/wallet-related dependencies and code, and writing a new App.tsx that wraps the page in <TamaguiProvider config={defaultConfig} defaultTheme="dark"> with no wallet provider. Assign a free dev port (3001/3002 taken by the two existing apps → use 3003).
  3. Data layer (src/lib/analyticsApi.ts, src/lib/analyticsConversions.ts, src/lib/generateDemoData.ts, src/hooks/useAnalyticsData.ts) — port the reference app.js logic per the "New app-local pieces" section above, as an internal module inside the app.
  4. UI composition — hero row (3 Scorecards), charts section (LineAreaChart ×2, BarChart ×1) driven by the hook's current dataset, DataSourceToggle, refresh button, PaginatedDataTable wrapping DataTable.
  5. Tests — new apps/ai-credits-analytics-web/tests/ + playwright.config.ts mirroring ai-credits-web's, covering: live data loaded, demo fallback, manual toggle to unavailable-live state, refresh loading state, empty state, table pagination (page 1 / middle page / last page).
  6. Deploy workflow (drafted, held back). Write .github/workflows/deploy-ai-credits-analytics-web.yml mirroring the existing two files, but with job names that include the app identifier from the start (e.g. preview-ai-credits-analytics-web / production-ai-credits-analytics-web, or an environment.name of preview / ai-credits-analytics-web) rather than the bare preview/production used by the existing two workflows. This PR only adds this one new file — it does not modify deploy-ai-credits-web.yml or deploy-superfluid-campaign-web.yml (tracked separately, see scope boundary above). Do not push or merge this workflow file until Laurence has explicitly confirmed specifics — everything else in this plan can proceed independently of that sign-off.
  7. PR — opened against feat/analytics-components (PR feat(ui): add analytics chart components (Scorecard, Pie/Donut, Bar, Line/Area, DataTable) #148's branch), not main.

Acceptance criteria

  • apps/ai-credits-analytics-web exists, builds, and deploys as a standalone Vite+React app (once the held-back deploy workflow is separately approved).
  • 3 scorecards render correct values (Total G$ Spent, AI Credits Used USD, G$ Flow Rate/day), computed via the documented wei-conversion rules.
  • Chart 1 (G$ Volume, 2-series area), Chart 2 (AI Credits/day, bar), Chart 3 (unique wallets/day, 2-series line) all render using PR feat(ui): add analytics chart components (Scorecard, Pie/Donut, Bar, Line/Area, DataTable) #148's packages/ui components, with no new chart components added.
  • Daily summary table renders the 7 documented columns with correct pagination (10 rows/page, first/prev/next/last controls).
  • Live/Demo toggle works, including graceful fallback when the live endpoint is unreachable (toggle stays clickable; inline "not yet deployed" state shown instead of a disabled button).
  • Auto-refresh every 5 minutes; manual Refresh button triggers POST /v1/analytics/refresh then re-fetches, with a loading state throughout.
  • BigInt wei-string conversions match the documented decimal rules exactly (no Number(BigInt) precision loss).
  • No wallet-connection UI or dependency anywhere in the app.
  • New deploy workflow file is written and reviewed, and shows which app it deploys in job/environment naming from the start — but confirmed not pushed/merged until Laurence has signed off on specifics.
  • Updating the two existing deploy workflows' job/environment naming is explicitly out of scope for this PR — tracked as a separate follow-up raised with Lewis directly (confirmed by Bounty Lead, 2026-08-18).

Human-reviewer checklist

  • Confirm the "no live-unavailable copy carried over from the old dashboard" call (dropping the hardcoded antseed-integration PR [Plan] Build packages/savings-widget: SDK integration, UI mapping, and execution checklist #19 link) — this wasn't stated in the issue but is clearly stale/one-off content from the reference implementation.
  • Confirm the decision to keep PaginatedDataTable's pagination logic app-local rather than proposing it as a new packages/ui primitive now — no second consumer exists yet, but flag if there's a known near-term need elsewhere that would justify building it there instead.
  • Confirm dev port assignment (3003) doesn't collide with any other in-flight app work not visible in this branch.
  • Verify the held-back deploy workflow file is reviewed for correctness even though it won't be merged yet, so there's no delay once Laurence's sign-off lands.
  • Confirm the existing-workflows follow-up (job/environment naming for ai-credits-web/superfluid-campaign-web) gets tracked somewhere (new issue) so it isn't lost once this PR merges.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Prepare AI Task

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions