Skip to content

feat(apps): AI Credits analytics data widget - #179

Draft
goodbounties-nanoclaw-agent[bot] wants to merge 41 commits into
mainfrom
feat/ai-credits-analytics-web
Draft

feat(apps): AI Credits analytics data widget#179
goodbounties-nanoclaw-agent[bot] wants to merge 41 commits into
mainfrom
feat/ai-credits-analytics-web

Conversation

@goodbounties-nanoclaw-agent

Copy link
Copy Markdown
Contributor

Implements #178 (plan sub-issue for #176 — AI Credits analytics data widget).

Summary

  • Adds a new standalone app apps/ai-credits-analytics-web (Vite + React + TamaguiProvider), following the apps/ai-credits-web / apps/superfluid-campaign-web scaffold pattern. No wallet connection anywhere.
  • Read-only AntSeed AI Credits analytics dashboard: 3 scorecards (G$ spent, AI credits used, G$ flow rate), 3 charts (G$ volume, AI credits used, unique wallets — all via existing @goodwidget/ui chart components: Scorecard, LineAreaChart, BarChart), and a paginated daily-summary table (DataTable + an app-local PaginatedDataTable wrapper, since DataTable has no built-in paging).
  • Data layer (src/lib/analyticsApi.ts, analyticsConversions.ts, generateDemoData.ts, src/hooks/useAnalyticsData.ts) is an internal module inside the app, not a separate package, per plan. Ported from GoodDollar/data-team's reference dashboard (projects/antseed-analytics/dashboard/app.js): live-fetch-with-demo-fallback state machine, manual Live/Demo toggle (stays clickable even when live is unavailable — shows an inline "not yet deployed" message instead of disabling), 5-minute auto-refresh, and a refresh button that POSTs then re-fetches.
  • All wei-denominated values stay as BigInt-safe strings end-to-end; conversions reduce precision in BigInt space before ever calling Number().
  • No new packages/ui components were needed — all 5 analytics chart components already cover this issue's requirements.

Out of scope (per Bounty Lead sign-off on the plan)

  • The existing ai-credits-web/superfluid-campaign-web deploy workflows' generic job/environment naming is not touched here — tracked as a separate follow-up.
  • This app's own deploy workflow (.github/workflows/deploy-ai-credits-analytics-web.yml) is drafted and committed locally but intentionally not included in this PR pending Laurence confirming the Vercel project/secret specifics for this app — will be pushed as a follow-up commit once confirmed.

Testing

  • pnpm turbo run build --filter=@goodwidget/ai-credits-analytics-web — passes.
  • pnpm turbo run lint --filter=@goodwidget/ai-credits-analytics-web — passes.
  • New Playwright spec apps/ai-credits-analytics-web/tests/dashboard.spec.ts (desktop/Chromium project) — 6/6 passing, covering: live data render, demo fallback, toggling to Live while unavailable (inline message, toggle stays clickable), refresh loading state, empty state, and table pagination.

Human-reviewer checklist

  • Confirm the existing-workflow job-naming fix is intentionally excluded from this PR (separate follow-up, per plan sign-off).
  • Confirm dropping the old dashboard's hardcoded antseed-integration PR [Plan] Build packages/savings-widget: SDK integration, UI mapping, and execution checklist #19 link from the "live unavailable" copy (replaced with generic "not yet deployed" text) is correct.
  • Confirm the app-local PaginatedDataTable wrapper (vs. promoting pagination into packages/ui's DataTable) is the right call for now.
  • Confirm dev port 3003 assignment for this app.
  • Review the held-back deploy-ai-credits-analytics-web.yml (committed locally, not in this PR) once Laurence confirms Vercel project/secret specifics, before it's pushed.

goodbounties-nanoclaw-agent Bot and others added 17 commits August 5, 2026 12:47
Adds a reusable Scorecard component to packages/ui — a single-metric KPI
card with label, formatted value, optional trend indicator, and bare/card
variants. Implements #139's compact/decimal/none formatting modes via a
shared formatMetricValue utility, and a golden-ratio modular typography
scale (base 24px, ratio 1.618, 12px floor) across sm/md/lg sizes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- formatMetricValue: fix a rounding-boundary bug where a scaled value could
  round up to the next unit's threshold without promoting (e.g. 999_950 ->
  "1000.0K" instead of "1.0M"); guard non-finite input (NaN/Infinity) with a
  "--" fallback shared by all consumers; note the intentional en-US locale.
