Overview
Every dashboard StatCard that shows a "vs last period" trend indicator is passing a hardcoded literal number, not a value derived from any real data:
src/app/dashboard/contributor/page.tsx:
<StatCard
label="Lifetime earnings"
value={stats?.lifetimeEarnings}
format="currency"
status={fetchStatus}
icon={DollarSign}
trend={fetchStatus === "loaded" ? 12 : undefined}
sparkline={fetchStatus === "loaded" ? contributorEarningsHistory : undefined}
zeroLabel="No earnings yet"
/>
<StatCard
label="Merged PRs"
value={stats?.mergedPRs}
format="count"
status={fetchStatus}
icon={GitMerge}
trend={fetchStatus === "loaded" ? 8 : undefined}
sparkline={fetchStatus === "loaded" ? contributorSparkline : undefined}
zeroLabel="No merged PRs yet"
/>
src/app/dashboard/sponsor/page.tsx:
<StatCard
label="Total paid out"
value={totalSpent}
format="currency"
status={fetchStatus}
icon={Receipt}
trend={fetchStatus === "loaded" ? 18 : undefined}
sparkline={fetchStatus === "loaded" ? sponsorSpendHistory : undefined}
/>
12, 8, and 18 are literal numeric constants — the only thing determining whether trend is shown at all is fetchStatus === "loaded"; the literal value itself never varies with stats, totalSpent, or anything else fetched. This is trivially demonstrable: the exact same contributorEarningsHistory/sponsorSpendHistory arrays these StatCards already receive as their sparkline prop (8 weeks of real, varying numbers — [420, 560, 310, 780, 640, 890, 720, 1050] for contributor earnings) sit right there, unused for the trend calculation, which a real implementation would derive from (e.g. comparing the most recent value against the prior period).
The consequence: StatCard's trend indicator — a green/rose arrow plus "X% vs last period," visually presented as real, computed analytics — is fabricated for every dashboard, in every state, including a fully-authenticated user viewing their own real, live data under a "Live data" badge (fetchStatus === "loaded" is true for both the live-fetch-succeeded case and the signed-out demo-data case, so the same hardcoded 12/8/18 shows regardless of which). A contributor whose actual earnings dropped week-over-week still sees a green up-arrow claiming "+12% vs last period," because the number was never computed from their data in the first place — it's a static prop value that happens to always render as if things are trending up (all three hardcoded values are positive, so trendUp = trend >= 0 is always true for every dashboard, every user, always).
Requirements
- Replace each hardcoded
trend literal with a real computation derived from the same historical data already available at each call site (e.g. contributorEarningsHistory, sponsorSpendHistory) — a straightforward last-period-vs-prior-period percentage change, or whatever comparison the design intends (week-over-week, this-period-vs-last-N-periods-average, etc. — pick and document one).
- For the values sourced from a live backend fetch (once the separate "dashboard widgets permanently hardcoded to mock data" issue in this batch is resolved and real history data is available), compute the trend from that live history rather than the static mock arrays — this issue's fix should not depend on that other issue landing first, but should be structured so the trend computation naturally works with either data source once both are addressed.
- Ensure the computed trend can be negative — verify
trendUp = typeof trend === "number" && trend >= 0 in StatCard.tsx correctly renders the down/rose-colored arrow for a genuinely negative computed trend, since this path has likely never been exercised given every current caller always passes a positive literal.
- Audit for any other hardcoded-looking numeric prop passed to a "real-looking" UI element across the dashboards (this fix should not stop at just these three
trend props if a similar hardcoded-but-presented-as-computed value is found elsewhere during the audit — document the finding either way).
Acceptance Criteria
Additional Notes
Precise references:
src/app/dashboard/contributor/page.tsx:141,151 — the two hardcoded trend literals (12, 8).
src/app/dashboard/sponsor/page.tsx:141 — the third hardcoded trend literal (18).
src/lib/mock-data.ts:255-256 (contributorEarningsHistory, sponsorSpendHistory) and :258 (contributorSparkline) — the exact historical arrays already passed to these same StatCards as sparkline, sitting right next to the hardcoded trend prop, unused for it.
src/components/ui/StatCard.tsx:137,248-264 — trendUp = typeof trend === "number" && trend >= 0 and the rendering branch; confirmed the down/rose-arrow code path exists and is presumably correct, just never exercised by any current caller since every literal passed today is positive.
Edge cases: deciding the comparison window matters — "last 8 weeks" sparkline data compared how (last week vs. the week before, or last week vs. an 8-week average) will produce meaningfully different numbers and should be a deliberate choice, not an implementation detail buried without comment. Also worth deciding whether a trend of exactly 0% should show as "up" (current trendUp = trend >= 0 treats zero as up) or as a distinct neutral state — this is a pre-existing minor ambiguity in StatCard itself, not newly introduced by this fix, but worth a one-line acknowledgment in the PR since a real, computed trend is now far more likely to actually land on exactly zero than a hand-picked positive literal ever was.
Test/reproduction plan: unit-test a new computeTrend(history: number[]): number | undefined-style helper (or equivalent inline logic, tested at the page level) with fixtures: a history array trending up (assert positive result), trending down (assert negative result), flat (assert 0 or undefined per the documented decision), and too short to compare (assert undefined). Then assert the dashboard pages pass this computed value into StatCard's trend prop instead of a literal, and that StatCard itself correctly renders the down-arrow/rose styling for a negative fixture (extending StatCard.test.tsx's existing trend-rendering test, which today only exercises a positive trend={12}).
Overview
Every dashboard
StatCardthat shows a "vs last period" trend indicator is passing a hardcoded literal number, not a value derived from any real data:src/app/dashboard/contributor/page.tsx:src/app/dashboard/sponsor/page.tsx:12,8, and18are literal numeric constants — the only thing determining whethertrendis shown at all isfetchStatus === "loaded"; the literal value itself never varies withstats,totalSpent, or anything else fetched. This is trivially demonstrable: the exact samecontributorEarningsHistory/sponsorSpendHistoryarrays theseStatCards already receive as theirsparklineprop (8 weeks of real, varying numbers —[420, 560, 310, 780, 640, 890, 720, 1050]for contributor earnings) sit right there, unused for the trend calculation, which a real implementation would derive from (e.g. comparing the most recent value against the prior period).The consequence:
StatCard's trend indicator — a green/rose arrow plus "X% vs last period," visually presented as real, computed analytics — is fabricated for every dashboard, in every state, including a fully-authenticated user viewing their own real, live data under a "Live data" badge (fetchStatus === "loaded"istruefor both the live-fetch-succeeded case and the signed-out demo-data case, so the same hardcoded12/8/18shows regardless of which). A contributor whose actual earnings dropped week-over-week still sees a green up-arrow claiming "+12% vs last period," because the number was never computed from their data in the first place — it's a static prop value that happens to always render as if things are trending up (all three hardcoded values are positive, sotrendUp = trend >= 0is alwaystruefor every dashboard, every user, always).Requirements
trendliteral with a real computation derived from the same historical data already available at each call site (e.g.contributorEarningsHistory,sponsorSpendHistory) — a straightforward last-period-vs-prior-period percentage change, or whatever comparison the design intends (week-over-week, this-period-vs-last-N-periods-average, etc. — pick and document one).trendUp = typeof trend === "number" && trend >= 0inStatCard.tsxcorrectly renders the down/rose-colored arrow for a genuinely negative computed trend, since this path has likely never been exercised given every current caller always passes a positive literal.trendprops if a similar hardcoded-but-presented-as-computed value is found elsewhere during the audit — document the finding either way).Acceptance Criteria
trendon every dashboardStatCardthat shows one is computed from real underlying data, not a literal constant.trendshould beundefinedin that case, not a fabricated placeholder.Additional Notes
Precise references:
src/app/dashboard/contributor/page.tsx:141,151— the two hardcodedtrendliterals (12,8).src/app/dashboard/sponsor/page.tsx:141— the third hardcodedtrendliteral (18).src/lib/mock-data.ts:255-256(contributorEarningsHistory,sponsorSpendHistory) and:258(contributorSparkline) — the exact historical arrays already passed to these sameStatCards assparkline, sitting right next to the hardcodedtrendprop, unused for it.src/components/ui/StatCard.tsx:137,248-264—trendUp = typeof trend === "number" && trend >= 0and the rendering branch; confirmed the down/rose-arrow code path exists and is presumably correct, just never exercised by any current caller since every literal passed today is positive.Edge cases: deciding the comparison window matters — "last 8 weeks" sparkline data compared how (last week vs. the week before, or last week vs. an 8-week average) will produce meaningfully different numbers and should be a deliberate choice, not an implementation detail buried without comment. Also worth deciding whether a
trendof exactly0%should show as "up" (currenttrendUp = trend >= 0treats zero as up) or as a distinct neutral state — this is a pre-existing minor ambiguity inStatCarditself, not newly introduced by this fix, but worth a one-line acknowledgment in the PR since a real, computed trend is now far more likely to actually land on exactly zero than a hand-picked positive literal ever was.Test/reproduction plan: unit-test a new
computeTrend(history: number[]): number | undefined-style helper (or equivalent inline logic, tested at the page level) with fixtures: a history array trending up (assert positive result), trending down (assert negative result), flat (assert0orundefinedper the documented decision), and too short to compare (assertundefined). Then assert the dashboard pages pass this computed value intoStatCard'strendprop instead of a literal, and thatStatCarditself correctly renders the down-arrow/rose styling for a negative fixture (extendingStatCard.test.tsx's existing trend-rendering test, which today only exercises a positivetrend={12}).