Skip to content

Polish and orchestrate dashboard motion and charts#16

Merged
tyl3r-ch merged 8 commits intomainfrom
v622
Apr 15, 2026
Merged

Polish and orchestrate dashboard motion and charts#16
tyl3r-ch merged 8 commits intomainfrom
v622

Conversation

@tyl3r-ch
Copy link
Copy Markdown
Contributor

@tyl3r-ch tyl3r-ch commented Apr 15, 2026

Summary by CodeRabbit

  • New Features

    • Centralized dashboard animation with section preload/reveal, per-item sequencing and eager preload for priority sections
    • Animated progress/meter fills and per-card/per-chart reveal timing for smoother visuals
    • Legend layout now wraps responsively instead of horizontal scrolling
    • Added translation strings for dominant provider info
  • Bug Fixes

    • Eliminated premature/flashing chart animations and inconsistent secondary-bar reveals
    • More consistent chart/meter loading timing and improved focusability during reveal
  • Tests

    • Added regression and integration tests covering motion sequencing, legend wrapping, and chart start timing

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 15, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ac697aa0-a89f-4a4a-97d8-f6454231d44c

📥 Commits

Reviewing files that changed from the base of the PR and between 5a6ce5d and 73dd7e6.

📒 Files selected for processing (1)
  • tests/frontend/dashboard-motion.test.tsx
✅ Files skipped from review due to trivial changes (1)
  • tests/frontend/dashboard-motion.test.tsx

📝 Walkthrough

Walkthrough

Introduces a centralized dashboard motion system (section + element lifecycle), per-item motion sequencing, chart animation helpers, AnimatedBarFill, ChartLegend API changes, many charts migrated to helper-driven animation, numerous frontend tests, and small locale and changelog updates.

Changes

