Files
SimpleFinanceDash/.planning/milestones/v1.0-phases/02-dashboard-charts-and-layout/02-RESEARCH.md

28 KiB

Phase 2: Dashboard Charts and Layout - Research

Researched: 2026-03-16 Domain: Recharts 2.x chart components (Donut/PieChart, BarChart, Horizontal BarChart), shadcn/ui ChartContainer integration, React Router URL state management Confidence: HIGH

Summary

Phase 2 replaces the existing flat pie chart and progress bars on the dashboard with three rich chart components -- an expense donut chart with center label and active sector, a grouped vertical bar chart for income budgeted vs actual, and a horizontal bar chart for category-type budget vs actual spending. All charts must consume CSS variable tokens from the established OKLCH palette (no hardcoded hex values) and handle empty states gracefully. Month navigation via URL search params enables shareable links and browser history navigation.

The project uses Recharts 2.15.4 (not 3.x as the roadmap loosely referenced). This is important because Recharts 2.x has working <Label> support inside <Pie> for center text, and the established chart.tsx from shadcn/ui with the initialDimension patch is already configured. The ChartContainer + ChartConfig pattern from shadcn/ui provides the theme-aware wrapper -- all chart colors flow through ChartConfig entries referencing CSS variables, which ChartStyle injects as scoped --color-{key} custom properties.

Primary recommendation: Build each chart as an isolated presentational component in src/components/dashboard/charts/, wire them into a refactored DashboardContent with a 3-column responsive grid, and manage month selection state with useSearchParams from react-router-dom.

<user_constraints>

User Constraints (from CONTEXT.md)

Locked Decisions

  • 3-column grid on desktop -- donut, vertical bar, and horizontal bar charts side by side in a single row
  • Each chart wrapped in its own Card component (consistent with StatCard pattern)
  • Visual order: SummaryStrip first, chart row second, QuickAdd button moved below charts
  • Month navigation: Arrow buttons for prev/next month plus a clickable month label that opens a dropdown for direct jump to any available month
  • Month stored in URL search params (e.g. /dashboard?month=2026-02) -- enables sharing links and browser back button
  • When navigating to a month with no budget: show the month page with an empty prompt ("No budget for this month") and a create/generate option
  • Dropdown lists all months that have budgets; arrow buttons allow navigating beyond existing budgets (showing empty prompt)
  • When a chart has no data: show a muted placeholder (greyed-out chart outline with text overlay) inside the chart card
  • Donut chart with zero amounts (budget exists, nothing spent): show empty ring in neutral/muted color with $0 center label
  • When a brand new budget has no items at all: show individual placeholders per chart card independently (no combined empty state)
  • Center label shows total expense amount only (formatted currency, no label text)
  • Active sector expands on hover
  • Uses existing category color CSS variables from palette.ts
  • Vertical bar chart (income): budgeted bars in muted/lighter shade, actual bars in vivid category color -- emphasizes actuals
  • Horizontal bar chart (category type spend): budgeted bars muted, actual bars vivid
  • Over-budget indicator: actual bar extends past budgeted mark with red accent (over-budget semantic color) -- visually flags overspending
  • All bars consume CSS variable tokens (no hardcoded hex values)

Claude's Discretion

  • Responsive breakpoints for chart row collapse (3-col to stacked)
  • Donut legend placement (below vs right side of chart)
  • Chart tooltip content and formatting
  • Exact spacing and typography within chart cards
  • DashboardSkeleton updates for new layout
  • Data memoization strategy (useMemo vs derived state)
  • Month navigation placement (PageShell CTA slot vs own row)

Deferred Ideas (OUT OF SCOPE)

None -- discussion stayed within phase scope </user_constraints>

<phase_requirements>

Phase Requirements

ID Description Research Support
UI-DASH-01 Redesign dashboard with hybrid layout -- summary cards, charts, and collapsible category sections with budget/actual columns This phase delivers the charts layer: 3-column chart grid below SummaryStrip, month navigation, responsive layout. Collapsible sections are Phase 3.
UI-BAR-01 Add bar chart comparing income budget vs actual Vertical BarChart with grouped bars (budgeted muted, actual vivid), using ChartContainer + ChartConfig pattern
UI-HBAR-01 Add horizontal bar chart comparing spend budget vs actual by category type Horizontal BarChart via layout="vertical" on <BarChart>, swapped axis types, over-budget red accent via Cell conditional fill
UI-DONUT-01 Improve donut chart for expense category breakdown with richer styling PieChart with innerRadius/outerRadius, activeShape for hover expansion, center Label for total, custom legend, category fill colors from CSS variables
</phase_requirements>