- Scorecard: set both testID and data-testid so React Native test tooling
  and web DOM queries both work; mark the trend arrow SVG as decorative
  (accessible=false / aria-hidden) since adjacent text already conveys
  direction; give resolveThemeColor a visible fallback and dev warning
  instead of silently rendering an empty fill; type its theme param via
  ReturnType<typeof useTheme> instead of an unsafe double cast.
- package.json: move react-native-svg to peerDependencies (kept in
  devDependencies for local build/test) to avoid native-module duplication
  for consumers.
- Storybook story + smoke test: render all 5 mock-data rows in both the
  bare and card variants with per-row testIDs, and assert all 10 are
  visible so the test matches what it claims to cover.

Co-Authored-By: Claude <noreply@anthropic.com>
…ing pass

- formatMetricValue: whole numbers below the compact threshold render
  without decimals ("47", not "47.0").
- Scorecard card variant: vertically centers content regardless of
  whether a trend row is present.
- Value text uses $color instead of $primary; prefix/suffix render at
  fontWeight 400 in $placeholderColor, subordinate to the value.
- Card variant elevation now uses a lightness-overlay + top highlight
  instead of a hard border, scoped to Scorecard's <Card> call site so
  the shared Card primitive is untouched.
- Spacing (label-to-value gap, value-to-trend gap, card padding) now
  derives from the same golden-ratio constants as the type scale.
- Bump the Scorecard smoke test's viewport height to fit the taller
  card layout without clipping the screenshot.

Co-Authored-By: Claude <noreply@anthropic.com>
Second consumer (PieDonutChart, and the 3 charts after it) needs the same
theme-token-to-raw-color resolution Scorecard already does privately.
Extracted to packages/ui/src/utils/resolveThemeColor.ts rather than
duplicating a 5th time, mirroring how formatMetricValue was shared.
Scorecard.tsx is untouched — it already shipped under a prior issue and
migrating it is out of this task's scope.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Generalizes FundingDistributionChart's SVG arc technique into a themeable
packages/ui primitive: computed ring geometry supports innerRadius from 0
(filled pie) to a thin donut, sort/maxSlices "Other" aggregation, legend
with percentages, empty/stress states, and onSegmentPress.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Vertical/horizontal categorical bar chart with nice-number axis ticks,
zero-baseline-aware single-edge-rounded bars, graceful long-label
truncation, and sub-pixel-safe rendering for large category counts.
Follows Scorecard/PieDonutChart conventions (createComponent, theme
color resolution, golden-ratio type scale).

Also adds a Text primitive to the Storybook react-native-svg web shim
— needed because BarChart is the first chart to render labels inside
the SVG (for pixel-precise axis alignment) rather than via Tamagui
Text, and the shim never had to support that primitive before.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Time-series line/area chart with linear/monotone/step interpolation,
optional area-fill gradient, reference lines, multi-series with an
optional secondary y-axis, null-gap handling (connectNulls), and
adaptive x-axis label thinning/truncation for dense datasets, mirroring
BarChart's established truncateLabelToWidth pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Shared CHART_FONT_FAMILY constant applied to every SVG <Text> in
  BarChart and LineAreaChart, matching the default preset's sans-serif
  typography stack instead of the browser's default serif fallback
  (react-native-svg's Text isn't part of Tamagui's styling system, same
  reason resolveThemeColor exists for fill/stroke).