Cohort / File(s) Summary
Dashboard motion core
src/components/dashboard/DashboardMotion.tsx, src/components/dashboard/DashboardSections.tsx
New DASHBOARD_MOTION constants, AnimatedDashboardSection, DashboardMotionItem, useDashboardSectionMotion, useDashboardElementMotion; added lazyWithPreload and per-section preload/eager wiring in DashboardSections.
Card item sequencing
src/components/cards/...
src/components/cards/MonthMetrics.tsx, src/components/cards/PrimaryMetrics.tsx, src/components/cards/SecondaryMetrics.tsx, src/components/cards/TodayMetrics.tsx
Removed grid-level FadeIn; each MetricCard is wrapped in DashboardMotionItem with explicit order values for per-card reveal sequencing.
Chart animation theme & helpers
src/components/charts/chart-theme.ts
Reworked CHART_ANIMATION timings and added exported helpers (getLineAnimationProps, getAreaAnimationProps, getBarAnimationProps, getRadialAnimationProps, getScatterAnimationProps) and SeriesRole type to centralize animation timing/easing/order.
Chart components — animation migration
src/components/charts/...
CostByWeekday.tsx, CostByModelOverTime.tsx, CostOverTime.tsx, CumulativeCost.tsx, ModelMix.tsx, TokenEfficiency.tsx, TokensOverTime.tsx, CostByModel.tsx, RequestsOverTime.tsx, TokenTypes.tsx, RequestCacheHitRateByModel.tsx, DistributionAnalysis.tsx, CorrelationAnalysis.tsx
Replaced direct Recharts animation props with chart-theme helper spreads; added order/role metadata, introduced runKey-based remounts where applicable, and updated legend usage to ChartLegend in several charts.
ChartCard & legend API changes
src/components/charts/ChartCard.tsx, src/components/charts/ChartLegend.tsx
ChartCard context changed from boolean to { active, delayMs, runKey } with new hooks (useChartAnimationState, useChartAnimationDelay, useChartAnimationRunKey) and ChartReveal motion wrapper. ChartLegend gained ChartLegendProps (payload,className,renderLabel) and now supports wrapping layout and custom label rendering.
Animated bar & replacements
src/components/features/animations/AnimatedBarFill.tsx, src/components/features/cache-roi/CacheROI.tsx, src/components/features/risk/ConcentrationRisk.tsx, src/components/tables/RecentDays.tsx, src/components/features/request-quality/RequestQuality.tsx, src/components/features/limits/ProviderLimitsSection.tsx
Added AnimatedBarFill component and replaced many inline/static progress fills with it; usages pass width, order, delayMs, durationMs and integrate with dashboard motion gating.
Heatmap & insights motion
src/components/features/heatmap/HeatmapCalendar.tsx, src/components/features/insights/UsageInsights.tsx
Heatmap cells converted to motion.rect with per-cell staggered delays and reduced-motion fallback; UsageInsights now uses DashboardMotionItem per card and adds test-friendly attributes.
UI card integration
src/components/ui/card.tsx
Card reveal logic now consults useDashboardSectionMotion() in addition to reduce-motion, changing when viewport whileInView reveal is used.
Tests — new & updated
tests/frontend/*, tests/unit/chart-theme.test.ts
Added many frontend tests for AnimatedDashboardSection, DistributionAnalysis, CorrelationAnalysis, legend integration, request-cache-rate, usage-insights, request-quality ordering, chart-theme unit test; updated tests to mock AnimatedBarFill/IntersectionObserver and extended some timeouts.
Changelog & locales
CHANGELOG.md, src/locales/en/common.json, src/locales/de/common.json
Added 6.2.2 changelog entry and new metricCards.secondary.dominantProviderInfo localization keys (en/de).
Misc. wiring/refactors
various files
Updated imports/usage to new helpers/hooks, keyed ResponsiveContainer by runKey in some charts, minor prop/type additions (ChartLegendProps) and internal prop adjustments; no breaking public API removals.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant Section as AnimatedDashboardSection
    participant Preload as onPreload Handler
    participant PreloadObs as IntersectionObserver(preload)
    participant RevealObs as IntersectionObserver(reveal)
    participant Context as DashboardSectionMotion
    participant Element as DashboardElement (Chart/Meter/Item)

    Browser->>Section: mount
    Section->>PreloadObs: observe (preload rootMargin)
    PreloadObs-->>Section: intersect (preload)
    Section->>Preload: call onPreload()
    Preload-->>Section: resolve
    Section->>Context: mark contentPrepared
    Section->>RevealObs: observe (reveal threshold)
    RevealObs-->>Section: intersect (reveal)
    Section->>Context: set sectionVisible=true
    Context->>Element: expose {sectionVisible, chartStartDelay, shouldReduceMotion}
    Element->>Element: compute delayMs (kind + order + chartStartDelay)
    Element->>Element: animate (framer-motion or AnimatedBarFill) using delayMs/runKey
    Element-->>Browser: becomes visible/interactive
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I hopped through props and timing, soft paws on every card,

I wrapped each metric in an order so reveals stay calm and starred.
Legends learned to kindly wrap their words, bars swell when called to play,
Charts wait for their cue to start, no flashes steal the day.
A tiny rabbit nods with joy — the dashboard hums away.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: introducing centralized motion orchestration for dashboard sections, elements, and charts with improved animation timing and lifecycle coordination.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch v622

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/components/features/insights/UsageInsights.tsx (1)

96-125: ⚠️ Potential issue | 🟡 Minor

Keep the insight cards stretching after adding motion wrappers.

These DashboardMotionItems are now the grid items, so the inner Card no longer gets grid stretch for free. On the 2/4-column layouts the card bottoms will go ragged once summaries wrap to different lengths. Give the wrappers and InsightCard root a full-height class.

Also applies to: 127-181, 183-229, 231-266

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/features/insights/UsageInsights.tsx` around lines 96 - 125,
The DashboardMotionItem wrappers are now the grid items so the inner InsightCard
no longer stretches; add full-height styling by giving the DashboardMotionItem
instances a full-height class (e.g., className="h-full") and ensure the root
element of InsightCard also accepts/applies a full-height class (add a h-full to
its root container or support a className prop passed from its callers). Apply
this change for the blocks around the InsightCard usages (the current block and
the other instances you noted) so cards in 2/4-column layouts keep equal
heights.
src/components/charts/CorrelationAnalysis.tsx (1)

147-162: ⚠️ Potential issue | 🟠 Major

Don't hide scatter data when reduced motion is enabled.

animatePoints now controls both animation and whether any points are rendered. When chartMotion.shouldReduceMotion is true, Line 219 forces animatePoints to false, so CorrelationPanel feeds [] into Recharts forever. That makes both charts blank for reduced-motion users instead of rendering them statically.

♿ Suggested split between visibility and animation
 function CorrelationPanel({
   title,
   subtitle,
   mode,
   data,
   color,
   animationBegin = 0,
   xAxisName,
   xTickFormatter,
   yAxisName,
   footer,
+  showPoints,
   animatePoints,
 }: {
   title: string
   subtitle: string
   mode: 'requestCost' | 'cacheEfficiency'
   data: ScatterPoint[]
   color: string
   animationBegin?: number
   xAxisName: string
   xTickFormatter?: (value: number) => string
   yAxisName: string
   footer: string
+  showPoints: boolean
   animatePoints: boolean
 }) {
-  const chartData = animatePoints ? data : []
+  const chartData = showPoints ? data : []
-  const animatePoints = !chartMotion.shouldReduceMotion && chartMotion.active
+  const showPoints = chartMotion.shouldReduceMotion || chartMotion.active
+  const animatePoints = !chartMotion.shouldReduceMotion && chartMotion.active
-          animatePoints={animatePoints}
+          showPoints={showPoints}
+          animatePoints={animatePoints}
...
-          animatePoints={animatePoints}
+          showPoints={showPoints}
+          animatePoints={animatePoints}

Also applies to: 214-220

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/charts/CorrelationAnalysis.tsx` around lines 147 - 162, The
component currently uses animatePoints to both hide points and control animation
(chartData = animatePoints ? data : []), which causes charts to be empty when
chartMotion.shouldReduceMotion forces animatePoints false; change this so
chartData always equals data (const chartData = data) and introduce/keep a
separate flag (e.g., animatePoints or animateFlag) that only controls animation
props on Recharts components; update CorrelationAnalysis (and the code path
where chartMotion.shouldReduceMotion sets animatePoints) so visibility uses
chartData/data while animation uses the reduced-motion-aware flag.
🧹 Nitpick comments (8)
src/components/charts/chart-theme.ts (1)

44-49: Simplify duplicated delay branch logic.

The stacked and default branches currently compute the same value, so this can be reduced to a simpler conditional for readability.

♻️ Suggested simplification
-  const delayOffset =
-    role === 'secondary'
-      ? 140 + order * CHART_ANIMATION.stagger
-      : role === 'stacked'
-        ? order * CHART_ANIMATION.stagger
-        : order * CHART_ANIMATION.stagger
+  const delayOffset =
+    role === 'secondary'
+      ? 140 + order * CHART_ANIMATION.stagger
+      : order * CHART_ANIMATION.stagger
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/charts/chart-theme.ts` around lines 44 - 49, The delayOffset
calculation has duplicate logic for the 'stacked' and default branches; simplify
by computing the common base (order * CHART_ANIMATION.stagger) and only adding
the 140 offset when role === 'secondary' so that delayOffset is set to that base
plus 140 for 'secondary' otherwise just the base (refer to the delayOffset
variable, role, order, and CHART_ANIMATION.stagger).
src/components/dashboard/dashboard-motion.tsx (1)

1-353: Rename this file to match the component filename convention.

This module exports React components, so introducing a kebab-cased filename here creates a new exception to the repo rule and propagates it through imports.

As per coding guidelines, src/components/**/*.tsx: Use PascalCase or descriptive kebab-free names for component filenames such as Dashboard.tsx.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/dashboard/dashboard-motion.tsx` around lines 1 - 353, The file
name uses kebab-case which violates the component filename convention; rename
the file to a PascalCase name that reflects the exported symbols (for example
DashboardMotion.tsx or DashboardSectionMotion.tsx) and update all imports that
reference "dashboard-motion" to the new filename; ensure the exported
identifiers (useDashboardSectionMotion, DashboardMotionItem,
AnimatedDashboardSection, etc.) remain unchanged so consumers keep working.
src/components/ui/card.tsx (1)

12-29: Consider consolidating duplicate motion configurations.

The shouldReduceMotion and dashboardSectionMotion branches produce identical objects. This duplication could be simplified.

♻️ Suggested simplification
-  const motionProps = shouldReduceMotion
-    ? {
-        initial: false as const,
-        animate: { opacity: 1, y: 0 },
-        transition: { duration: 0 },
-      }
-    : dashboardSectionMotion
-      ? {
-          initial: false as const,
-          animate: { opacity: 1, y: 0 },
-          transition: { duration: 0 },
-        }
-      : {
-          initial: { opacity: 0, y: 14 },
-          whileInView: { opacity: 1, y: 0 },
-          viewport: { once: true, amount: 0.15 },
-          transition: { duration: 0.35, ease: 'easeOut' as const },
-        }
+  const skipViewportAnimation = shouldReduceMotion || dashboardSectionMotion
+  const motionProps = skipViewportAnimation
+    ? {
+        initial: false as const,
+        animate: { opacity: 1, y: 0 },
+        transition: { duration: 0 },
+      }
+    : {
+        initial: { opacity: 0, y: 14 },
+        whileInView: { opacity: 1, y: 0 },
+        viewport: { once: true, amount: 0.15 },
+        transition: { duration: 0.35, ease: 'easeOut' as const },
+      }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/card.tsx` around lines 12 - 29, The motionProps assignment
duplicates the same motion object for both shouldReduceMotion and
dashboardSectionMotion branches; refactor motionProps to compute the
reduced/static motion object once (e.g., const staticMotion = { initial: false
as const, animate: { opacity: 1, y: 0 }, transition: { duration: 0 } }) and then
use that for the shouldReduceMotion and dashboardSectionMotion cases, leaving
the original animated object for the fallback; update references to motionProps
only (no other API changes).
src/components/features/request-quality/RequestQuality.tsx (1)