Standard Stack

Core

Library Version Purpose Why Standard
recharts 2.15.4 All chart rendering (PieChart, BarChart) Already installed, shadcn/ui chart.tsx built on it
react-router-dom 7.13.1 useSearchParams for month URL state Already installed, provides shareable URL state
react 19.2.4 useMemo for data derivation memoization Already installed

Supporting

Library Version Purpose When to Use
@/components/ui/chart shadcn ChartContainer, ChartConfig, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent Wrap every chart for theme-aware color injection
@/lib/palette project categoryColors map (CSS variable references) Feed into ChartConfig color entries
@/lib/format project formatCurrency for tooltip/label values All monetary displays in charts
lucide-react 0.577.0 ChevronLeft, ChevronRight icons for month nav Month navigation arrows

Alternatives Considered

Instead of Could Use Tradeoff
Recharts 2.x Recharts 3.x v3 has broken <Label> in PieChart (issue #5985); stay on 2.15.4
useSearchParams useState Would lose URL shareability and browser back/forward; user explicitly chose URL params
Custom tooltip ChartTooltipContent from shadcn shadcn's built-in tooltip handles config labels/colors automatically; prefer it

Installation:

# No new packages needed -- all dependencies already installed

Architecture Patterns

src/
  components/
    dashboard/
      charts/
        ExpenseDonutChart.tsx    # Donut pie chart with center label
        IncomeBarChart.tsx       # Vertical grouped bar (budgeted vs actual)
        SpendBarChart.tsx        # Horizontal bar (budget vs actual by category)
        ChartEmptyState.tsx      # Shared muted placeholder for no-data charts
      MonthNavigator.tsx         # Prev/Next arrows + month dropdown
      SummaryStrip.tsx           # (existing)
      StatCard.tsx               # (existing)
      DashboardSkeleton.tsx      # (existing -- needs update for 3-col chart row)
  hooks/
    useMonthParam.ts             # Custom hook wrapping useSearchParams for month state
  pages/
    DashboardPage.tsx            # Refactored: month param -> budget lookup -> DashboardContent
  lib/
    palette.ts                   # (existing -- categoryColors, add chartFillColors)
    format.ts                    # (existing -- formatCurrency)

Pattern 1: ChartContainer + ChartConfig Color Injection

What: shadcn's ChartContainer reads a ChartConfig object and injects scoped --color-{key} CSS custom properties via a <style> tag. Chart components then reference these as fill="var(--color-{key})". When to use: Every chart in this phase. Example:

// Source: shadcn/ui chart docs + project's chart.tsx
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart"
import type { ChartConfig } from "@/components/ui/chart"

const chartConfig = {
  bill: {
    label: "Bills",
    color: "var(--color-bill-fill)",  // references index.css @theme token
  },
  variable_expense: {
    label: "Variable Expenses",
    color: "var(--color-variable-expense-fill)",
  },
  // ... other category types
} satisfies ChartConfig

// In JSX:
<ChartContainer config={chartConfig} className="min-h-[250px] w-full">
  <PieChart>
    <Pie data={data} dataKey="value" nameKey="type" fill="var(--color-bill)" />
    <ChartTooltip content={<ChartTooltipContent />} />
  </PieChart>
</ChartContainer>

Pattern 2: Donut Chart with Active Shape + Center Label

What: <Pie> with innerRadius/outerRadius creates donut shape. activeIndex + activeShape prop renders expanded sector on hover. <Label> placed inside <Pie> renders center text. When to use: ExpenseDonutChart component. Example:

// Source: Recharts 2.x API docs, recharts/recharts#191
import { PieChart, Pie, Cell, Sector, Label } from "recharts"

// Active shape: renders the hovered sector with larger outerRadius
const renderActiveShape = (props: any) => {
  const { cx, cy, innerRadius, outerRadius, startAngle, endAngle, fill } = props
  return (
    <Sector
      cx={cx}
      cy={cy}
      innerRadius={innerRadius}
      outerRadius={outerRadius + 8}
      startAngle={startAngle}
      endAngle={endAngle}
      fill={fill}
    />
  )
}

// In component:
const [activeIndex, setActiveIndex] = useState(-1)

<Pie
  data={pieData}
  dataKey="value"
  nameKey="type"
  innerRadius={60}
  outerRadius={85}
  activeIndex={activeIndex}
  activeShape={renderActiveShape}
  onMouseEnter={(_, index) => setActiveIndex(index)}
  onMouseLeave={() => setActiveIndex(-1)}
>
  {pieData.map((entry) => (
    <Cell key={entry.type} fill={`var(--color-${entry.type}-fill)`} />
  ))}
  <Label
    content={({ viewBox }) => {
      if (viewBox && "cx" in viewBox && "cy" in viewBox) {
        return (
          <text x={viewBox.cx} y={viewBox.cy} textAnchor="middle" dominantBaseline="middle">
            <tspan className="fill-foreground text-xl font-bold">
              {formatCurrency(totalExpenses, currency)}
            </tspan>
          </text>
        )
      }
    }}
  />
</Pie>

Pattern 3: Horizontal Bar Chart via layout="vertical"

What: Recharts uses layout="vertical" on <BarChart> to produce horizontal bars. Axes must be swapped: XAxis type="number" and YAxis type="category". When to use: SpendBarChart (budget vs actual by category type). Example:

// Source: Recharts API docs, shadcn/ui chart-bar-horizontal pattern
import { BarChart, Bar, XAxis, YAxis, CartesianGrid } from "recharts"

<BarChart layout="vertical" data={spendData}>
  <CartesianGrid horizontal={false} />
  <XAxis type="number" hide />
  <YAxis
    type="category"
    dataKey="label"
    width={120}
    tick={{ fontSize: 12 }}
  />
  <Bar dataKey="budgeted" fill="var(--color-budgeted)" radius={4} />
  <Bar dataKey="actual" fill="var(--color-actual)" radius={4}>
    {spendData.map((entry, index) => (
      <Cell
        key={index}
        fill={entry.actual > entry.budgeted
          ? "var(--color-over-budget)"
          : `var(--color-${entry.type}-fill)`
        }
      />
    ))}
  </Bar>
</BarChart>

Pattern 4: Month Navigation with URL Search Params

What: useSearchParams stores the selected month as ?month=YYYY-MM in the URL. A custom useMonthParam hook parses the param, falls back to current month, and provides setter functions. When to use: DashboardPage and MonthNavigator. Example:

// Source: React Router v7 docs
import { useSearchParams } from "react-router-dom"

function useMonthParam() {
  const [searchParams, setSearchParams] = useSearchParams()

  const monthParam = searchParams.get("month")
  const now = new Date()
  const currentMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`
  const month = monthParam || currentMonth  // "YYYY-MM"

  const setMonth = (newMonth: string) => {
    setSearchParams((prev) => {
      prev.set("month", newMonth)
      return prev
    })
  }

  const navigateMonth = (delta: number) => {
    const [year, mo] = month.split("-").map(Number)
    const d = new Date(year, mo - 1 + delta, 1)
    const next = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`
    setMonth(next)
  }

  return { month, setMonth, navigateMonth }
}