- PieDonutChart's Default story now shows the spec default (innerRadius
  0.6, donut with center metric) instead of overriding it to a flat
  pie; the pie mode moves to its own PurePie story.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
centerContentMaxWidth was derived from ringRadius * (1 - innerRadius),
which shrinks as the hole grows instead of growing with it — ringRadius
is the stroke's centerline, not the hole radius. Compute the actual hole
radius (ringRadius - strokeWidth / 2) and size the available square off
that instead. Also default the center value's own formatting to 0
decimals (450K, not 450.0K) so typical aggregated totals fit the hole at
the component's default size.

Co-Authored-By: Claude <noreply@anthropic.com>
Pure-Tamagui data table with typed columns, sorting, striping, sticky
header, and horizontal/vertical scroll. Applies the design-hierarchy
rules from the Pie/Bar/Line fix round: muted $placeholderColor headers
(bold weight kept per spec's own rule 4) vs full-contrast $color data
values.

Co-Authored-By: Claude <noreply@anthropic.com>
formatMetricValue's compact formatter always applied toFixed(decimals) to
the scaled value, even when it landed on a whole unit (892000 -> "892.0K"
instead of "892K"). Mirrors the same whole-number check already used in the
below-threshold branch. Affects BarChart/LineAreaChart axis labels and
DataTable cells.

Also backfills missing Playwright screenshot coverage requested in QA:
Pie/Donut empty + stress, Bar empty, Line/Area step/multi-series/gap/empty/
single-point, DataTable empty/null-cells/card variant.

Co-Authored-By: Claude <noreply@anthropic.com>
…pped elements

Several new QA-round screenshots (Pie/Donut, Bar, Line/Area empty states,
Line/Area step/single-point, DataTable empty/nulls) used element-level
locator screenshots instead of the established screenshotStory() full-iframe
helper, producing tightly-cropped composites inconsistent with the rest of
the suite.

DataTable/Default now uses a taller viewport (1800px) so the card variant,
previously captured as a separate cropped shot, renders within the same
full-page capture as the bare table.

Co-Authored-By: Claude <noreply@anthropic.com>
examples/react-web's local web shim for react-native-svg never got the
Text/Defs/LinearGradient/Stop passthroughs that examples/storybook's copy
already has. BarChart and LineAreaChart import these directly from
react-native-svg, which resolves to this shim in the web build, so
example-react-web#build failed at "Text" is not exported by
reactNativeSvg.tsx — and since all 3 PR #148 Vercel preview checks share
this turbo build task, all 3 were failing on it.

Mirrors the existing storybook shim's implementation exactly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
examples/html/src/shims/reactNativeSvg.tsx had the identical gap already
fixed in examples/react-web (missing Text/Defs/LinearGradient/Stop),
which was the next failure turbo hit on the shared example-html#build
task once the react-web build was unblocked.

apps/superfluid-campaign-web and apps/ai-credits-web each keep their own
differently-structured shim (src/reactNativeSvgWeb.tsx, aliased directly
in vite.config.ts rather than living under a shims/ folder) with a wider
pre-existing gap: also missing G, needed since PieDonutChart. Added
G/Text/Defs/LinearGradient/Stop to both, following each file's own
per-element-typed function style rather than the shims/ copies'
createPassthroughSvgPrimitive factory.

Full `pnpm build` across the monorepo now succeeds.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nt stories

Add an explicit-render Controllable story to each analytics component
(BarChart, PieDonutChart, LineAreaChart, DataTable, Scorecard), leaving
Default fixed/static with a comment noting Controls are inert there by
design, matching the Stepper/Card convention.

Root cause of the reported inertness: any argType-covered prop absent
from a story's args object renders as a non-functional "Set boolean/
string/number" placeholder button in Storybook's live Controls panel
instead of a working control, until clicked once to initialize a
default. Every argType-covered prop across all 5 components now has an
explicit default in Controllable's args, matched to each component's
own real internal default value, so every control renders live and
interactive on first load.

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

On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
New standalone read-only Vite/React app (@goodwidget/ai-credits-analytics-web)
visualizing AntSeed AI Credits activity: G$ volume, credit usage, and unique
wallet counts, sourced from the AntSeed Analytics Worker with a local demo
fallback. Ported from GoodDollar/data-team's reference dashboard, composed
entirely from existing @goodwidget/ui chart primitives (Scorecard,
LineAreaChart, BarChart, DataTable). No wallet connection.

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

On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
…s-web

Vercel token/project-id secret naming confirmed by Laurence; job/environment
naming scoped to this new workflow only per the same confirmation.

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

On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
Copilot AI lite review requested due to automatic review settings August 21, 2026 14:13
@L03TJ3
L03TJ3 deployed to internal August 21, 2026 14:13 — with GitHub Actions Active
@L03TJ3
L03TJ3 deployed to internal August 21, 2026 14:13 — with GitHub Actions Active
@L03TJ3
L03TJ3 deployed to internal August 21, 2026 14:13 — with GitHub Actions Active
@L03TJ3
L03TJ3 deployed to internal August 24, 2026 06:29 — with GitHub Actions Active
@L03TJ3
L03TJ3 deployed to internal August 24, 2026 06:29 — with GitHub Actions Active
@L03TJ3
L03TJ3 deployed to internal August 24, 2026 06:29 — with GitHub Actions Active
@L03TJ3
L03TJ3 deployed to internal August 24, 2026 06:29 — with GitHub Actions Active
goodbounties-nanoclaw-agent Bot added a commit that referenced this pull request Aug 24, 2026
Implements Thales's 7 blocking QA fixes on PR #179:
- Hover tooltips on LineAreaChart, BarChart, PieDonutChart (shared packages/ui
  chart components), via new ChartTooltip primitive
- Y-axis floor at 0 when all series data is non-negative
- Adaptive x-axis date-label thinning, always keeping first/last tick
- Chart titles rendered as H5
- Rotated Y-axis title labels via new yAxisLabel prop
- Right-aligned numeric columns in the Daily Summary table
- Loading skeleton (new SkeletonBlock primitive) shown within 1s instead of a
  blank wait during the live fetch

Regenerated baseline screenshots for tests/design-system/smoke.spec.ts and
tests/widgets/gooddata-widget/states.spec.ts to reflect the updated chart/table
rendering; both suites pass in full, confirming no regressions in the other
consumers of the shared packages/ui chart components.

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

On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
Implements Thales's 7 blocking QA fixes on PR #179:
- Hover tooltips on LineAreaChart, BarChart, PieDonutChart (shared packages/ui
  chart components), via new ChartTooltip primitive
- Y-axis floor at 0 when all series data is non-negative
- Adaptive x-axis date-label thinning, always keeping first/last tick
- Chart titles rendered as H5
- Rotated Y-axis title labels via new yAxisLabel prop
- Right-aligned numeric columns in the Daily Summary table
- Loading skeleton (new SkeletonBlock primitive) shown within 1s instead of a
  blank wait during the live fetch

Regenerated baseline screenshots for tests/design-system/smoke.spec.ts and
tests/widgets/gooddata-widget/states.spec.ts to reflect the updated chart/table
rendering; both suites pass in full, confirming no regressions in the other
consumers of the shared packages/ui chart components.

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

On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
…cimal precision

- ChartTooltip splits label/value into separate Text nodes so the numeric
  value never truncates; the series name ellipsizes first if space is tight.
- Tooltip width is now derived from actual header/row content
  (estimateChartTooltipWidthPx), replacing the old fixed 200px constant.
- New formatChartAxisValue wrapper (trimIntegers: false) is the default
  formatter for LineAreaChart/BarChart axis ticks and tooltips, so a chart's
  hover values always match its own axis-tick decimal precision instead of
  formatMetricValue's per-value integer-trimming quirk.
- Extracted the duplicated text-width-estimate/truncate helpers (previously
  copy-pasted in LineAreaChart and BarChart) into a shared utils module now
  that ChartTooltip needs the same logic too.

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

On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
…ement

Replaces the old "every Nth label" adaptive-skip approach in
LineAreaChart and BarChart (vertical layout) with a shared
computeXAxisLabelPlan() utility that:
- always renders the first/last category, anchored so its label can't
  overhang past the plot edge
- sizes minimum label spacing off the widest *actually rendered*
  formatted label (dates, numbers, or arbitrary strings) at its real
  font size, not a fixed per-dashboard guess
- distributes intermediate labels at equal pixel intervals, snapped to
  the nearest real category index with a deterministic floor-tiebreak
  on exact ties
- runs an explicit post-snap collision check (dropping crowded
  intermediates, never an anchor) and a boundary check against the
  reserved Y-axis label gutter

The old skip factor could still leave the last category unlabeled or
let two labels touch once real (non-average) label widths landed on
unevenly spaced snapped points — this makes both guarantees explicit
and verifiable instead of incidental.

BarChart's horizontal layout is untouched: its category axis stacks
labels top-to-bottom in a fixed-width column, a height-crowding problem
unrelated to this x-axis fix, so it keeps computeLabelSkipFactor.

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

On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
…label-width floors

Fix B follow-up, prompted by review questions on d9a5cf7:

1. Removed the two fixed-56px fallback constants
   (X_LABEL_APPROX_WIDTH_PX in LineAreaChart, CATEGORY_LABEL_APPROX_SIZE_PX
   in BarChart). They were not inert defaults — Math.max against the real
   measured widest-label width made them a live override any time every
   real label was narrower than 56px, silently packing short-label charts
   more sparsely than their actual content needed. Violates the
   no-hardcoded-values rule; the widest-real-label width alone is now the
   floor (0 initial value).

2. While visually re-verifying (1) against a short-label edge case,
   direct SVG getBBox() measurement caught a real, pre-existing collision
   in computeXAxisLabelPlan's step-8 check: the flat
   `labelWidthPx + minGapPx` threshold assumes every adjacent label pair
   is middle-anchored, but the first/last anchors are start/end-anchored
   and extend their full label width toward the interior instead of half.
   Adjacent to those anchors the flat check under-counts the required gap
   by labelWidthPx / 2 — confirmed via measurement as an actual ~0.6px
   (visually merged) overlap in a 20-short-category case.

   Added rightwardReachPx/leftwardReachPx/requiredGapPx, which compute
   each pair's true required gap from each entry's own textAnchor, and
   used them in place of the flat threshold in both the forward
   collision-check loop and the trailing anchor-boundary pop loop. The
   old flat formula still seeds the approximate maxTotalLabels capacity
   estimate (upper bound only); correctness now comes from requiredGapPx.

Re-verified: rebuilt, linted (0 errors), re-measured the short-label
edge case (previously-overlapping labels now correctly dropped, all
gaps healthy), re-ran the four original stress-test scenarios
(LineAreaChart 100/1095 points, BarChart 100/1000 categories) with the
corrected math, and ran the full 45-test smoke suite — all pass.

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

On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
…ter collision, rename Wallets label

Y-axis titles (LineAreaChart/BarChart) now render at the same Caption
tier as tick labels instead of the roomier x-axis-title tier, since
they share the same constrained left gutter.

That surfaced a pre-existing bug: the left padding gutter was a fixed
48px regardless of how wide the actual formatted tick labels were, so
a wide tick value (e.g. "50.0K") could reach far enough left to
collide with/garble the rotated y-axis title. Fixed by computing the
required left gutter from the real widest formatted tick label (plus
the title's own footprint when set) and widening the padding to fit,
mirroring the existing dynamic-width approach already used for x-axis
labels. Explicit per-instance padding overrides still take precedence.

Also renames the Unique Wallets chart's yAxisLabel from "Count" to
"Wallets" per QA.

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


On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
…non-bold weight

QA3 Fix D. LineAreaChart's LEGEND_LABEL_BASE_SIZE_PX and PieDonutChart's
LEGEND_LABEL_SIZE_PX were pinned to the axis-title tier
(CHART_BASE_SIZE_PX / GOLDEN_RATIO). For LineAreaChart, which scales its
non-title text with the container up to MAX_SCALE_RATIO (2x) while the
chart title stays fixed, this let the legend render larger than the
chart's own title on wide containers (measured 29.67px legend vs 24px
title) - the "oversized" legend QA flagged.

Move both to the tick-label/Caption tier
(CHART_BASE_SIZE_PX / GOLDEN_RATIO ** 2), which stays below the fixed
title size across the full MIN_SCALE_RATIO-MAX_SCALE_RATIO range
(0.6-2), not just this dashboard's specific container widths. Also add
an explicit fontWeight: '400' on both legend label components so
"not bold" is a pinned invariant rather than an inherited default.

BarChart has no legend/multi-series concept, so it's unaffected.

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


On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
…se DataTable's shared center default

TABLE_COLUMNS in AiCreditsDashboard.tsx explicitly set align: 'left' on
the date column and align: 'right' on every numeric/currency column,
overriding DataTable's own default (column.align ?? 'center' in both
HeaderCell and DataCell). Removed the per-column overrides so all 7
columns fall through to the shared component default instead of this
dashboard re-deriving its own left/right-by-type rule.

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