78-90: Redundant CSS transition classes with framer-motion animation.

The className includes transition-[width] duration-700 ease-out for CSS transitions, but AnimatedBarFill internally uses framer-motion's animate prop to animate the width. These compete, with framer-motion typically taking precedence. The CSS transition classes are effectively no-ops and add confusion.

♻️ Remove redundant CSS transition classes
 <AnimatedBarFill
-  className="h-full rounded-full transition-[width] duration-700 ease-out motion-reduce:transition-none"
+  className="h-full rounded-full"
   style={{
     backgroundColor: `hsl(${item.accent})`,
   }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/features/request-quality/RequestQuality.tsx` around lines 78 -
90, The CSS width transition classes in RequestQuality.tsx are redundant because
AnimatedBarFill uses framer-motion for width animation; update the JSX for
AnimatedBarFill to remove the CSS transition utilities (`transition-[width]`,
`duration-700`, `ease-out`) from its className and retain only non-conflicting
classes like `h-full` and `rounded-full` (and keep any
`motion-reduce:transition-none` if you still want to support reduced-motion).
This ensures framer-motion's animate prop drives the width changes without
confusing or dead CSS rules.
src/components/dashboard/DashboardSections.tsx (2)

41-140: lazyWithPreload helper defined after its usage.

The lazyWithPreload function is used at lines 41-140 but defined at lines 148-154. While JavaScript hoisting handles function declarations, this pattern reduces readability. Consider moving the helper definition before its usages.

📦 Suggested reordering

Move lines 142-160 (type definition, lazyWithPreload, and preloadComponents) to before line 41, right after the imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/dashboard/DashboardSections.tsx` around lines 41 - 140, The
helper function lazyWithPreload (and its related preloadComponents/type) is
declared after it's used, hurting readability; move the type definition for
LazyWithPreload, the lazyWithPreload function, and preloadComponents declaration
so they appear before any imports that use them (i.e., place them above the
const declarations like CostForecast, CostByModelOverTime, etc.), then run a
quick build to ensure no import order issues.

285-296: Deeply nested ternary could be simplified with a lookup object.

The section anchor ID mapping uses a 5-level nested ternary. A lookup object would improve readability.

♻️ Proposed refactor
+  const sectionAnchorMap: Partial<Record<DashboardSectionId, string>> = {
+    costAnalysis: 'charts',
+    currentMonth: 'current-month',
+    tokenAnalysis: 'token-analysis',
+    requestAnalysis: 'request-analysis',
+    advancedAnalysis: 'advanced-analysis',
+  }
+
   const renderAnimatedSection = (
     sectionId: DashboardSectionId,
     children: ReactNode,
     {
       eager = false,
       onPreload,
     }: { eager?: boolean; onPreload?: () => void | Promise<unknown> } = {},
   ) => {
-    const sectionAnchorId =
-      sectionId === 'costAnalysis'
-        ? 'charts'
-        : sectionId === 'currentMonth'
-          ? 'current-month'
-          : sectionId === 'tokenAnalysis'
-            ? 'token-analysis'
-            : sectionId === 'requestAnalysis'
-              ? 'request-analysis'
-              : sectionId === 'advancedAnalysis'
-                ? 'advanced-analysis'
-                : sectionId
+    const sectionAnchorId = sectionAnchorMap[sectionId] ?? sectionId

     return (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/dashboard/DashboardSections.tsx` around lines 285 - 296, The
nested ternary that computes sectionAnchorId is hard to read; replace it with a
lookup object mapping known sectionId values to their anchor strings (e.g., map
'costAnalysis' -> 'charts', 'currentMonth' -> 'current-month', 'tokenAnalysis'
-> 'token-analysis', 'requestAnalysis' -> 'request-analysis', 'advancedAnalysis'
-> 'advanced-analysis') and then set sectionAnchorId = lookup[sectionId] ||
sectionId. Update the code that currently references sectionAnchorId to use this
new mapping logic (keep the symbol names sectionAnchorId and sectionId) and
ensure the fallback to the original sectionId remains.
tests/frontend/provider-limits-section.test.tsx (1)

99-99: Consider if 15-second timeout is necessary.

Both tests use 15_000 ms timeout but appear to be synchronous renders without real timers or network calls. If there's no specific reason for this extended timeout (e.g., slow CI environments), consider using the default or a smaller value.

Also applies to: 124-124

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/frontend/provider-limits-section.test.tsx` at line 99, Two tests in
provider-limits-section.test.tsx pass an explicit 15_000 ms timeout (the numeric
literal 15_000) but appear to be synchronous renders; remove the oversized
timeout by deleting the third argument so the tests use Jest's default timeout,
or replace 15_000 with a much smaller value like 2000 if a custom timeout is
required; apply this change to both occurrences of the 15_000 timeout.
src/components/features/heatmap/HeatmapCalendar.tsx (1)

380-416: Consider extracting magic numbers into named constants.

The delay calculations use inline magic numbers (12, 6, 90). Extracting these to named constants would improve readability and maintainability.

♻️ Proposed constants
+const CELL_STAGGER_WEEK_OFFSET_MS = 12
+const CELL_STAGGER_DAY_OFFSET_MS = 6
+const TODAY_OUTLINE_DELAY_MS = 90

 const cellMotionProps = shouldReduceMotion
   ? {}
   : {
       // ...
       transition: {
         duration: 0.28,
         delay:
           (animateCells
             ? cellAnimationDelayMs +
-              cell.week * (DASHBOARD_MOTION.itemStaggerMs + 12) +
-              cell.day * 6
+              cell.week * (DASHBOARD_MOTION.itemStaggerMs + CELL_STAGGER_WEEK_OFFSET_MS) +
+              cell.day * CELL_STAGGER_DAY_OFFSET_MS
             : 0) / 1000,
         // ...
       },
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/features/heatmap/HeatmapCalendar.tsx` around lines 380 - 416,
Extract the inline magic numbers used in the delay calculations inside
HeatmapCalendar.tsx (within the cellMotionProps and todayOutlineMotionProps
blocks) into well-named constants—e.g., WEEK_STAGGER_OFFSET_MS (for 12),
DAY_STAGGER_MS (for 6), and TODAY_OUTLINE_EXTRA_DELAY_MS (for 90)—and replace
the literals in the delay expressions that add to cellAnimationDelayMs +
cell.week * (DASHBOARD_MOTION.itemStaggerMs + 12) + cell.day * 6 and the today
outline delay; keep the existing arithmetic/units (ms) and ensure the constants
are defined near the top of the module (or next to DASHBOARD_MOTION) so the
delay expressions in cellMotionProps and todayOutlineMotionProps read clearly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/cards/SecondaryMetrics.tsx`:
- Around line 95-101: The MetricCard for the dominant provider is using the
wrong tooltip key (t('metricCards.secondary.medianInfo')); update the info prop
on the MetricCard inside DashboardMotionItem (the component rendering
value={metrics.topProvider?.name ?? '–'} and subtitle via topProviderSubtitle)
to use the provider-specific translation key instead (e.g.
t('metricCards.secondary.dominantProviderInfo') or the corresponding provider
help entry).

In `@src/components/charts/CostByModel.tsx`:
- Around line 101-105: The legend renderer in renderLabel produces "Label ()"
when no matching segment is found; update renderLabel (the arrow function using
entry, data, and formatCurrency) to build the label conditionally so it only
appends ` (formattedValue)` when segment is found and non-empty—i.e., compute
value = String(entry.value ?? '') and segment = data.find(...) and return value
alone if !segment, otherwise return `${value}
(${formatCurrency(segment.value)})`.

In `@src/components/charts/RequestCacheHitRateByModel.tsx`:
- Around line 224-229: In RequestCacheHitRateByModel, the non-daily
summary/legend blocks hard-code summary.total.trailing7Rate and the label
instead of using the existing trendLabel/trendRate switch; replace the
hard-coded label and value with trendLabel for the label and use the computed
trendRate key to access the value from summary.total (e.g.,
summary.total[trendRate]) so monthly/yearly views show the same metric as
trendLabel, and apply the same replacement to the other summary/legend block
later in the file that repeats this pattern.

In `@src/components/dashboard/dashboard-motion.tsx`:
- Around line 291-317: When sectionVisible is false, replace aria-hidden usage
with the inert attribute on the container so it and its focusable descendants
are removed from the keyboard/tab order; update both the reduced-motion branch
(the plain div) and the motion.div branch to set inert={true} when
sectionVisible is false and remove aria-hidden, and keep the inline opacity
styling. Also ensure a runtime fallback for browsers that don't support
inert—either load an inert polyfill or, as an immediate fallback, add
pointer-events: none and ensure focusable children (e.g., the expand button in
ChartCard.tsx) are set to tabIndex={-1} while the section is inert and restored
when visible; use the existing symbols DashboardSectionMotionContext.Provider,
shouldReduceMotion, sectionVisible, contentClassName, motion.div and children to
locate where to apply these changes.
- Around line 198-204: The current preload chain uses
Promise.resolve(onPreload()) which does not catch synchronous throws from
onPreload; change the creation of preloadTask to wrap synchronous execution by
using Promise.resolve().then(() => onPreload()) so any synchronous exception is
routed into the .catch() branch; keep the existing .catch(() => undefined) and
.finally(() => { if (isMountedRef.current) setContentPrepared(true) }) so that
setContentPrepared is always reached even if onPreload throws, and ensure this
change is applied to the preloadTask declaration that interacts with
hasTriggeredPreloadRef and isMountedRef.

In `@tests/frontend/dashboard-motion.test.tsx`:
- Line 4: The test suite replaces globalThis.IntersectionObserver but never
restores it; add cleanup to restore the global stub after each test by invoking
vi.unstubAllGlobals() (or capture the original globalThis.IntersectionObserver
before stubbing and restore it in an afterEach) so later tests are not
order-dependent; reference the globalThis.IntersectionObserver replacement in
this file and add an afterEach hook that calls vi.unstubAllGlobals() or
reassigns the saved original.

---

Outside diff comments:
In `@src/components/charts/CorrelationAnalysis.tsx`:
- Around line 147-162: The component currently uses animatePoints to both hide
points and control animation (chartData = animatePoints ? data : []), which
causes charts to be empty when chartMotion.shouldReduceMotion forces
animatePoints false; change this so chartData always equals data (const
chartData = data) and introduce/keep a separate flag (e.g., animatePoints or
animateFlag) that only controls animation props on Recharts components; update
CorrelationAnalysis (and the code path where chartMotion.shouldReduceMotion sets
animatePoints) so visibility uses chartData/data while animation uses the
reduced-motion-aware flag.

In `@src/components/features/insights/UsageInsights.tsx`:
- Around line 96-125: The DashboardMotionItem wrappers are now the grid items so
the inner InsightCard no longer stretches; add full-height styling by giving the
DashboardMotionItem instances a full-height class (e.g., className="h-full") and
ensure the root element of InsightCard also accepts/applies a full-height class
(add a h-full to its root container or support a className prop passed from its
callers). Apply this change for the blocks around the InsightCard usages (the
current block and the other instances you noted) so cards in 2/4-column layouts
keep equal heights.

---

Nitpick comments:
In `@src/components/charts/chart-theme.ts`:
- Around line 44-49: The delayOffset calculation has duplicate logic for the
'stacked' and default branches; simplify by computing the common base (order *
CHART_ANIMATION.stagger) and only adding the 140 offset when role ===
'secondary' so that delayOffset is set to that base plus 140 for 'secondary'
otherwise just the base (refer to the delayOffset variable, role, order, and
CHART_ANIMATION.stagger).

In `@src/components/dashboard/dashboard-motion.tsx`:
- Around line 1-353: The file name uses kebab-case which violates the component
filename convention; rename the file to a PascalCase name that reflects the
exported symbols (for example DashboardMotion.tsx or DashboardSectionMotion.tsx)
and update all imports that reference "dashboard-motion" to the new filename;
ensure the exported identifiers (useDashboardSectionMotion, DashboardMotionItem,
AnimatedDashboardSection, etc.) remain unchanged so consumers keep working.

In `@src/components/dashboard/DashboardSections.tsx`:
- Around line 41-140: The helper function lazyWithPreload (and its related
preloadComponents/type) is declared after it's used, hurting readability; move
the type definition for LazyWithPreload, the lazyWithPreload function, and
preloadComponents declaration so they appear before any imports that use them
(i.e., place them above the const declarations like CostForecast,
CostByModelOverTime, etc.), then run a quick build to ensure no import order
issues.
- Around line 285-296: The nested ternary that computes sectionAnchorId is hard
to read; replace it with a lookup object mapping known sectionId values to their
anchor strings (e.g., map 'costAnalysis' -> 'charts', 'currentMonth' ->
'current-month', 'tokenAnalysis' -> 'token-analysis', 'requestAnalysis' ->
'request-analysis', 'advancedAnalysis' -> 'advanced-analysis') and then set
sectionAnchorId = lookup[sectionId] || sectionId. Update the code that currently
references sectionAnchorId to use this new mapping logic (keep the symbol names
sectionAnchorId and sectionId) and ensure the fallback to the original sectionId
remains.

In `@src/components/features/heatmap/HeatmapCalendar.tsx`:
- Around line 380-416: Extract the inline magic numbers used in the delay
calculations inside HeatmapCalendar.tsx (within the cellMotionProps and
todayOutlineMotionProps blocks) into well-named constants—e.g.,
WEEK_STAGGER_OFFSET_MS (for 12), DAY_STAGGER_MS (for 6), and
TODAY_OUTLINE_EXTRA_DELAY_MS (for 90)—and replace the literals in the delay
expressions that add to cellAnimationDelayMs + cell.week *
(DASHBOARD_MOTION.itemStaggerMs + 12) + cell.day * 6 and the today outline
delay; keep the existing arithmetic/units (ms) and ensure the constants are
defined near the top of the module (or next to DASHBOARD_MOTION) so the delay
expressions in cellMotionProps and todayOutlineMotionProps read clearly.

In `@src/components/features/request-quality/RequestQuality.tsx`:
- Around line 78-90: The CSS width transition classes in RequestQuality.tsx are
redundant because AnimatedBarFill uses framer-motion for width animation; update
the JSX for AnimatedBarFill to remove the CSS transition utilities
(`transition-[width]`, `duration-700`, `ease-out`) from its className and retain
only non-conflicting classes like `h-full` and `rounded-full` (and keep any
`motion-reduce:transition-none` if you still want to support reduced-motion).
This ensures framer-motion's animate prop drives the width changes without
confusing or dead CSS rules.

In `@src/components/ui/card.tsx`:
- Around line 12-29: The motionProps assignment duplicates the same motion
object for both shouldReduceMotion and dashboardSectionMotion branches; refactor
motionProps to compute the reduced/static motion object once (e.g., const
staticMotion = { initial: false as const, animate: { opacity: 1, y: 0 },
transition: { duration: 0 } }) and then use that for the shouldReduceMotion and
dashboardSectionMotion cases, leaving the original animated object for the
fallback; update references to motionProps only (no other API changes).

In `@tests/frontend/provider-limits-section.test.tsx`:
- Line 99: Two tests in provider-limits-section.test.tsx pass an explicit 15_000
ms timeout (the numeric literal 15_000) but appear to be synchronous renders;
remove the oversized timeout by deleting the third argument so the tests use
Jest's default timeout, or replace 15_000 with a much smaller value like 2000 if
a custom timeout is required; apply this change to both occurrences of the
15_000 timeout.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7fbec493-6889-4c22-9ade-749ed63aff73

📥 Commits

Reviewing files that changed from the base of the PR and between dd9d808 and f26b6b6.

📒 Files selected for processing (44)
  • CHANGELOG.md
  • src/components/cards/MonthMetrics.tsx
  • src/components/cards/PrimaryMetrics.tsx
  • src/components/cards/SecondaryMetrics.tsx
  • src/components/cards/TodayMetrics.tsx
  • src/components/charts/ChartCard.tsx
  • src/components/charts/ChartLegend.tsx
  • src/components/charts/CorrelationAnalysis.tsx
  • src/components/charts/CostByModel.tsx
  • src/components/charts/CostByModelOverTime.tsx
  • src/components/charts/CostByWeekday.tsx
  • src/components/charts/CostOverTime.tsx
  • src/components/charts/CumulativeCost.tsx
  • src/components/charts/DistributionAnalysis.tsx
  • src/components/charts/ModelMix.tsx
  • src/components/charts/RequestCacheHitRateByModel.tsx
  • src/components/charts/RequestsOverTime.tsx
  • src/components/charts/TokenEfficiency.tsx
  • src/components/charts/TokenTypes.tsx
  • src/components/charts/TokensOverTime.tsx
  • src/components/charts/chart-theme.ts
  • src/components/dashboard/DashboardSections.tsx
  • src/components/dashboard/dashboard-motion.tsx
  • src/components/features/animations/AnimatedBarFill.tsx
  • src/components/features/cache-roi/CacheROI.tsx
  • src/components/features/forecast/CostForecast.tsx
  • src/components/features/heatmap/HeatmapCalendar.tsx
  • src/components/features/insights/UsageInsights.tsx
  • src/components/features/limits/ProviderLimitsSection.tsx
  • src/components/features/request-quality/RequestQuality.tsx
  • src/components/features/risk/ConcentrationRisk.tsx
  • src/components/tables/RecentDays.tsx
  • src/components/ui/card.tsx
  • tests/frontend/cache-roi.test.tsx
  • tests/frontend/chart-legend-integration.test.tsx
  • tests/frontend/cost-over-time.test.tsx
  • tests/frontend/dashboard-motion.test.tsx
  • tests/frontend/distribution-analysis.test.tsx
  • tests/frontend/info-heading.test.tsx
  • tests/frontend/provider-limits-section.test.tsx
  • tests/frontend/request-cache-hit-rate-by-model.test.tsx
  • tests/frontend/request-quality.test.tsx
  • tests/frontend/toast.test.tsx
  • tests/integration/server.test.ts

Comment thread src/components/cards/SecondaryMetrics.tsx
Comment thread src/components/charts/CostByModel.tsx
Comment thread src/components/charts/RequestCacheHitRateByModel.tsx Outdated
Comment thread src/components/dashboard/dashboard-motion.tsx Outdated
Comment thread src/components/dashboard/dashboard-motion.tsx Outdated
Comment thread tests/frontend/dashboard-motion.test.tsx Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/components/features/request-quality/RequestQuality.tsx (1)

78-89: Consider wiring bar order for deterministic stagger.

Right now each bar uses the default order=0, so they animate simultaneously. Passing index-based order would better match orchestrated sequencing.

💡 Proposed change
-          {qualityMetrics.map((item) => (
+          {qualityMetrics.map((item, index) => (
             <div key={item.label} className="rounded-xl border border-border/50 bg-muted/15 p-3">
               <div className="text-[10px] tracking-[0.14em] text-muted-foreground uppercase">
                 {item.label}
               </div>
@@
                 <AnimatedBarFill
                   className="h-full rounded-full"
+                  order={index}
                   style={{
                     backgroundColor: `hsl(${item.accent})`,
                   }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/features/request-quality/RequestQuality.tsx` around lines 78 -
89, The bars all animate at once because no ordering is supplied; when mapping
items pass a deterministic index-based order into AnimatedBarFill (e.g., add an
order={index} prop or style={{ order: index }}) so each bar can stagger based on
its position; update the map where AnimatedBarFill is rendered (reference
AnimatedBarFill, item, metrics.hasRequestData) to use the incoming index and
ensure the component/stylesheet consumes that order for staggered animation.
tests/frontend/usage-insights.test.tsx (1)

65-69: Make assertions more focused than generic class-chain matching.

The .grid > .h-full selectors are broad and can pass due to unrelated layout changes. Prefer a scoped container/test-id and assert the intended items explicitly.

As per coding guidelines: "Prefer focused *.test.ts or *.test.tsx coverage for data transforms, hooks, or complex UI behavior".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/frontend/usage-insights.test.tsx` around lines 65 - 69, The current
assertions use broad selectors ('.grid > .h-full' and '.grid > .h-full >
.h-full') which can be satisfied by unrelated layout elements; update the test
to target a scoped container or test ids and assert the intended items
explicitly: add/use a stable data-testid on the parent/panel (or use the
rendered component's semantic role) and replace motionItems/cards with queries
like getAllByTestId or within(scopedContainer).getAllByTestId for the specific
card/motion items, then assert on their expected count or specific
content/attributes to make the test focused and resistant to layout changes.
src/components/dashboard/DashboardMotion.tsx (1)

295-324: Consider simplifying duplicate style application.

The contentStyle is applied via the style prop, but it includes opacity: 0 when not visible while the animate prop also controls opacity. This creates redundancy—the motion.div's animate already handles opacity transitions.

💡 Suggested simplification

The contentStyle with opacity: 0 and pointerEvents: 'none' may be redundant when framer-motion's animate already controls opacity. However, this could be intentional for the initial render before animation kicks in. Consider whether the inline style is necessary or if animate's initial state suffices.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/dashboard/DashboardMotion.tsx` around lines 295 - 324, Remove
the redundant opacity in contentStyle when rendering the animated branch: for
the motion.div controlled by sectionVisible and DASHBOARD_MOTION, rely on
motion's initial/animate props for opacity and remove any hardcoded opacity: 0
from contentStyle so opacity is not applied twice; keep any non-opacity styles
(e.g., pointerEvents) and ensure the non-animated branch (when
shouldReduceMotion is true) still gets the necessary styles via contentStyle and
the inert logic around sectionVisible.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/cards/SecondaryMetrics.tsx`:
- Around line 78-92: The info prop is hardcoded to day-specific entries causing
wrong help text when viewMode is monthly/yearly; update the MetricCard usages
inside DashboardMotionItem (the card with label computed from viewMode and the
card around lines 104-120) to select the appropriate METRIC_HELP key based on
viewMode (e.g., use METRIC_HELP.mostExpensiveYear / .mostExpensiveMonth /
.mostExpensiveDay for the top-cost card and METRIC_HELP.avgCostPerYear /
.avgCostPerMonth / .avgCostPerDay for the average-cost card) so the info prop
mirrors the label logic; keep existing props like value, icon and subtitle
(topDaySubtitle/topAvgSubtitle) unchanged.

In `@src/components/charts/chart-theme.ts`:
- Around line 93-99: The scatter animation helper getScatterAnimationProps
currently sets animationDuration to CHART_ANIMATION.barDuration; change it to
use the scatter/reveal-specific constant CHART_ANIMATION.revealDuration so
scatter timing honors the new setting (keep animationBegin, animationEasing, and
the delayOffsetMs behavior as-is).

---

Nitpick comments:
In `@src/components/dashboard/DashboardMotion.tsx`:
- Around line 295-324: Remove the redundant opacity in contentStyle when
rendering the animated branch: for the motion.div controlled by sectionVisible
and DASHBOARD_MOTION, rely on motion's initial/animate props for opacity and
remove any hardcoded opacity: 0 from contentStyle so opacity is not applied
twice; keep any non-opacity styles (e.g., pointerEvents) and ensure the
non-animated branch (when shouldReduceMotion is true) still gets the necessary
styles via contentStyle and the inert logic around sectionVisible.

In `@src/components/features/request-quality/RequestQuality.tsx`:
- Around line 78-89: The bars all animate at once because no ordering is
supplied; when mapping items pass a deterministic index-based order into
AnimatedBarFill (e.g., add an order={index} prop or style={{ order: index }}) so
each bar can stagger based on its position; update the map where AnimatedBarFill
is rendered (reference AnimatedBarFill, item, metrics.hasRequestData) to use the
incoming index and ensure the component/stylesheet consumes that order for
staggered animation.

In `@tests/frontend/usage-insights.test.tsx`:
- Around line 65-69: The current assertions use broad selectors ('.grid >
.h-full' and '.grid > .h-full > .h-full') which can be satisfied by unrelated
layout elements; update the test to target a scoped container or test ids and
assert the intended items explicitly: add/use a stable data-testid on the
parent/panel (or use the rendered component's semantic role) and replace
motionItems/cards with queries like getAllByTestId or
within(scopedContainer).getAllByTestId for the specific card/motion items, then
assert on their expected count or specific content/attributes to make the test
focused and resistant to layout changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5104819f-cc13-4571-a1d5-50d8b9bd0a63

📥 Commits

Reviewing files that changed from the base of the PR and between f26b6b6 and 77e2cbb.

📒 Files selected for processing (28)
  • src/components/cards/MonthMetrics.tsx
  • src/components/cards/PrimaryMetrics.tsx
  • src/components/cards/SecondaryMetrics.tsx
  • src/components/cards/TodayMetrics.tsx
  • src/components/charts/ChartCard.tsx
  • src/components/charts/CorrelationAnalysis.tsx
  • src/components/charts/CostByModel.tsx
  • src/components/charts/RequestCacheHitRateByModel.tsx
  • src/components/charts/chart-theme.ts
  • src/components/dashboard/DashboardMotion.tsx
  • src/components/dashboard/DashboardSections.tsx
  • src/components/features/animations/AnimatedBarFill.tsx
  • src/components/features/heatmap/HeatmapCalendar.tsx
  • src/components/features/insights/UsageInsights.tsx
  • src/components/features/limits/ProviderLimitsSection.tsx
  • src/components/features/request-quality/RequestQuality.tsx
  • src/components/ui/card.tsx
  • src/locales/de/common.json
  • src/locales/en/common.json
  • tests/frontend/correlation-analysis.test.tsx
  • tests/frontend/dashboard-motion.test.tsx
  • tests/frontend/drill-down-modal.test.tsx
  • tests/frontend/heatmap-calendar.test.tsx
  • tests/frontend/phase4-correctness.test.tsx
  • tests/frontend/provider-limits-section.test.tsx
  • tests/frontend/request-cache-hit-rate-by-model.test.tsx
  • tests/frontend/sortable-tables.test.tsx
  • tests/frontend/usage-insights.test.tsx
✅ Files skipped from review due to trivial changes (3)
  • src/locales/en/common.json
  • src/locales/de/common.json
  • tests/frontend/dashboard-motion.test.tsx
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/components/cards/PrimaryMetrics.tsx
  • src/components/cards/TodayMetrics.tsx
  • tests/frontend/provider-limits-section.test.tsx
  • src/components/ui/card.tsx
  • tests/frontend/request-cache-hit-rate-by-model.test.tsx
  • src/components/features/animations/AnimatedBarFill.tsx
  • src/components/features/insights/UsageInsights.tsx

Comment thread src/components/cards/SecondaryMetrics.tsx
Comment thread src/components/charts/chart-theme.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
tests/frontend/request-quality-order.test.tsx (1)

43-53: Add afterEach cleanup to prevent global stub leakage.

Line 44 stubs IntersectionObserver globally without cleanup. Add afterEach(() => vi.unstubAllGlobals()) to prevent mock state from leaking into other tests. This pattern is used consistently across dashboard-motion.test.tsx and motion-accessibility.test.tsx in the codebase.

Suggested patch
-import { beforeEach, describe, expect, it, vi } from 'vitest'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
@@
 describe('RequestQuality bar ordering', () => {
   beforeEach(async () => {
@@
     await initI18n('en')
   })
+
+  afterEach(() => {
+    vi.unstubAllGlobals()
+  })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/frontend/request-quality-order.test.tsx` around lines 43 - 53, The test
stubs the global IntersectionObserver in the beforeEach block which leaks into
other tests; add an afterEach block that calls vi.unstubAllGlobals() to restore
globals after each test. Modify the file's setup by adding afterEach(() =>
vi.unstubAllGlobals()) alongside the existing beforeEach that defines the
IntersectionObserver stub and calls initI18n('en') so the global stub is cleaned
up between tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/cards/SecondaryMetrics.tsx`:
- Around line 116-132: The MetricCard inside DashboardMotionItem is passing
avgCostInfo for the info/tooltip even when viewMode === 'daily' (which shows
busiestWeek/peak7Days); update the info prop on this MetricCard to supply the
peak/peak7Days help text when viewMode === 'daily' (e.g., conditional on
viewMode or metrics.busiestWeek) instead of always using avgCostInfo so the
tooltip matches the label and value (reference symbols: DashboardMotionItem,
MetricCard, viewMode, metrics.busiestWeek, avgCostInfo,
METRIC_HELP.avgCostPerDay).

In `@src/components/dashboard/DashboardMotion.tsx`:
- Around line 133-149: Make inactive items non-interactive until reveal by using
itemMotion.active to toggle interactivity: add aria-hidden={!itemMotion.active}
and a style pointerEvents: itemMotion.active ? undefined : 'none' on the
motion.div (the element created in DashboardMotion.tsx), and implement a small
useEffect that watches itemMotion.active and, using itemRef.current, sets
tabIndex=-1 on any focusable descendants when inactive and restores their
original tabIndex when active (so keyboard users cannot tab into visually hidden
content). Reference: motion.div, itemRef, itemMotion.active.
- Around line 73-74: The element-level motion is still gated by useInView
(isInView) which defaults to false when IntersectionObserver is unavailable;
change the active calculation to apply the same fallback as sectionMotion (treat
in-view as true when IntersectionObserver is missing). Update the code
referencing isInView/active (e.g., the const isInView = useInView(...) and const
active = ...) to short-circuit when IntersectionObserver is undefined (use
typeof IntersectionObserver === 'undefined' or globalThis check) so active
becomes (sectionMotion?.sectionVisible ?? true) && (IntersectionObserver missing
? true : isInView).

In `@tests/frontend/secondary-metrics.test.tsx`:
- Around line 50-59: The test installs a global IntersectionObserver stub in the
beforeEach but never restores it, risking test pollution; update the spec to
capture the original global.IntersectionObserver before calling
vi.stubGlobal('IntersectionObserver', ...) and add an afterEach that restores
the original (or calls
vi.unstubAllGlobals()/vi.unstubGlobal('IntersectionObserver')) so the fake
observer is removed after each test; refer to the beforeEach,
IntersectionObserver stub and add a corresponding afterEach restoration.

---

Nitpick comments:
In `@tests/frontend/request-quality-order.test.tsx`:
- Around line 43-53: The test stubs the global IntersectionObserver in the
beforeEach block which leaks into other tests; add an afterEach block that calls
vi.unstubAllGlobals() to restore globals after each test. Modify the file's
setup by adding afterEach(() => vi.unstubAllGlobals()) alongside the existing
beforeEach that defines the IntersectionObserver stub and calls initI18n('en')
so the global stub is cleaned up between tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a4296320-5b35-4743-9bb0-78833446fb08

📥 Commits

Reviewing files that changed from the base of the PR and between 77e2cbb and 9f65580.

📒 Files selected for processing (10)
  • src/components/cards/SecondaryMetrics.tsx
  • src/components/charts/chart-theme.ts
  • src/components/dashboard/DashboardMotion.tsx
  • src/components/features/insights/UsageInsights.tsx
  • src/components/features/request-quality/RequestQuality.tsx
  • src/lib/help-content.ts
  • tests/frontend/request-quality-order.test.tsx
  • tests/frontend/secondary-metrics.test.tsx
  • tests/frontend/usage-insights.test.tsx
  • tests/unit/chart-theme.test.ts
✅ Files skipped from review due to trivial changes (2)
  • src/lib/help-content.ts
  • tests/unit/chart-theme.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/components/features/insights/UsageInsights.tsx
  • src/components/features/request-quality/RequestQuality.tsx
  • tests/frontend/usage-insights.test.tsx

Comment thread src/components/cards/SecondaryMetrics.tsx
Comment thread src/components/dashboard/DashboardMotion.tsx Outdated
Comment thread src/components/dashboard/DashboardMotion.tsx
Comment thread tests/frontend/secondary-metrics.test.tsx
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tests/frontend/dashboard-motion.test.tsx (1)

15-41: Avoid binding these tests to IntersectionObserver construction order.

Hard-coding instances[0], instances[1], and slice(2) makes the suite fail on harmless internal refactors that add or reorder observers. Capture the mock's options/observed target and select preload, reveal, and item observers by those semantics instead.

♻️ Possible cleanup
 class MockIntersectionObserver {
   static instances: MockIntersectionObserver[] = []

   callback: IntersectionObserverCallback
+  options?: IntersectionObserverInit

-  constructor(callback: IntersectionObserverCallback) {
+  constructor(callback: IntersectionObserverCallback, options?: IntersectionObserverInit) {
     this.callback = callback
+    this.options = options
     MockIntersectionObserver.instances.push(this)
   }

-  observe() {}
+  observe() {}

   unobserve() {}

   disconnect() {}
@@
 }
+
+function getObserver(predicate: (observer: MockIntersectionObserver) => boolean) {
+  const observer = MockIntersectionObserver.instances.find(predicate)
+  expect(observer).toBeDefined()
+  return observer!
+}
@@
-    act(() => {
-      MockIntersectionObserver.instances[0]?.trigger(true)
-    })
+    act(() => {
+      getObserver((observer) => observer.options?.threshold === 0).trigger(true)
+    })
@@
-    act(() => {
-      MockIntersectionObserver.instances[1]?.trigger(true)
-    })
+    act(() => {
+      getObserver(
+        (observer) => observer.options?.threshold === 0.14,
+      ).trigger(true)
+    })

Also applies to: 100-126, 147-163, 223-225

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/frontend/dashboard-motion.test.tsx` around lines 15 - 41, Tests rely on
MockIntersectionObserver.instances index positions (e.g., instances[0],
instances[1], instances.slice(2)) which is brittle; modify
MockIntersectionObserver to record the constructor options and the observed
targets (store the last observed element in observe()) and then update tests to
locate the preload/reveal/item observers by matching those recorded semantics
(e.g., matching options.root/rootMargin or the observed target element) instead
of by array index; update references to use instance.find by property (e.g.,
instance.options or instance.lastObserved) and keep trigger/triggering behavior
the same via the existing trigger method.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/frontend/dashboard-motion.test.tsx`:
- Around line 15-41: Tests rely on MockIntersectionObserver.instances index
positions (e.g., instances[0], instances[1], instances.slice(2)) which is
brittle; modify MockIntersectionObserver to record the constructor options and
the observed targets (store the last observed element in observe()) and then
update tests to locate the preload/reveal/item observers by matching those
recorded semantics (e.g., matching options.root/rootMargin or the observed
target element) instead of by array index; update references to use
instance.find by property (e.g., instance.options or instance.lastObserved) and
keep trigger/triggering behavior the same via the existing trigger method.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 58a1d9af-ff5a-4306-aa27-4bd0b01ba4cb

📥 Commits

Reviewing files that changed from the base of the PR and between 9f65580 and 5a6ce5d.

📒 Files selected for processing (6)
  • src/components/cards/SecondaryMetrics.tsx
  • src/components/dashboard/DashboardMotion.tsx
  • src/lib/help-content.ts
  • tests/frontend/dashboard-motion.test.tsx
  • tests/frontend/request-quality-order.test.tsx
  • tests/frontend/secondary-metrics.test.tsx
✅ Files skipped from review due to trivial changes (2)
  • src/lib/help-content.ts
  • tests/frontend/secondary-metrics.test.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/frontend/request-quality-order.test.tsx

@tyl3r-ch tyl3r-ch merged commit 8fe0a76 into main Apr 15, 2026
8 checks passed
@tyl3r-ch tyl3r-ch deleted the v622 branch April 20, 2026 20:39
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.

1 participant