Pattern 5: Over-Budget Visual Indicator

What: For bar charts, when actual exceeds budgeted, the actual bar uses --color-over-budget (red) fill via <Cell> conditional coloring. The bar naturally extends past the budgeted bar length, providing a visual overshoot. When to use: Both IncomeBarChart and SpendBarChart. Key detail: With grouped (non-stacked) bars, the actual bar and budgeted bar render side by side. The actual bar being taller/longer than the budgeted bar IS the visual indicator. Adding a red fill when over-budget reinforces the message.

Anti-Patterns to Avoid

  • Wrapping Recharts in abstractions: shadcn/ui explicitly says "we do not wrap Recharts." Use Recharts components directly, only adding ChartContainer/ChartTooltip as enhancement wrappers.
  • Hardcoded hex colors in charts: All colors must flow through CSS variables via ChartConfig. The existing categoryColors in palette.ts already uses var(--color-*) references.
  • Using ResponsiveContainer directly: The project's chart.tsx already wraps it inside ChartContainer with the initialDimension patch. Using raw ResponsiveContainer bypasses the fix and causes width(-1) console warnings.
  • Stacking bars when grouped is needed: For income bar chart, budgeted and actual should be side-by-side (grouped), not stacked. Do NOT use stackId -- just place two <Bar> components without it.
  • Putting month state in React state: User decision requires URL search params. Using useState for month would lose shareability and browser back/forward support.