On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
…f.description

DataTable columns can now set a description string that shows as a
hover/focus tooltip on the header, and every Daily Summary column in
the AI Credits dashboard now documents what it measures.

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

On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
…inspection

Adds a valueType field ('integer' | 'currency' | 'decimal' | 'auto') to
LineAreaChart/BarChart so axis ticks and tooltips share one formatter
instead of independently guessing decimal precision from the value
itself. Wires the AI Credits dashboard: G$ Volume -> decimal,
AI Credits Used -> currency, Unique Wallets -> integer.

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

On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
…nd values

'decimal' valueType (rates, percentages, G$ volume) must render round
values without padded zeros ("20K" not "20.00K") while still showing
up to its precision when a value isn't round ("20.50K"); 'currency'
and 'integer' stay fixed-precision regardless of roundness since
those must never drop a real decimal (a currency value landing on a
whole dollar amount still needs "5.00"). Trimming is keyed off the
declared valueType, not runtime inspection of the value itself.

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

On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
…xed 280px

Tooltip width now derives from the hosting chart's own real pixel
width (viewBoxWidth for LineAreaChart/BarChart, geometry.size for
PieDonutChart) instead of a hardcoded TOOLTIP_MAX_WIDTH_PX constant,
so a label only truncates once it would genuinely exceed 80% of the
chart's width rather than an arbitrary fixed cap that could truncate
short labels on wide charts or fit long ones on narrow charts.

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

On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
…-sizing

The prior fix (d41d7c2) added the 80%-cap mechanism but the tooltip
still truncated "Credit Users" because two things underestimated the
row's real rendered width:

- estimateTextWidthPx used a character-count heuristic instead of
  measuring actual glyph widths, so estimates diverged from what the
  browser renders. Now measures via canvas.measureText() on web, with
  the heuristic kept only as a React Native fallback (no canvas/DOM).
- estimateRowContentWidthPx only accounted for one inter-element gap,
  though the row's XStack applies gap="$2" twice (dot->label and
  label->value).
- The tooltip frame's 1px border (border-box sizing) was never
  subtracted from the content-area math, costing 2px the row needed.

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

On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
… fix header/data column misalignment

resolveColumnFlexStyle's no-explicit-width branch only set flex:1, leaving
flexBasis at its CSS default of "auto" (content-based sizing). Since the
header row and each data row are separate, independent flex containers,
each one distributed width based on its own children's content — a wide
header label vs a short data value in the same column produced different
per-row flex-basis inputs, landing same-index columns at different pixel
boundaries between header and data rows. Pinning flexBasis to 0 makes every
row split width purely by the flex ratio, independent of content, so
same-index columns land on identical boundaries across all rows.

Updates the 10 story-datatable-* Storybook baselines (evenly-redistributed
column widths) and the 3 gooddata-widget baselines affected by the same
Daily Summary table.

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