Don't Hand-Roll

Problem Don't Build Use Instead Why
Chart color theming Custom CSS injection per chart ChartContainer + ChartConfig from chart.tsx Handles theme injection, dark mode, and scoped CSS variables automatically
Responsive chart sizing Manual resize observers ChartContainer (wraps ResponsiveContainer with initialDimension patch) Already solves the SSR/initial-render sizing bug
Chart tooltips Custom div overlays on hover <ChartTooltip content={<ChartTooltipContent />} /> Pre-styled, reads ChartConfig labels, handles positioning
Currency formatting toFixed(2) or template literals formatCurrency(amount, currency) from lib/format.ts Handles locale-aware formatting (Intl.NumberFormat)
Month arithmetic Manual date string manipulation new Date(year, month + delta, 1) Handles year rollover (Dec to Jan) automatically
Category color lookup Switch statements or if/else chains categoryColors[type] from palette.ts Single source of truth, already uses CSS variable references

Key insight: The shadcn chart.tsx component is the critical integration layer. It provides ChartContainer (with the initialDimension fix), ChartConfig (color theming), ChartTooltip/Content (pre-styled tooltips), and ChartLegend/Content (pre-styled legends). Every chart MUST use ChartContainer as its outer wrapper.

Common Pitfalls

Pitfall 1: Recharts width(-1) Console Warnings

What goes wrong: Charts render with 0 or negative width on initial mount, producing console warnings and invisible charts. Why it happens: ResponsiveContainer measures parent on mount; if parent has no explicit dimensions yet, width resolves to 0 or -1. How to avoid: Always use ChartContainer from chart.tsx (which sets initialDimension={{ width: 320, height: 200 }}). Also set className="min-h-[250px] w-full" on ChartContainer. Warning signs: Console warns width(-1) or chart appears blank on first render.

Pitfall 2: Horizontal Bar Chart Axis Confusion

What goes wrong: Setting layout="vertical" but leaving XAxis/YAxis in default configuration produces broken or invisible bars. Why it happens: Recharts naming is counterintuitive -- layout="vertical" means bars go horizontal. When layout is vertical, XAxis must be type="number" and YAxis must be type="category". How to avoid: Always pair layout="vertical" with <XAxis type="number" /> and <YAxis type="category" dataKey="label" />. Warning signs: Bars not visible, axis labels missing, or bars rendering as tiny dots.

Pitfall 3: ChartConfig Keys Must Match Data Keys

What goes wrong: Tooltip labels show raw dataKey names instead of formatted labels, or colors don't apply. Why it happens: ChartConfig keys are used to look up labels and colors. If the config key doesn't match the dataKey or nameKey used in the chart, the lookup fails silently. How to avoid: Ensure ChartConfig keys exactly match the dataKey and nameKey values used on <Bar>, <Pie>, and tooltip/legend nameKey props. Warning signs: Tooltips showing "budgeted" instead of "Budgeted Amount", or missing color dots in legend.

Pitfall 4: Pie Chart Label Positioning with viewBox

What goes wrong: Center label text does not appear or appears at wrong position. Why it happens: In Recharts 2.x, the <Label> component inside <Pie> receives a viewBox prop with cx/cy coordinates. If the content function doesn't destructure and check for these, the text won't render. How to avoid: Always check viewBox && "cx" in viewBox && "cy" in viewBox before rendering the <text> element in the Label content function. Warning signs: Donut chart renders but center is empty.

Pitfall 5: useSearchParams Replaces All Params

What goes wrong: Setting one search param wipes out others. Why it happens: setSearchParams({ month: "2026-03" }) replaces ALL params. If other params existed, they're gone. How to avoid: Use the callback form: setSearchParams(prev => { prev.set("month", value); return prev }). This preserves existing params. Warning signs: Other URL params disappearing when changing month.

Pitfall 6: Empty Array Passed to PieChart

What goes wrong: Recharts throws errors or renders broken SVG when <Pie data={[]}/> is used. Why it happens: Recharts expects at least one data point for proper SVG path calculation. How to avoid: Conditionally render the chart only when data exists, or show the ChartEmptyState placeholder when data is empty. Warning signs: Console errors about NaN or invalid SVG path.

Pitfall 7: Month Param Mismatch with Budget start_date Format

What goes wrong: Budget lookup fails even though the correct month is selected. Why it happens: URL param is YYYY-MM but budget.start_date is YYYY-MM-DD. Comparison must use startsWith prefix matching. How to avoid: Use budget.start_date.startsWith(monthParam) for matching, consistent with the existing currentMonthStart helper pattern. Warning signs: "No budget" message shown for a month that has a budget.

Code Examples

Verified patterns from the project codebase and official sources:

ChartConfig for Category Colors (Using Existing CSS Variables)

// Source: project index.css @theme tokens + palette.ts pattern
import type { ChartConfig } from "@/components/ui/chart"

// For donut chart -- uses fill variants (lighter for chart fills)
export const expenseChartConfig = {
  bill: { label: "Bills", color: "var(--color-bill-fill)" },
  variable_expense: { label: "Variable Expenses", color: "var(--color-variable-expense-fill)" },
  debt: { label: "Debts", color: "var(--color-debt-fill)" },
  saving: { label: "Savings", color: "var(--color-saving-fill)" },
  investment: { label: "Investments", color: "var(--color-investment-fill)" },
} satisfies ChartConfig

// For income bar chart -- budgeted (muted) vs actual (vivid)
export const incomeBarConfig = {
  budgeted: { label: "Budgeted", color: "var(--color-budget-bar-bg)" },
  actual: { label: "Actual", color: "var(--color-income-fill)" },
} satisfies ChartConfig

// For spend bar chart -- same muted/vivid pattern, per-cell override for over-budget
export const spendBarConfig = {
  budgeted: { label: "Budgeted", color: "var(--color-budget-bar-bg)" },
  actual: { label: "Actual", color: "var(--color-muted-foreground)" },  // base; overridden per-cell
} satisfies ChartConfig

Memoized Data Derivation Pattern

// Source: React useMemo best practice for derived chart data
const pieData = useMemo(() => {
  return EXPENSE_TYPES.map((type) => {
    const total = items
      .filter((i) => i.category?.type === type)
      .reduce((sum, i) => sum + i.actual_amount, 0)
    return { type, value: total, label: t(`categories.types.${type}`) }
  }).filter((d) => d.value > 0)
}, [items, t])

const totalExpenses = useMemo(() => {
  return items
    .filter((i) => i.category?.type !== "income")
    .reduce((sum, i) => sum + i.actual_amount, 0)
}, [items])

Budget Lookup by Month Param

// Source: project DashboardPage.tsx existing pattern + useSearchParams
const { month } = useMonthParam()  // "YYYY-MM"
const { budgets, loading } = useBudgets()

const currentBudget = useMemo(() => {
  return budgets.find((b) => b.start_date.startsWith(month))
}, [budgets, month])

// Month dropdown options: all months that have budgets
const availableMonths = useMemo(() => {
  return budgets.map((b) => b.start_date.slice(0, 7))  // "YYYY-MM"
}, [budgets])

Empty Donut State (Zero Amounts)

// When budget exists but all actuals are 0: show neutral ring
const hasExpenseData = pieData.length > 0
const allZero = items
  .filter((i) => i.category?.type !== "income")
  .every((i) => i.actual_amount === 0)

// If allZero but items exist: render single neutral sector with $0 center
// If no items at all: render ChartEmptyState placeholder

State of the Art

Old Approach Current Approach When Changed Impact
Raw ResponsiveContainer ChartContainer with initialDimension shadcn/ui chart.tsx (Phase 1 patch) Eliminates width(-1) warnings
Hardcoded hex colors in charts CSS variable tokens via ChartConfig Phase 1 OKLCH token system Theme-aware, dark-mode-ready charts
Month in React state Month in URL search params Phase 2 (this phase) Shareable links, browser history
Single pie chart + progress bars 3-chart dashboard (donut + 2 bar charts) Phase 2 (this phase) Richer financial visualization