On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
…ap with chart width

CHART_TO_LEGEND_GAP_BASE_PX was routed through scalePx() using the same
golden-ratio tier as font-driven sizes, so on wide/responsive dashboard
charts (scaleRatio approaching the 2x cap) the gap ballooned to ~30px,
reading as excessive whitespace between the chart and its legend.

Fixes it the same way titleSizePx and PieDonutChart's own legend gap are
already handled in this codebase: as a flat, unscaled structural gap
(CHART_TO_LEGEND_GAP_PX = 10) instead of a font-legibility size that needs
to track chart width. Scoped to LineAreaChart only — BarChart has no legend.

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

On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
…xis for G$ Volume chart

Wires the AI Credits dashboard's "Streamed" series to LineAreaChart's
secondaryYAxis prop so it gets its own scale instead of being flattened
against the much larger "One-time Deposits" series. Also fixes
secondaryYAxis.label, which previously had no effect (dead prop, never
rendered), by adding the missing axis title, and color-codes each axis
to match its series once a secondary axis is active — generically, only
when the primary axis still represents exactly one series, to avoid a
misleading color choice when multiple series still share it.

Also corrects the QA fixture's streamed/deposits values, which were an
exact 1/5 ratio on every day — coincidentally producing identical
normalized curves on independent axes and hiding one line behind the
other, masking the fix in the dashboard screenshot.

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