Deprecated/outdated:

  • Recharts 3.x <Label> in PieChart: Broken in v3.0 (issue #5985). The project is on 2.15.4 where it works correctly -- do NOT upgrade.
  • Direct style={{ backgroundColor: categoryColors[type] }}: The existing DashboardContent uses inline styles for legend dots. Charts should use ChartConfig + fill="var(--color-*)" instead.

Open Questions

  1. Donut legend placement decision

    • What we know: User left this to Claude's discretion (below vs right side of chart)
    • What's unclear: In a 3-column layout, right-side legend may be too cramped
    • Recommendation: Place legend below the donut chart. In a tight 3-column grid, vertical space is more available than horizontal. Use the shadcn ChartLegendContent with verticalAlign="bottom" or a custom legend matching the existing li-based pattern.
  2. Month navigation placement

    • What we know: User left this to Claude's discretion (PageShell CTA slot vs own row)
    • What's unclear: PageShell has an action slot that renders top-right
    • Recommendation: Use PageShell action slot for the MonthNavigator component. This keeps the dashboard title and month selector on the same row, saving vertical space and following the established PageShell pattern.
  3. Responsive breakpoint for chart collapse

    • What we know: User wants 3-col on desktop, stacked on smaller screens
    • What's unclear: Exact breakpoint (md? lg? xl?)
    • Recommendation: Use lg:grid-cols-3 (1024px+) for 3-column, md:grid-cols-2 for 2-column (donut + one bar side-by-side, third stacks below), single column below md. This matches the existing lg:grid-cols-3 breakpoint used by SummaryStrip.
  4. Muted bar color for "budgeted" amounts

    • What we know: The existing --color-budget-bar-bg: oklch(0.92 0.01 260) token is available
    • What's unclear: Whether this is visually distinct enough next to vivid category fills
    • Recommendation: Use --color-budget-bar-bg for budgeted bars. It is intentionally muted (low chroma, high lightness) to recede behind vivid actual bars. If too subtle, a slightly darker variant can be added to index.css.

Validation Architecture

Test Framework

Property Value
Framework None installed
Config file None
Quick run command bun run build (type-check + build)
Full suite command bun run build && bun run lint

Phase Requirements to Test Map

Req ID Behavior Test Type Automated Command File Exists?
UI-DONUT-01 Expense donut chart renders with center label, active hover, custom legend manual-only Visual inspection in browser N/A
UI-BAR-01 Vertical bar chart shows income budgeted vs actual manual-only Visual inspection in browser N/A
UI-HBAR-01 Horizontal bar chart shows category spend budget vs actual manual-only Visual inspection in browser N/A
UI-DASH-01 3-column chart layout, month navigation, empty states manual-only Visual inspection in browser N/A

Justification for manual-only: All requirements are visual/UI-specific (chart rendering, hover interactions, layout, responsive breakpoints). No test framework is installed, and adding one is out of scope for this phase. Type-checking via bun run build catches structural errors.

Sampling Rate

  • Per task commit: bun run build (catches type errors and import issues)
  • Per wave merge: bun run build && bun run lint
  • Phase gate: Build passes + visual verification of all 3 charts + month navigation

Wave 0 Gaps

  • No test infrastructure exists in the project
  • Visual/chart testing would require a framework like Playwright or Storybook (out of scope for this milestone)
  • bun run build serves as the automated quality gate

Sources

Primary (HIGH confidence)

  • Project codebase: src/components/ui/chart.tsx -- ChartContainer with initialDimension patch, ChartConfig type, ChartTooltip/Legend components
  • Project codebase: src/lib/palette.ts -- categoryColors using CSS variable references
  • Project codebase: src/index.css -- OKLCH color tokens including chart fills and semantic status colors
  • Project codebase: package.json -- recharts 2.15.4, react-router-dom 7.13.1
  • Recharts Pie API docs -- activeShape, activeIndex, innerRadius/outerRadius
  • Recharts Bar API docs -- stackId, layout, Cell
  • shadcn/ui Chart docs -- ChartContainer, ChartConfig, ChartTooltip patterns
  • React Router useSearchParams -- URL state management API

Secondary (MEDIUM confidence)

Tertiary (LOW confidence)

  • None -- all findings verified with primary or secondary sources

Metadata

Confidence breakdown:

  • Standard stack: HIGH -- all libraries already installed and in use; Recharts 2.15.4 API verified
  • Architecture: HIGH -- builds on established project patterns (PageShell, SummaryStrip, ChartContainer, palette.ts)
  • Pitfalls: HIGH -- verified via official docs, GitHub issues, and project-specific chart.tsx code

Research date: 2026-03-16 Valid until: 2026-04-16 (stable -- Recharts 2.x is mature, no breaking changes expected)