On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
… fixed 200px

LineAreaChart and BarChart now measure their PlotArea's resolved layout
height via useMeasuredHeight and use it for the SVG height/viewBox, so
charts fill whatever vertical space their container offers instead of
rendering at a fixed 200px. Bumped the pre-layout fallback height to 280px
so charts render taller even without a flex ancestor providing extra room.

Rotated y-axis titles now wrap to a second line at a word boundary
(wrapLabelToTwoLines) before falling back to ellipsis truncation, fixing
the clipped "One-time Deposits (G$)" title on the G$ Volume dual-axis
chart now that the chart is taller.

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

On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
…ooltip/label clipping

estimateTextWidthPx() used canvas measureText() (and a character-count
fallback) with no rounding, so its estimate could land a fraction of a
pixel narrower than the real DOM-rendered text width for the same
string/font. Every caller (ChartTooltip sizing/truncation, axis-title
wrapping, legend truncation) treats "estimate <= available width" as
"fits without truncating" — that sub-pixel shortfall was enough for CSS
text-overflow:ellipsis to clip text the JS logic believed would render
in full, e.g. BarChart's tooltip row "Claims by Chain : 45.2K" getting
cut to "Claims by Cha…" even though LineAreaChart's tooltip (shorter
per-series labels) rarely landed on the same precision boundary.

Math.ceil() on both estimation branches guarantees the estimate is
never smaller than the true render, at the cost of at most ~1px of
imperceptible extra reserved space.

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

On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
…-item gap slightly

Both the legend label font size and the gap between legend items read a
touch large on wide embeds (near MAX_SCALE_RATIO). Introduced
LEGEND_SIZE_SCALE_FACTOR (0.9) and applied it to both
LEGEND_LABEL_BASE_SIZE_PX and the legend's inter-item gap (replacing the
flat, non-responsive "$4" Tamagui spacing token with a computed
LEGEND_ITEM_GAP_PX derived from the same factor), so the reduction stays
proportional to the label size rather than a separately hardcoded value.
Scoped to LineAreaLegend only — BarChart has no legend UI.

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

On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants