Skip to content

Refactor codebase, enhance TypeScript checks, and update tooling#8

Merged
tyl3r-ch merged 34 commits intomainfrom
v616
Apr 13, 2026
Merged

Refactor codebase, enhance TypeScript checks, and update tooling#8
tyl3r-ch merged 34 commits intomainfrom
v616

Conversation

@tyl3r-ch
Copy link
Copy Markdown
Contributor

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

Summary by CodeRabbit

  • New Features

    • Added editor/formatting configs, Prettier/ESLint integration, model-name normalization, CSV export helpers, and a numeric coercion utility.
  • Bug Fixes

    • Improved null-safety and defensive checks across data transforms, charts, API error handling, and SSE parsing.
  • Chores

    • Strengthened CI/build verification gates, separated production build task, pinned workflow actions, and updated npm scripts.
  • Documentation

    • Updated README, CONTRIBUTING, RELEASING, AGENTS and CHANGELOG with new build/verify guidance.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b203ea29-107b-4bf7-b0ea-fbc7dcf2375f

📥 Commits

Reviewing files that changed from the base of the PR and between 8423700 and 9a6bf96.

📒 Files selected for processing (10)
  • src/components/charts/CostByModelOverTime.tsx
  • src/components/charts/CostOverTime.tsx
  • src/components/features/drill-down/DrillDownModal.tsx
  • src/lib/data-transforms.ts
  • src/lib/help-content.ts
  • tests/frontend/cost-by-model-over-time.test.tsx
  • tests/frontend/cost-over-time.test.tsx
  • tests/frontend/phase4-correctness.test.tsx
  • tests/unit/code-rabbit-phase4.test.ts
  • tests/unit/help-content.test.ts
✅ Files skipped from review due to trivial changes (1)
  • src/lib/help-content.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/features/drill-down/DrillDownModal.tsx

📝 Walkthrough

Walkthrough

Adds editor/Prettier/ESLint configs, tightens CI and release workflows with format/lint/tsc gates and pinned actions, introduces spec-driven model normalization, refactors server CLI/port/exports and many client components/hooks for defensive checks, and updates scripts, types, and documentation to use new verify/build scripts.

Changes

Cohort / File(s) Summary
Editor & Formatter
/.editorconfig, /.prettierrc.json, /.prettierignore
New repo-wide editor and Prettier settings: UTF‑8, LF, 2‑space indent, single quotes, no semicolons, trailing commas; markdown exempt from trailing-whitespace trimming; common artifacts ignored.
Linting
eslint.config.mjs
New flat ESLint configuration with JS/TS/TSX rules, type-aware checks for src/**/*, react-hooks plugin, and Prettier integration.
CI & Release Workflows
.github/workflows/ci.yml, .github/workflows/release.yml
Workflows now run format:check, lint, and tsc --noEmit; build uses build:app; actions and setup steps pinned to SHAs; release job uses release environment and reduced token permissions.
Package scripts & deps
package.json
Added format, format:check, lint, lint:fix, build:app, verify, updated prepare and release verify scripts; added ESLint/Prettier/TS‑ESLint devDependencies.
Server model normalization spec
server/model-normalization.json
New JSON mapping of display aliases and provider regex matchers for model name normalization.
Model normalization & utils
server/report/utils.js, src/lib/model-utils.ts
Switched from substring heuristics to spec-driven regex normalization/provider detection; added __test__ exports for normalization/provider helpers.
Server CLI & runtime
server.js
Introduced bootstrapCli() and exports (bootstrapCli, runCli, __test__), added getExecutableName and listenOnAvailablePort, refactored port probing and lock/error handling; removed sendFile.
API & SSE/auto-import
src/lib/api.ts, src/lib/auto-import.ts
Standardized response parsing and error-message extraction; added typed SSE parsing (parseEventData) and a typed translator; API functions use helpers for robust error handling.
Data transforms & calculations
src/lib/data-transforms.ts, src/lib/calculations.ts
Added defensive guards for missing entries, conditional prev/MA fields, safer quantile/forecast logic and single-pass peak scans.
Formatters
src/lib/formatters.ts
Added coerceNumber() and hardened month parsing/formatting.
Types & constants
src/types/index.ts, src/lib/constants.ts
Added optional prev fields to chart types and totalTokens to token points; normalized Gemini/OpenCode keys syntax.
Charts & reporting
server/report/*.js, src/components/charts/*
CSV helpers, multi-line signatures, Y axis tick coercion via coerceNumber, defensive checks for undefined points and array bounds; formatting and API-compatible changes.
Dashboard & UI components
src/components/Dashboard.tsx, src/components/**
Large set of formatting and defensive updates: memoization, conditional prop spreads, typed PDF request usage, many component refactors (mostly behavior-preserving).
Settings & limits UI
src/components/features/settings/SettingsModal.tsx, .../limits/ProviderLimitsSection.tsx
onSaveSettings tightened to `Promise
Scripts & verification
scripts/* (start-test-server.js, verify-*.js, report-smoke.js)
Call bootstrapCli() in test server, improved error handling, switched build invocations to build:app, and many formatting/argument-layout changes.
Hooks & state
src/hooks/*
Removed viewMode param from useComputedMetrics, standardized updater styles, added void for discarded promise invalidations.
UI primitives & accessibility
src/components/ui/*, src/components/layout/*, src/components/tables/*
Card props now align with motion.div, added aria-sort buttons, DatePicker aria-labels localized, and multiple prop-structure/typing tweaks.
Docs, examples & assets
README.md, CONTRIBUTING.md, RELEASING.md, AGENTS.md, CHANGELOG.md, examples/*, index.html, src/index.css
Docs updated to document verify, build:app, and CI gates; changelog for v6.1.6; minor JSON/HTML/CSS formatting changes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I hopped through lint and tidy lines,

Regex maps to match model signs,
CI gates click, the builds take flight,
TypeScript nods and Prettier’s bright,
A carrot cheer for cleaner mines 🥕

✨ 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 v616

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: 9

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (2)
src/components/tables/ProviderEfficiency.tsx (1)

91-104: ⚠️ Potential issue | 🟠 Major

Sortable headers need keyboard and ARIA support.

<th onClick> is mouse-only. Use a button inside the header and add aria-sort to the <th> for assistive tech.

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

In `@src/components/tables/ProviderEfficiency.tsx` around lines 91 - 104, The
SortHeader <th> currently uses onClick (mouse-only); move the interactive
handler to a <button> inside SortHeader (call handleSort(field) from the button)
and remove onClick from the <th>; add aria-sort on the <th> so assistive tech
knows the column state (e.g., aria-sort={sortKey === field ? 'ascending' :
'none'}), ensure the button has an accessible label/focus styles and that
ArrowUpDown remains inside the button; keep references to SortHeader,
handleSort, sortKey and field when making the changes.
src/components/features/settings/SettingsModal.tsx (1)

117-127: ⚠️ Potential issue | 🟠 Major

Dragging downward reorders to the wrong slot.

targetIndex is captured before the source item is removed. When sourceIndex < targetIndex, dropping A onto C inserts it after C instead of at C's position.

Suggested fix
 function reorderSections(
   order: DashboardSectionOrder,
   sourceId: DashboardSectionOrder[number],
   targetId: DashboardSectionOrder[number],
 ) {
   if (sourceId === targetId) return order

   const sourceIndex = order.indexOf(sourceId)
   const targetIndex = order.indexOf(targetId)

   if (sourceIndex < 0 || targetIndex < 0) {
     return order
   }

   const next = [...order]
   const [moved] = next.splice(sourceIndex, 1)
   if (!moved) return order
-  next.splice(targetIndex, 0, moved)
+  const insertionIndex = sourceIndex < targetIndex ? targetIndex - 1 : targetIndex
+  next.splice(insertionIndex, 0, moved)
   return next
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/features/settings/SettingsModal.tsx` around lines 117 - 127,
The bug is that targetIndex is calculated from order before removing the source,
so when sourceIndex < targetIndex the subsequent splice inserts the moved item
after the intended slot; fix by recalculating or adjusting targetIndex after
removing the source: after creating next and doing next.splice(sourceIndex, 1)
(the [moved] removal), either set targetIndex = next.indexOf(targetId) or if you
prefer minimal change, decrement targetIndex by 1 when sourceIndex <
targetIndex, then call next.splice(targetIndex, 0, moved); update the logic
around sourceIndex, targetIndex, next, and moved accordingly.
🟡 Minor comments (16)
src/components/charts/CorrelationAnalysis.tsx-88-90 (1)

88-90: ⚠️ Potential issue | 🟡 Minor

Fix inconsistent undefined check for tokens.

Line 89 uses a truthy check (point.tokens ?) which will incorrectly display '–' when point.tokens is 0. This is inconsistent with the explicit undefined checks used for point.requests (lines 79, 110) and point.cacheRate (line 98).

🐛 Proposed fix to use explicit undefined check
-              <span className="font-mono font-medium">
-                {point.tokens ? formatTokens(point.tokens) : '–'}
-              </span>
+              <span className="font-mono font-medium">
+                {point.tokens !== undefined ? formatTokens(point.tokens) : '–'}
+              </span>
🤖 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 88 - 90, Change
the truthy check for tokens to an explicit undefined check so zero values render
correctly: in the CorrelationAnalysis component replace the conditional on
point.tokens (currently using point.tokens ? ...) with an explicit typeof or !==
undefined check (matching how point.requests and point.cacheRate are handled) so
formatTokens(point.tokens) is called for 0 and only '–' is shown when tokens is
actually undefined.
src/components/cards/SecondaryMetrics.tsx-63-66 (1)

63-66: ⚠️ Potential issue | 🟡 Minor

Localize the hardcoded σ Req suffix in subtitle text.

This introduces untranslated UI text in localized flows.

Suggested fix
-  const medianSubtitle =
-    median !== null && metrics.avgDailyCost > 0
-      ? `${t('metricCards.secondary.vsAverage', { direction: median < metrics.avgDailyCost ? '↓' : '↑', value: Math.abs(((median - metrics.avgDailyCost) / metrics.avgDailyCost) * 100).toFixed(0) })} · σ Req ${Math.round(metrics.requestVolatility)}`
-      : null
+  const medianSubtitle =
+    median !== null && metrics.avgDailyCost > 0
+      ? t('metricCards.secondary.medianSubtitle', {
+          direction: median < metrics.avgDailyCost ? '↓' : '↑',
+          value: Math.abs(((median - metrics.avgDailyCost) / metrics.avgDailyCost) * 100).toFixed(0),
+          volatility: Math.round(metrics.requestVolatility),
+        })
+      : null
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/cards/SecondaryMetrics.tsx` around lines 63 - 66, The subtitle
construction in medianSubtitle currently appends a hardcoded "· σ Req" suffix
(using metrics.requestVolatility), which is not localized; update the
translation usage to include that suffix (e.g., add a new i18n key such as
'metricCards.secondary.vsAverageWithStd' or include a placeholder in
'metricCards.secondary.vsAverage') and pass the rounded volatility value as a
parameter instead of concatenating "· σ Req" in code — modify the medianSubtitle
expression to call t(...) with the new key/placeholder and provide { direction,
value, requestVolatility: Math.round(metrics.requestVolatility) } so the entire
subtitle is translatable.
src/components/cards/MonthMetrics.tsx-180-187 (1)

180-187: ⚠️ Potential issue | 🟡 Minor

Localize the hardcoded “/ Request” suffix.

This user-facing English fragment bypasses i18n and can leak untranslated text.

Suggested fix
-                  insight={`${formatCurrency(agg.totalCost / agg.requestCount)} / Request`}
+                  insight={t('metricCards.month.costPerRequest', {
+                    value: formatCurrency(agg.totalCost / agg.requestCount),
+                  })}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/cards/MonthMetrics.tsx` around lines 180 - 187, The hardcoded
" / Request" suffix should be localized: in MonthMetrics.tsx (where
FormattedValue is used) replace the string interpolation that appends " /
Request" with a call to the i18n translator (t) and concatenate the formatted
currency with a new translation key (e.g. metricCards.month.perRequest) so the
suffix is translatable; add the new key to your locale files with appropriate
translations and ensure you use the existing t function in the component when
building the insight prop for FormattedValue.
src/components/tables/ModelEfficiency.tsx-310-313 (1)

310-313: ⚠️ Potential issue | 🟡 Minor

Remove cursor-pointer from non-clickable rows.

The row styling suggests click behavior, but no row action exists.

💡 Suggested fix
-                  className="border-b border-border/50 even:bg-muted/5 hover:bg-muted/10 transition-colors cursor-pointer"
+                  className="border-b border-border/50 even:bg-muted/5 hover:bg-muted/10 transition-colors"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/tables/ModelEfficiency.tsx` around lines 310 - 313, The table
row in the ModelEfficiency component is styled with "cursor-pointer" but has no
click behavior; remove "cursor-pointer" from the className on the <tr> element
(the row rendered with key={model.name}) in ModelEfficiency.tsx so non-clickable
rows do not imply interactivity.
src/components/charts/ChartCard.tsx-247-257 (1)

247-257: ⚠️ Potential issue | 🟡 Minor

Hardcoded labels introduce mixed-language UI in an otherwise translated component.

Gesamt and Datenpunkte should go through i18n keys like the rest of the card labels to keep locale consistency.

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

In `@src/components/charts/ChartCard.tsx` around lines 247 - 257, The ChartCard
component contains hardcoded German labels "Gesamt" and "Datenpunkte" which
breaks localization; replace these literals with the app's i18n calls (the same
translation helper used elsewhere in this component, e.g., the local
t/useTranslations) so the labels use keys like "charts.total" and
"charts.datapoints" (or your project's existing keys), keeping the surrounding
markup (the divs, classes, and fmt(stats.total)) intact; also add the new keys
to the translation files for supported locales so strings render correctly.
RELEASING.md-61-64 (1)

61-64: ⚠️ Potential issue | 🟡 Minor

Indent the verification command bullets under step 12.

These bullets should be nested under the numbered item to avoid markdown rendering them as a separate top-level list.

💡 Suggested doc fix
-12. verifies:
-
-- `npx --yes `@roastcodes/ttdash`@<version> --help`
-- `bunx `@roastcodes/ttdash`@<version> --help`
+12. verifies:
+   - `npx --yes `@roastcodes/ttdash`@<version> --help`
+   - `bunx `@roastcodes/ttdash`@<version> --help`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@RELEASING.md` around lines 61 - 64, Indent the two verification lines so they
are nested under the numbered item "12. verifies:" by adding a consistent list
indent (e.g., four spaces or a tab) before each bullet (`- `npx --yes
`@roastcodes/ttdash`@<version> --help`` and `- `bunx `@roastcodes/ttdash`@<version>
--help``) so they render as sub-bullets under step 12 rather than a separate
top-level list.
src/components/features/forecast/CostForecast.tsx-186-190 (1)

186-190: ⚠️ Potential issue | 🟡 Minor

Remove accidental ~ text node from forecast value rendering.

Line 188 currently includes a leading ~, which will render in the UI before the currency value.

Proposed fix
-        value={
-          <>
-            ~<FormattedValue value={forecastTotal} type="currency" />
-          </>
-        }
+        value={<FormattedValue value={forecastTotal} type="currency" />}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/features/forecast/CostForecast.tsx` around lines 186 - 190,
The CostForecast component is rendering an accidental literal tilde text node in
the value prop (the fragment containing "~<FormattedValue .../>"); remove the
leading "~" text so the value prop only renders the FormattedValue (and you can
also drop the unnecessary fragment if present) in the JSX where value is set in
CostForecast.
CHANGELOG.md-5-5 (1)

5-5: ⚠️ Potential issue | 🟡 Minor

Use Unreleased or the actual release date here.

This entry is dated 2026-04-13, but the PR is still open on 2026-04-12. Future-dating the release makes the changelog inaccurate until the tag is actually cut.

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

In `@CHANGELOG.md` at line 5, The changelog header "## [6.1.6] - 2026-04-13" is
future-dated; update that header to either use "Unreleased" or the actual
release date (e.g., "## [6.1.6] - Unreleased") so the entry reflects the PR's
current state and is not dated ahead of the tag being cut.
src/components/features/drill-down/DrillDownModal.tsx-149-152 (1)

149-152: ⚠️ Potential issue | 🟡 Minor

Guard zero-token days before dividing by day.totalTokens.

If a day has 0 tokens, the $ /1M card renders Infinity/NaN, and the legend percentages render NaN% even though the stacked bar is hidden. Please short-circuit these displays to 0 or .

Also applies to: 253-288

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

In `@src/components/features/drill-down/DrillDownModal.tsx` around lines 149 -
152, In DrillDownModal, guard all divisions by day.totalTokens (e.g., the
FormattedValue calculation value={day.totalCost / (day.totalTokens /
1_000_000)}) and any legend percentage computations so they short-circuit when
day.totalTokens is 0 or falsy; replace the resulting value with 0 or a
placeholder like '–' (or pass null/undefined to FormattedValue) instead of
performing the division. Locate usages of day.totalCost and day.totalTokens in
the component (including the $/1M card and the legend percentage calculations)
and wrap them with a conditional such as day.totalTokens ? (day.totalCost /
(day.totalTokens / 1_000_000)) : fallback, and do the same for each percent
formula so Infinity/NaN are never passed to rendering. Ensure the fallback type
matches what FormattedValue and the legend expect (number or placeholder
string).
src/lib/formatters.ts-124-127 (1)

124-127: ⚠️ Potential issue | 🟡 Minor

Avoid defaulting malformed month strings to January.

This change makes inputs like '2026' format as January 2026, which hides an invalid period key behind a plausible label. It would be safer to validate YYYY-MM explicitly and return an empty string or the original input when parsing fails.

🩹 Proposed fix
 export function formatMonthYear(dateStr: string): string {
-  const [year = '0', month = '1'] = dateStr.split('-')
+  if (!/^\d{4}-\d{2}$/.test(dateStr)) return ''
+  const [year, month] = dateStr.split('-')
   const date = new Date(parseInt(year, 10), parseInt(month, 10) - 1)
   return date.toLocaleDateString(getCurrentLocale(), { month: 'long', year: 'numeric' })
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/formatters.ts` around lines 124 - 127, The formatMonthYear function
currently defaults a missing month to January (making "2026" format as Jan
2026); instead, validate the input explicitly (e.g., with a YYYY-MM regex)
before parsing and if the input doesn't match return an empty string or the
original input as desired; update formatMonthYear to check the pattern for year
and month, avoid using default values when splitting, and only construct a Date
and call toLocaleDateString when the validation passes.
src/components/tables/RecentDays.tsx-176-180 (1)

176-180: ⚠️ Potential issue | 🟡 Minor

Keep the peak cost on the shared currency formatter.

This hardcodes USD and bypasses the locale-aware formatting used everywhere else in the table, so the peak row can drift from the rest of the UI. Reuse formatCurrency(...) or FormattedValue here as well.

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

In `@src/components/tables/RecentDays.tsx` around lines 176 - 180, The peak row
currently hardcodes "USD" and bypasses the app's locale-aware formatting;
replace the literal `${summary.top.totalCost.toFixed(2)} USD` with the shared
currency formatter (e.g., call formatCurrency(summary.top.totalCost,
summary.top.currency?) or render <FormattedValue value={summary.top.totalCost}
currency={...}>) so the peak cost uses the same locale/currency logic as the
rest of the table; locate this in the RecentDays component where summary.top is
accessed (near formatDate(summary.top.date)) and use the existing formatCurrency
or FormattedValue helper consistent with other rows.
src/components/layout/FilterBar.tsx-239-257 (1)

239-257: ⚠️ Potential issue | 🟡 Minor

Localize the calendar navigation labels.

aria-label="Previous month" / "Next month" stay English even when the rest of the picker is translated, so screen-reader users get a mixed-language control. These should come from t(...) like the other date-picker copy.

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

In `@src/components/layout/FilterBar.tsx` around lines 239 - 257, Replace the
hard-coded English aria labels for the month navigation in FilterBar.tsx so they
use the i18n translation function (same one used elsewhere in this component)
instead of static strings; update the left button aria-label and the right
button aria-label to call t(...) (e.g., t('previousMonth') / t('nextMonth') or
your existing calendar keys) where the buttons are defined alongside the
setDisplayMonth callbacks and monthLabel so screen readers receive localized
labels.
src/components/features/limits/ProviderLimitsSection.tsx-71-80 (1)

71-80: ⚠️ Potential issue | 🟡 Minor

Keep the badge localized and preserve subscription ratios above 100%.

The new helper hardcodes Limit / Sub / Offen, and the caller now clamps subscriptionProgress to 100 before passing it in. That means providers above break-even all render as 100% Sub, even though the helper clearly expects a higher cap. Use translated badge labels and pass the raw ratio for the badge while keeping a separately clamped value for the progress bar width.

Also applies to: 299-302, 333-333

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

In `@src/components/features/limits/ProviderLimitsSection.tsx` around lines 71 -
80, formatLimitBadge currently hardcodes English/German strings and assumes
subscriptionProgress is clamped; update it to use the app i18n/localization
instead of literal "Limit"/"Sub"/"Offen" and stop clamping the subscription
ratio inside the helper so values >100% are preserved (accept the raw
subscriptionProgress number and render Math.min(...) only where the progress bar
width is computed). Locate the function formatLimitBadge and change badge labels
to use the translation keys (e.g., t('limits.badge.limit'),
t('limits.badge.subscription'), t('limits.badge.off')) and ensure any clamping
to 100 for UI width happens in the progress bar rendering code (the caller code
around the progress bar, not formatLimitBadge); also apply the same change
pattern for the other occurrences referenced (around the other similar badge
lines).
src/components/features/limits/ProviderLimitsSection.tsx-923-939 (1)

923-939: ⚠️ Potential issue | 🟡 Minor

Avoid pluralizing translated legend labels with string concatenation.

${t(...)}s only works in English. In other locales it will produce awkward or incorrect legend text. These legend names should come from dedicated translation keys instead of appending 's' in code.

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

In `@src/components/features/limits/ProviderLimitsSection.tsx` around lines 923 -
939, The legend labels in ProviderLimitsSection.tsx currently build plurals by
appending "s" to t('limits.tracks.limit') and t('limits.tracks.subscription')
inside the Line components (dataKey "totalLimit" and "totalSubscriptions"),
which breaks i18n; replace these string-concatenated names with proper
translated strings—either add dedicated keys like t('limits.tracks.limits') and
t('limits.tracks.subscriptions') or use the i18n pluralization form
t('limits.tracks.limit', { count: n }) (or analogous plural key for
subscription) and pass an appropriate count/variable so the legend names come
from translation keys instead of using `${t(...)}s`. Ensure you update both Line
name props referenced above.
src/lib/help-content.ts-245-255 (1)

245-255: ⚠️ Potential issue | 🟡 Minor

Return undefined for missing keys in the proxy descriptor trap.

getOwnPropertyDescriptor currently reports every queried key as an own property, even when it does not exist in the selected help map. That breaks normal Object.hasOwn() / hasOwnProperty() semantics on these exported proxies.

Suggested fix
 function dynamicMap<const T extends Record<string, string>>(selector: () => T): T {
   return new Proxy({} as T, {
     get: (_, key) => Reflect.get(selector(), key),
     has: (_, key) => key in selector(),
     ownKeys: () => Reflect.ownKeys(selector()),
-    getOwnPropertyDescriptor: (_, key) => ({
-      value: Reflect.get(selector(), key),
-      enumerable: true,
-      configurable: true,
-    }),
+    getOwnPropertyDescriptor: (_, key) =>
+      key in selector()
+        ? {
+            value: Reflect.get(selector(), key),
+            enumerable: true,
+            configurable: true,
+          }
+        : undefined,
   })
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/help-content.ts` around lines 245 - 255, The getOwnPropertyDescriptor
trap in dynamicMap incorrectly returns a descriptor for every key; modify it to
first fetch the current map (const map = selector()) and check ownership with
Object.prototype.hasOwnProperty.call(map, key) (or return undefined for non-own
keys), returning undefined if the property does not exist, otherwise return the
existing descriptor ({ value: Reflect.get(map, key), enumerable: true,
configurable: true }); keep the other traps as-is and use the same selector()
result to avoid calling selector() multiple times.
src/lib/data-transforms.ts-35-47 (1)

35-47: ⚠️ Potential issue | 🟡 Minor

modelsUsed loses set semantics after filtering.

This rebuild copies every breakdown name into modelsUsed, so duplicate/aliased breakdown rows inflate modelsUsed.length. computeMetrics uses that length for avgModelsPerEntry, so filtered views can overreport the metric.

Suggested fix
   return {
     ...day,
     totalCost,
     totalTokens:
       inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens + thinkingTokens,
@@
     thinkingTokens,
     requestCount,
     modelBreakdowns: filteredBreakdowns,
-    modelsUsed: filteredBreakdowns.map((mb) => mb.modelName),
+    modelsUsed: [...new Set(filteredBreakdowns.map((mb) => normalizeModelName(mb.modelName)))],
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/data-transforms.ts` around lines 35 - 47, The modelsUsed array
returned from the transform currently maps filteredBreakdowns to a plain array
allowing duplicates, which inflates counts used by computeMetrics
(avgModelsPerEntry); change the return so modelsUsed is deduplicated (preserve
order if needed) from filteredBreakdowns — e.g., derive modelsUsed from
filteredBreakdowns.map(mb => mb.modelName) and convert to a unique collection
(Set or Array.from(new Set(...))) before returning, updating the symbol
modelsUsed in this function and ensuring computeMetrics still consumes it as an
array.
🧹 Nitpick comments (8)
src/index.css (1)

3-3: Note: Static analysis false positive.

The stylelint error flagging @theme as an unknown at-rule is a false positive. @theme is a valid Tailwind CSS v4 directive and was present before this PR. Consider updating the stylelint configuration to recognize Tailwind v4 syntax if this warning causes confusion.

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

In `@src/index.css` at line 3, The stylelint warning about the `@theme` at-rule is a
false positive for Tailwind CSS v4; update the stylelint configuration (e.g.,
the rule that validates unknown at-rules) to recognize Tailwind v4 directives so
`@theme` is allowed. Modify the stylelint config (the rule like
"at-rules/no-unknown" or add the tailwind plugin/ignore at-rules list) to
include "theme" or enable the official Tailwind parser/plugin so the `@theme`
at-rule used in src/index.css is not flagged.
src/components/charts/CorrelationAnalysis.tsx (2)

285-291: Consider extracting nested ternaries to helper functions.

The three-level nested ternaries for correlation interpretation are logically correct but could be more readable as helper functions or simple if-else chains.

♻️ Example refactor using helper function
function getCorrelationInterpretation(
  correlation: number,
  type: 'requestCost' | 'cacheEfficiency'
): string {
  const { t } = useTranslation()
  
  if (type === 'requestCost') {
    if (correlation >= 0.6) return t('charts.correlation.strongRequestCost')
    if (correlation >= 0.3) return t('charts.correlation.mediumRequestCost')
    return t('charts.correlation.weakRequestCost')
  } else {
    if (correlation <= -0.3) return t('charts.correlation.negativeCache')
    if (correlation < 0.2) return t('charts.correlation.neutralCache')
    return t('charts.correlation.positiveCache')
  }
}

Then use: footer={getCorrelationInterpretation(requestCostCorrelation, 'requestCost')}

Also applies to: 305-311

🤖 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 285 - 291,
Extract the nested ternary logic used in the footer props into a small helper
function (e.g., getCorrelationInterpretation) that takes the correlation number
and a type flag ('requestCost' | 'cacheEfficiency') and returns the appropriate
translated string using the existing useTranslation hook or by accepting t as an
argument; replace the inline ternaries that compute footer for
requestCostCorrelation and the one for cacheEfficiencyCorrelation with calls to
this helper (reference requestCostCorrelation, cacheEfficiencyCorrelation, and
the component CorrelationAnalysis or its rendering of footer).

170-177: Consider simplifying the conditional tickFormatter spread.

React and Recharts handle undefined props gracefully, so the conditional spread {...(xTickFormatter ? { tickFormatter: xTickFormatter } : {})} adds unnecessary complexity. A direct prop assignment would be more idiomatic.

♻️ Proposed simplification
 <XAxis
   type="number"
   dataKey="x"
   stroke={CHART_COLORS.axis}
   fontSize={10}
   tickLine={false}
   name={xAxisName}
-  {...(xTickFormatter ? { tickFormatter: xTickFormatter } : {})}
+  tickFormatter={xTickFormatter}
 />
🤖 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 170 - 177, The
XAxis prop uses an unnecessary conditional spread to set tickFormatter; simplify
by assigning tickFormatter directly (replace the {...(xTickFormatter ? {
tickFormatter: xTickFormatter } : {})} pattern with a direct tickFormatter prop)
so XAxis receives tickFormatter={xTickFormatter} and React/Recharts can handle
undefined values; locate the XAxis component in CorrelationAnalysis.tsx
(symbols: XAxis, xTickFormatter, xAxisName) and update accordingly.
src/components/features/anomaly/AnomalyDetection.tsx (1)

65-67: Prefer non-mutating sort in render path.

anomalies.sort(...) mutates the memoized array. Use a copied array before sorting to keep render data flow immutable.

Suggested diff
-          {anomalies
-            .sort((a, b) => b.totalCost - a.totalCost)
+          {[...anomalies]
+            .sort((a, b) => b.totalCost - a.totalCost)
             .map((day) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/features/anomaly/AnomalyDetection.tsx` around lines 65 - 67,
The render currently calls anomalies.sort(...), which mutates the memoized
anomalies array; instead create a non-mutating copy before sorting (e.g., using
slice() or spread) and call sort on that copy so the original anomalies value
from the hook/memo isn't mutated; update the expression in AnomalyDetection
where anomalies.sort((a,b) => b.totalCost - a.totalCost).map(...) is used to use
a copied array (e.g., [...anomalies] or anomalies.slice()) before .sort and then
.map.
src/components/ui/card.tsx (1)

5-20: Prevent accidental override of Card motion defaults.

Line 18 spreads {...props} after the motion.div animation props, allowing consumers to unintentionally override initial, whileInView, viewport, or transition. While no current callers do this, the safer pattern is to spread consumer props before the built-in animation props to preserve the animation contract.

Suggested fix
 const Card = React.forwardRef<HTMLDivElement, CardProps>(({ className, ...props }, ref) => (
   <motion.div
     ref={ref}
+    {...props}
     initial={{ opacity: 0, y: 14 }}
     whileInView={{ opacity: 1, y: 0 }}
     viewport={{ once: true, amount: 0.15 }}
     transition={{ duration: 0.35, ease: 'easeOut' }}
     className={cn(
       'relative rounded-xl border border-border/50 bg-card/80 backdrop-blur-xl text-card-foreground shadow-[var(--shadow-card)] transition-all duration-300 hover:shadow-[var(--shadow-card-hover)] hover:border-border/80',
       className,
     )}
-    {...props}
   />
 ))
🤖 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 5 - 20, The Card component currently
spreads {...props} after the motion.div animation props which allows consumers
to override built-in animation settings; move the spread of consumer props so
they are applied before the animation props (i.e., spread {...props} earlier on
the motion.div and then set initial, whileInView, viewport, transition,
className, ref explicitly) to ensure Card's
initial/whileInView/viewport/transition cannot be overridden by callers while
still forwarding ref and merging className.
.github/workflows/release.yml (1)

145-147: Pin Bun to a fixed version for reproducible releases.

bun-version: latest can cause release results to drift over time and introduce non-deterministic failures. Use a specific version instead.

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

In @.github/workflows/release.yml around lines 145 - 147, The workflow currently
uses the setup-bun action with bun-version: latest which makes releases
non-reproducible; change the bun-version value to a specific fixed Bun release
(e.g., a concrete semver like 1.x.y) and pin the setup action if desired (the
current uses: oven-sh/setup-bun@0c5077... may remain or be updated to a stable
tag), so update the bun-version field from "latest" to a specific version string
to ensure deterministic builds.
src/components/charts/ModelMix.tsx (1)

126-143: Avoid repeated lookup in series animation delay.

You can use the map index directly instead of models.indexOf(model) for cleaner and slightly cheaper rendering logic.

♻️ Suggested refactor
-                {models.map((model) => {
+                {models.map((model, index) => {
                   const color = getModelColor(model)
                   const id = `mix-grad-${model.replace(/[\s.]/g, '-')}`
                   return (
                     <Area
@@
-                      animationBegin={CHART_ANIMATION.stagger * (models.indexOf(model) % 5)}
+                      animationBegin={CHART_ANIMATION.stagger * (index % 5)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/charts/ModelMix.tsx` around lines 126 - 143, The animation
delay currently recomputes the model index with models.indexOf(model) inside the
models.map loop; update the mapping to use the map callback index (e.g., change
models.map((model) => { ... }) to models.map((model, idx) => { ... }) and
replace models.indexOf(model) with idx when computing animationBegin) so the
Area rendering (animationBegin property) uses the direct index and avoids
repeated lookups; keep the rest of the Area props (type, dataKey, stackId,
stroke, fill, name, isAnimationActive, animationDuration, animationEasing)
unchanged.
src/components/charts/CostByModelOverTime.tsx (1)

69-84: Use map index for animation delay in both model loops.

Same as other chart files: this avoids repeated indexOf lookups and keeps intent clearer.

♻️ Suggested refactor
-                {models.map((model) => (
+                {models.map((model, index) => (
                   <Line
@@
-                    animationBegin={CHART_ANIMATION.stagger * (models.indexOf(model) % 5)}
+                    animationBegin={CHART_ANIMATION.stagger * (index % 5)}
@@
-                {models.map((model) => (
+                {models.map((model, index) => (
                   <Line
@@
-                    animationBegin={CHART_ANIMATION.stagger * (models.indexOf(model) % 5)}
+                    animationBegin={CHART_ANIMATION.stagger * (index % 5)}

Also applies to: 136-150

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

In `@src/components/charts/CostByModelOverTime.tsx` around lines 69 - 84, The
animation delay currently computes animationBegin using models.indexOf(model)
inside the models.map, which is inefficient and brittle; update both model
iteration sites (the map that renders <Line key={`${model}_ma7`} ... /> and the
other models.map around lines 136-150) to use the map callback index parameter
(e.g., (model, idx) => ...) and replace models.indexOf(model) with that idx when
calculating animationBegin (still using CHART_ANIMATION.stagger * (idx % 5));
keep all other props (dataKey, key, stroke, etc.) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Around line 36-39: The npm scripts "verify" and "verify:release" currently
invoke tsc using an explicit ./node_modules/.bin/tsc path; remove the hardcoded
path and call tsc directly in both script definitions so npm's PATH resolution
is used (update the "verify" script and the "verify:release" script to replace
"./node_modules/.bin/tsc --noEmit" with "tsc --noEmit").

In `@server/model-normalization.json`:
- Around line 3-10: The alias regexes are consuming the trailing hyphen so
suffixed variants (e.g., gpt-5-4-codex) get flattened; update each pattern to
assert the token boundary with a lookahead instead of consuming the hyphen:
replace occurrences of ($|-) with (?=$|-) (and for the gpt-5 pattern keep the
existing negative-digit check as (?=$|-(?!\d))). Apply this change to the listed
patterns (e.g., the patterns for "gpt-5-4", "gpt-5", "opus-4-6", "opus-4-5",
"sonnet-4-6", "sonnet-4-5", "haiku-4-5", "gemini-3-flash-preview") so aliases
match the token but do not remove subsequent suffixes.

In `@src/components/cards/TodayMetrics.tsx`:
- Around line 63-67: The branch in requestsSubtitle reads
today.modelsUsed.length and divides by it, which can throw if modelsUsed is
null/undefined; update the logic in the requestsSubtitle computation
(referencing today and requestsSubtitle) to use a null-safe count (e.g., compute
const modelsCount = Array.isArray(today.modelsUsed) ? today.modelsUsed.length :
0 or use today.modelsUsed?.length ?? 0) and guard the division with modelsCount
> 0 before computing the average and before formatting to avoid NaN/throwing;
ensure the ternary condition uses modelsCount instead of today.modelsUsed.length
and handle zero counts to keep the existing fallback behavior.

In `@src/components/charts/ChartCard.tsx`:
- Around line 33-49: The CSV export breaks when cell text contains commas,
quotes or newlines because stringifyCsvCell returns raw strings that are later
joined with commas; update stringifyCsvCell to CSV-escape and quote values by
converting the value to a string (as currently done), then wrap it in double
quotes and replace any " with "" (double them) so internal quotes are escaped
and commas/newlines are preserved inside the quoted field; apply the same
quoting/escaping logic to the other export path referenced around the join(',')
usage (the block at 155-158) so all CSV rows use consistent CSV-quoted cells.

In `@src/components/features/settings/SettingsModal.tsx`:
- Around line 200-203: The code builds nextProviderLimits from the existing
limits, which preserves providers that are no longer in limitProviders; change
the construction so nextProviderLimits only contains entries for the current
limitProviders (e.g., initialize nextProviderLimits as an empty object and then
add for each provider in limitProviders: nextProviderLimits[provider] =
limitDraft[provider] ?? { ...DEFAULT_PROVIDER_LIMIT_CONFIG }). Update the logic
in SettingsModal where nextProviderLimits, limits, limitProviders, and
limitDraft are used so a save or reset replaces the provider map entirely (and
thus clears stale entries) rather than merging onto the old limits.

In `@src/components/tables/ModelEfficiency.tsx`:
- Around line 138-150: The SortHeader component currently attaches onClick to
the <th>, which is not keyboard-accessible and does not expose sort state;
update SortHeader to render a <th> containing a <button> that receives the
onClick handler (handleSort) and visual classes (instead of the <th>), make the
button focusable, and set aria-sort on the <th> (or the button if you prefer)
based on sortKey and the passed field (use 'none' when sortKey !== field, and
'ascending' or 'descending' when it matches — map your existing sort direction
state if you have one, or add one alongside sortKey). Ensure unique symbols
referenced: SortHeader, SortKey, sortKey, handleSort, and ArrowUpDown.

In `@src/components/tables/RecentDays.tsx`:
- Around line 289-358: The sortable column headers (those using sortKey and
handleSort and showing ArrowUpDown) must use a real button inside the th for
keyboard focus and activation; move the onClick from the th into a <button>
placed around the label/ArrowUpDown (preserve the cn classes for styling/focus)
and call handleSort('date'|'cost'|'tokens'|'costPerM') from the button; also add
aria-sort on the th (values 'none' or 'ascending'/'descending' derived from
sortKey and your sortDirection state) so screen readers announce sort state, and
ensure the button has an accessible name (use the translated label or
aria-label) while keeping ArrowUpDown as a decorative icon.

In `@src/lib/auto-import.ts`:
- Around line 24-31: parseEventData currently throws on malformed JSON and
accepts primitives/arrays which break downstream handlers; update parseEventData
to catch JSON.parse errors and return null on parse failure, then validate the
parsed value is a non-null plain object (reject primitives and arrays using
typeof === 'object' && value !== null && !Array.isArray(value)) before casting
to T; keep the MessageEvent/type checks, and only return the cast value when
these guards pass to ensure handlers can safely access properties like message
or line.

In `@src/lib/formatters.ts`:
- Around line 18-29: coerceNumber currently maps invalid inputs (undefined, NaN,
±Infinity) to 0 which causes bad data to display as legitimate zero values;
change coerceNumber to return a nullable type (e.g., number | null) and return
null for any non-finite or non-parsable inputs instead of 0, then update the
axis/value formatter call sites that consume coerceNumber to treat null as "no
value" (render blank or skip the label) rather than formatting it as $0.00/0%;
reference the coerceNumber function and the axis/value formatter call sites so
they consistently handle nulls.

---

Outside diff comments:
In `@src/components/features/settings/SettingsModal.tsx`:
- Around line 117-127: The bug is that targetIndex is calculated from order
before removing the source, so when sourceIndex < targetIndex the subsequent
splice inserts the moved item after the intended slot; fix by recalculating or
adjusting targetIndex after removing the source: after creating next and doing
next.splice(sourceIndex, 1) (the [moved] removal), either set targetIndex =
next.indexOf(targetId) or if you prefer minimal change, decrement targetIndex by
1 when sourceIndex < targetIndex, then call next.splice(targetIndex, 0, moved);
update the logic around sourceIndex, targetIndex, next, and moved accordingly.

In `@src/components/tables/ProviderEfficiency.tsx`:
- Around line 91-104: The SortHeader <th> currently uses onClick (mouse-only);
move the interactive handler to a <button> inside SortHeader (call
handleSort(field) from the button) and remove onClick from the <th>; add
aria-sort on the <th> so assistive tech knows the column state (e.g.,
aria-sort={sortKey === field ? 'ascending' : 'none'}), ensure the button has an
accessible label/focus styles and that ArrowUpDown remains inside the button;
keep references to SortHeader, handleSort, sortKey and field when making the
changes.

---

Minor comments:
In `@CHANGELOG.md`:
- Line 5: The changelog header "## [6.1.6] - 2026-04-13" is future-dated; update
that header to either use "Unreleased" or the actual release date (e.g., "##
[6.1.6] - Unreleased") so the entry reflects the PR's current state and is not
dated ahead of the tag being cut.

In `@RELEASING.md`:
- Around line 61-64: Indent the two verification lines so they are nested under
the numbered item "12. verifies:" by adding a consistent list indent (e.g., four
spaces or a tab) before each bullet (`- `npx --yes `@roastcodes/ttdash`@<version>
--help`` and `- `bunx `@roastcodes/ttdash`@<version> --help``) so they render as
sub-bullets under step 12 rather than a separate top-level list.

In `@src/components/cards/MonthMetrics.tsx`:
- Around line 180-187: The hardcoded " / Request" suffix should be localized: in
MonthMetrics.tsx (where FormattedValue is used) replace the string interpolation
that appends " / Request" with a call to the i18n translator (t) and concatenate
the formatted currency with a new translation key (e.g.
metricCards.month.perRequest) so the suffix is translatable; add the new key to
your locale files with appropriate translations and ensure you use the existing
t function in the component when building the insight prop for FormattedValue.

In `@src/components/cards/SecondaryMetrics.tsx`:
- Around line 63-66: The subtitle construction in medianSubtitle currently
appends a hardcoded "· σ Req" suffix (using metrics.requestVolatility), which is
not localized; update the translation usage to include that suffix (e.g., add a
new i18n key such as 'metricCards.secondary.vsAverageWithStd' or include a
placeholder in 'metricCards.secondary.vsAverage') and pass the rounded
volatility value as a parameter instead of concatenating "· σ Req" in code —
modify the medianSubtitle expression to call t(...) with the new key/placeholder
and provide { direction, value, requestVolatility:
Math.round(metrics.requestVolatility) } so the entire subtitle is translatable.

In `@src/components/charts/ChartCard.tsx`:
- Around line 247-257: The ChartCard component contains hardcoded German labels
"Gesamt" and "Datenpunkte" which breaks localization; replace these literals
with the app's i18n calls (the same translation helper used elsewhere in this
component, e.g., the local t/useTranslations) so the labels use keys like
"charts.total" and "charts.datapoints" (or your project's existing keys),
keeping the surrounding markup (the divs, classes, and fmt(stats.total)) intact;
also add the new keys to the translation files for supported locales so strings
render correctly.

In `@src/components/charts/CorrelationAnalysis.tsx`:
- Around line 88-90: Change the truthy check for tokens to an explicit undefined
check so zero values render correctly: in the CorrelationAnalysis component
replace the conditional on point.tokens (currently using point.tokens ? ...)
with an explicit typeof or !== undefined check (matching how point.requests and
point.cacheRate are handled) so formatTokens(point.tokens) is called for 0 and
only '–' is shown when tokens is actually undefined.

In `@src/components/features/drill-down/DrillDownModal.tsx`:
- Around line 149-152: In DrillDownModal, guard all divisions by day.totalTokens
(e.g., the FormattedValue calculation value={day.totalCost / (day.totalTokens /
1_000_000)}) and any legend percentage computations so they short-circuit when
day.totalTokens is 0 or falsy; replace the resulting value with 0 or a
placeholder like '–' (or pass null/undefined to FormattedValue) instead of
performing the division. Locate usages of day.totalCost and day.totalTokens in
the component (including the $/1M card and the legend percentage calculations)
and wrap them with a conditional such as day.totalTokens ? (day.totalCost /
(day.totalTokens / 1_000_000)) : fallback, and do the same for each percent
formula so Infinity/NaN are never passed to rendering. Ensure the fallback type
matches what FormattedValue and the legend expect (number or placeholder
string).

In `@src/components/features/forecast/CostForecast.tsx`:
- Around line 186-190: The CostForecast component is rendering an accidental
literal tilde text node in the value prop (the fragment containing
"~<FormattedValue .../>"); remove the leading "~" text so the value prop only
renders the FormattedValue (and you can also drop the unnecessary fragment if
present) in the JSX where value is set in CostForecast.

In `@src/components/features/limits/ProviderLimitsSection.tsx`:
- Around line 71-80: formatLimitBadge currently hardcodes English/German strings
and assumes subscriptionProgress is clamped; update it to use the app
i18n/localization instead of literal "Limit"/"Sub"/"Offen" and stop clamping the
subscription ratio inside the helper so values >100% are preserved (accept the
raw subscriptionProgress number and render Math.min(...) only where the progress
bar width is computed). Locate the function formatLimitBadge and change badge
labels to use the translation keys (e.g., t('limits.badge.limit'),
t('limits.badge.subscription'), t('limits.badge.off')) and ensure any clamping
to 100 for UI width happens in the progress bar rendering code (the caller code
around the progress bar, not formatLimitBadge); also apply the same change
pattern for the other occurrences referenced (around the other similar badge
lines).
- Around line 923-939: The legend labels in ProviderLimitsSection.tsx currently
build plurals by appending "s" to t('limits.tracks.limit') and
t('limits.tracks.subscription') inside the Line components (dataKey "totalLimit"
and "totalSubscriptions"), which breaks i18n; replace these string-concatenated
names with proper translated strings—either add dedicated keys like
t('limits.tracks.limits') and t('limits.tracks.subscriptions') or use the i18n
pluralization form t('limits.tracks.limit', { count: n }) (or analogous plural
key for subscription) and pass an appropriate count/variable so the legend names
come from translation keys instead of using `${t(...)}s`. Ensure you update both
Line name props referenced above.

In `@src/components/layout/FilterBar.tsx`:
- Around line 239-257: Replace the hard-coded English aria labels for the month
navigation in FilterBar.tsx so they use the i18n translation function (same one
used elsewhere in this component) instead of static strings; update the left
button aria-label and the right button aria-label to call t(...) (e.g.,
t('previousMonth') / t('nextMonth') or your existing calendar keys) where the
buttons are defined alongside the setDisplayMonth callbacks and monthLabel so
screen readers receive localized labels.

In `@src/components/tables/ModelEfficiency.tsx`:
- Around line 310-313: The table row in the ModelEfficiency component is styled
with "cursor-pointer" but has no click behavior; remove "cursor-pointer" from
the className on the <tr> element (the row rendered with key={model.name}) in
ModelEfficiency.tsx so non-clickable rows do not imply interactivity.

In `@src/components/tables/RecentDays.tsx`:
- Around line 176-180: The peak row currently hardcodes "USD" and bypasses the
app's locale-aware formatting; replace the literal
`${summary.top.totalCost.toFixed(2)} USD` with the shared currency formatter
(e.g., call formatCurrency(summary.top.totalCost, summary.top.currency?) or
render <FormattedValue value={summary.top.totalCost} currency={...}>) so the
peak cost uses the same locale/currency logic as the rest of the table; locate
this in the RecentDays component where summary.top is accessed (near
formatDate(summary.top.date)) and use the existing formatCurrency or
FormattedValue helper consistent with other rows.

In `@src/lib/data-transforms.ts`:
- Around line 35-47: The modelsUsed array returned from the transform currently
maps filteredBreakdowns to a plain array allowing duplicates, which inflates
counts used by computeMetrics (avgModelsPerEntry); change the return so
modelsUsed is deduplicated (preserve order if needed) from filteredBreakdowns —
e.g., derive modelsUsed from filteredBreakdowns.map(mb => mb.modelName) and
convert to a unique collection (Set or Array.from(new Set(...))) before
returning, updating the symbol modelsUsed in this function and ensuring
computeMetrics still consumes it as an array.

In `@src/lib/formatters.ts`:
- Around line 124-127: The formatMonthYear function currently defaults a missing
month to January (making "2026" format as Jan 2026); instead, validate the input
explicitly (e.g., with a YYYY-MM regex) before parsing and if the input doesn't
match return an empty string or the original input as desired; update
formatMonthYear to check the pattern for year and month, avoid using default
values when splitting, and only construct a Date and call toLocaleDateString
when the validation passes.

In `@src/lib/help-content.ts`:
- Around line 245-255: The getOwnPropertyDescriptor trap in dynamicMap
incorrectly returns a descriptor for every key; modify it to first fetch the
current map (const map = selector()) and check ownership with
Object.prototype.hasOwnProperty.call(map, key) (or return undefined for non-own
keys), returning undefined if the property does not exist, otherwise return the
existing descriptor ({ value: Reflect.get(map, key), enumerable: true,
configurable: true }); keep the other traps as-is and use the same selector()
result to avoid calling selector() multiple times.

---

Nitpick comments:
In @.github/workflows/release.yml:
- Around line 145-147: The workflow currently uses the setup-bun action with
bun-version: latest which makes releases non-reproducible; change the
bun-version value to a specific fixed Bun release (e.g., a concrete semver like
1.x.y) and pin the setup action if desired (the current uses:
oven-sh/setup-bun@0c5077... may remain or be updated to a stable tag), so update
the bun-version field from "latest" to a specific version string to ensure
deterministic builds.

In `@src/components/charts/CorrelationAnalysis.tsx`:
- Around line 285-291: Extract the nested ternary logic used in the footer props
into a small helper function (e.g., getCorrelationInterpretation) that takes the
correlation number and a type flag ('requestCost' | 'cacheEfficiency') and
returns the appropriate translated string using the existing useTranslation hook
or by accepting t as an argument; replace the inline ternaries that compute
footer for requestCostCorrelation and the one for cacheEfficiencyCorrelation
with calls to this helper (reference requestCostCorrelation,
cacheEfficiencyCorrelation, and the component CorrelationAnalysis or its
rendering of footer).
- Around line 170-177: The XAxis prop uses an unnecessary conditional spread to
set tickFormatter; simplify by assigning tickFormatter directly (replace the
{...(xTickFormatter ? { tickFormatter: xTickFormatter } : {})} pattern with a
direct tickFormatter prop) so XAxis receives tickFormatter={xTickFormatter} and
React/Recharts can handle undefined values; locate the XAxis component in
CorrelationAnalysis.tsx (symbols: XAxis, xTickFormatter, xAxisName) and update
accordingly.

In `@src/components/charts/CostByModelOverTime.tsx`:
- Around line 69-84: The animation delay currently computes animationBegin using
models.indexOf(model) inside the models.map, which is inefficient and brittle;
update both model iteration sites (the map that renders <Line
key={`${model}_ma7`} ... /> and the other models.map around lines 136-150) to
use the map callback index parameter (e.g., (model, idx) => ...) and replace
models.indexOf(model) with that idx when calculating animationBegin (still using
CHART_ANIMATION.stagger * (idx % 5)); keep all other props (dataKey, key,
stroke, etc.) unchanged.

In `@src/components/charts/ModelMix.tsx`:
- Around line 126-143: The animation delay currently recomputes the model index
with models.indexOf(model) inside the models.map loop; update the mapping to use
the map callback index (e.g., change models.map((model) => { ... }) to
models.map((model, idx) => { ... }) and replace models.indexOf(model) with idx
when computing animationBegin) so the Area rendering (animationBegin property)
uses the direct index and avoids repeated lookups; keep the rest of the Area
props (type, dataKey, stackId, stroke, fill, name, isAnimationActive,
animationDuration, animationEasing) unchanged.

In `@src/components/features/anomaly/AnomalyDetection.tsx`:
- Around line 65-67: The render currently calls anomalies.sort(...), which
mutates the memoized anomalies array; instead create a non-mutating copy before
sorting (e.g., using slice() or spread) and call sort on that copy so the
original anomalies value from the hook/memo isn't mutated; update the expression
in AnomalyDetection where anomalies.sort((a,b) => b.totalCost -
a.totalCost).map(...) is used to use a copied array (e.g., [...anomalies] or
anomalies.slice()) before .sort and then .map.

In `@src/components/ui/card.tsx`:
- Around line 5-20: The Card component currently spreads {...props} after the
motion.div animation props which allows consumers to override built-in animation
settings; move the spread of consumer props so they are applied before the
animation props (i.e., spread {...props} earlier on the motion.div and then set
initial, whileInView, viewport, transition, className, ref explicitly) to ensure
Card's initial/whileInView/viewport/transition cannot be overridden by callers
while still forwarding ref and merging className.

In `@src/index.css`:
- Line 3: The stylelint warning about the `@theme` at-rule is a false positive for
Tailwind CSS v4; update the stylelint configuration (e.g., the rule that
validates unknown at-rules) to recognize Tailwind v4 directives so `@theme` is
allowed. Modify the stylelint config (the rule like "at-rules/no-unknown" or add
the tailwind plugin/ignore at-rules list) to include "theme" or enable the
official Tailwind parser/plugin so the `@theme` at-rule used in src/index.css is
not flagged.
🪄 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: 89a45369-2956-410e-91b2-94bf6449329d

📥 Commits

Reviewing files that changed from the base of the PR and between 0bd7db7 and 1db830c.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (111)
  • .editorconfig
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • .prettierignore
  • .prettierrc.json
  • AGENTS.md
  • CHANGELOG.md
  • CONTRIBUTING.md
  • README.md
  • RELEASING.md
  • eslint.config.mjs
  • examples/sample-usage.json
  • index.html
  • package.json
  • scripts/report-smoke.js
  • scripts/start-test-server.js
  • scripts/verify-main-ci.js
  • scripts/verify-package.js
  • scripts/verify-registry-install.js
  • server.js
  • server/model-normalization.json
  • server/report/charts.js
  • server/report/index.js
  • server/report/utils.js
  • src/components/Dashboard.tsx
  • src/components/EmptyState.tsx
  • src/components/cards/MetricCard.tsx
  • 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/CostByModelOverTime.tsx
  • src/components/charts/CostByWeekday.tsx
  • src/components/charts/CostOverTime.tsx
  • src/components/charts/CumulativeCost.tsx
  • src/components/charts/CustomTooltip.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/features/animations/FadeIn.tsx
  • src/components/features/anomaly/AnomalyDetection.tsx
  • src/components/features/auto-import/AutoImportModal.tsx
  • src/components/features/cache-roi/CacheROI.tsx
  • src/components/features/command-palette/CommandPalette.tsx
  • src/components/features/comparison/PeriodComparison.tsx
  • src/components/features/drill-down/DrillDownModal.tsx
  • src/components/features/forecast/CostForecast.tsx
  • src/components/features/heatmap/HeatmapCalendar.tsx
  • src/components/features/help/HelpPanel.tsx
  • src/components/features/help/InfoButton.tsx
  • src/components/features/insights/UsageInsights.tsx
  • src/components/features/limits/ProviderLimitsSection.tsx
  • src/components/features/pdf-report/PDFReport.tsx
  • src/components/features/request-quality/RequestQuality.tsx
  • src/components/features/risk/ConcentrationRisk.tsx
  • src/components/features/settings/SettingsModal.tsx
  • src/components/layout/FilterBar.tsx
  • src/components/layout/Header.tsx
  • src/components/tables/ModelEfficiency.tsx
  • src/components/tables/ProviderEfficiency.tsx
  • src/components/tables/RecentDays.tsx
  • src/components/ui/badge.tsx
  • src/components/ui/button.tsx
  • src/components/ui/card.tsx
  • src/components/ui/dialog.tsx
  • src/components/ui/expandable-card.tsx
  • src/components/ui/formatted-value.tsx
  • src/components/ui/select.tsx
  • src/components/ui/skeleton.tsx
  • src/components/ui/toast.tsx
  • src/components/ui/tooltip.tsx
  • src/hooks/use-app-settings.ts
  • src/hooks/use-computed-metrics.ts
  • src/hooks/use-dashboard-filters.ts
  • src/hooks/use-provider-limits.ts
  • src/hooks/use-theme.ts
  • src/hooks/use-usage-data.ts
  • src/index.css
  • src/lib/api.ts
  • src/lib/app-settings.ts
  • src/lib/auto-import.ts
  • src/lib/calculations.ts
  • src/lib/constants.ts
  • src/lib/csv-export.ts
  • src/lib/dashboard-preferences.ts
  • src/lib/data-transforms.ts
  • src/lib/formatters.ts
  • src/lib/help-content.ts
  • src/lib/i18n.ts
  • src/lib/model-utils.ts
  • src/lib/provider-limits.ts
  • src/types/index.ts
  • tests/e2e/dashboard.spec.ts
  • tests/frontend/provider-limits-section.test.tsx
  • tests/frontend/use-dashboard-filters.test.tsx
  • tests/integration/server.test.ts
  • tests/unit/analytics.test.ts
  • tests/unit/model-normalization.test.ts
  • tests/unit/report-charts.test.ts
  • tests/unit/report-utils.test.ts
  • tests/unit/server-helpers.test.ts
  • tsconfig.json
  • usage-normalizer.js
  • vitest.config.ts

Comment thread package.json Outdated
Comment thread server/model-normalization.json Outdated
Comment thread src/components/cards/TodayMetrics.tsx
Comment thread src/components/charts/ChartCard.tsx Outdated
Comment thread src/components/features/settings/SettingsModal.tsx Outdated
Comment thread src/components/tables/ModelEfficiency.tsx
Comment thread src/components/tables/RecentDays.tsx
Comment thread src/lib/auto-import.ts Outdated
Comment thread src/lib/formatters.ts 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: 4

Caution

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

⚠️ Outside diff range comments (1)
src/lib/data-transforms.ts (1)

292-297: ⚠️ Potential issue | 🟠 Major

Weekday labels can be shifted by timezone, causing mislabeled chart buckets.

On Line 293–294, you create UTC dates but format them in the user's local timezone. In many US timezones this can shift labels by one day (e.g., Monday bucket labeled as Sunday), while bucket indexing uses Monday=0 logic.

💡 Suggested fix
-  const weekdayLabels = Array.from({ length: 7 }, (_, index) =>
-    new Intl.DateTimeFormat(getCurrentLocale(), { weekday: 'short' })
-      .format(new Date(Date.UTC(2024, 0, 1 + index)))
-      .replace('.', '')
-      .slice(0, 2),
-  )
+  const weekdayFormatter = new Intl.DateTimeFormat(getCurrentLocale(), {
+    weekday: 'short',
+    timeZone: 'UTC',
+  })
+  const weekdayLabels = Array.from({ length: 7 }, (_, index) =>
+    weekdayFormatter.format(new Date(Date.UTC(2024, 0, 1 + index))).replace('.', ''),
+  )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/data-transforms.ts` around lines 292 - 297, The weekdayLabels
generation (variable weekdayLabels) formats UTC-built dates with
Intl.DateTimeFormat using the user's local timezone, which can shift the day and
mislabel chart buckets; fix it by forcing UTC in the formatter options (add
timeZone: 'UTC' to the options passed to Intl.DateTimeFormat in the
weekdayLabels code that calls getCurrentLocale()), so the formatter interprets
the Date.UTC values in UTC and labels align with the Monday=0 bucket logic.
🧹 Nitpick comments (3)
src/components/charts/CostOverTime.tsx (1)

31-33: Avoid sorting to compute the peak point.

Line 32 does an O(n log n) sort + array clone just to get max. A single-pass max is cheaper and clearer for larger datasets.

♻️ Suggested refactor
-    const peak = [...data].sort((a, b) => b.cost - a.cost)[0]
+    let peak = data[0]
+    for (let i = 1; i < data.length; i += 1) {
+      if (data[i].cost > peak.cost) peak = data[i]
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/charts/CostOverTime.tsx` around lines 31 - 33, The code
computes peak by cloning and sorting data (const peak = [...data].sort((a, b) =>
b.cost - a.cost)[0]) which is O(n log n); replace that with a single-pass max
scan (e.g., use Array.prototype.reduce or a simple loop) to find the item with
the largest cost without cloning data, keep the existing latest =
data[data.length - 1] logic and the same peak variable name so subsequent code
is unchanged, and ensure the function still returns null when either latest or
peak is falsy.
src/lib/data-transforms.ts (1)

13-408: Please run the full verification gates for this transform-heavy change before merge.

Given this file drives filtering/aggregation/chart data paths, run verify, unit coverage, e2e, and manual flow checks (dashboard load, auto-import, JSON upload, filtering, export).

Based on learnings: "Run npm run verify before opening a PR to validate code quality", "Run npm run test:e2e before opening a PR for end-to-end testing", "Run npm run test:unit:coverage to match the gate used by the release workflow", and "Manually verify the main flows affected by the change: dashboard load, auto-import, JSON upload, filtering, and export actions".

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

In `@src/lib/data-transforms.ts` around lines 13 - 408, This change touches many
transforms (recalculateDayFromBreakdowns, filterByModels, filterByProviders,
aggregateToDailyFormat, toModelCostChartData, toTokenChartData,
toRequestChartData, toWeekdayData and related helpers), so before merging run
the full verification gates and manual flows: run npm run verify, npm run
test:unit:coverage and fix any failing unit tests/coverage gaps, run npm run
test:e2e and resolve failing e2e tests, then manually exercise the dashboard
load, auto-import, JSON upload, filtering (by date/model/provider/month), export
and aggregation paths to confirm outputs (charts, weekday aggregation,
monthly/yearly aggregation) are correct; iterate on the affected functions above
until all gates and manual checks pass.
src/lib/help-content.ts (1)

1-1: Consider renaming this utility file to match kebab-free naming guidance.

src/lib/help-content.ts conflicts with the utility filename convention; src/lib/helpContent.ts (or src/lib/HelpContent.ts) would align better.

As per coding guidelines "src/lib/**/*.ts: Use PascalCase or descriptive kebab-free names for utility filenames (e.g., formatters.ts)".

🤖 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/charts/CostByModelOverTime.tsx`:
- Around line 31-33: The aggregation for the `total` in the `data.reduce`
(summing `point[model]`) currently checks `typeof point[model] === 'number'`
which allows NaN/Infinity; update the reducer to use the existing `coerceNumber`
helper (or call it on `point[model]`) and only add the returned finite numeric
value (e.g., treat non-finite as 0) so `total` is never polluted by
NaN/Infinity; change the `total` computation in `CostByModelOverTime` to reuse
`coerceNumber(point[model])` when summing.

In `@src/components/features/drill-down/DrillDownModal.tsx`:
- Around line 79-100: Compute a single tokensTotal value from
day.cacheReadTokens + day.cacheCreationTokens + day.inputTokens +
day.outputTokens + day.thinkingTokens and use that single tokensTotal everywhere
instead of mixing day.totalTokens and ad-hoc sums: replace the hasTokens,
cacheRate denominator, costPerMillion calculation and any other places (e.g.,
avgTokensPerRequest / avgCostPerRequest usage points) to reference tokensTotal
(and base hasTokens on tokensTotal > 0) so all percentage/cost derivations use
the same canonical token total.

In `@src/lib/data-transforms.ts`:
- Around line 108-118: getDateRange currently returns null if data[0] is falsy,
which drops valid later entries; change the logic in getDateRange to scan for
the first non-null DailyUsage entry before deciding to return null and
initialize start/end from that found entry (or return null only if no non-null
entries exist), e.g., use a loop to find first valid entry and then continue
iterating to compute min/max dates using the existing loop logic; update
references in getDateRange to use that firstValidEntry instead of assuming
data[0].

In `@src/lib/help-content.ts`:
- Around line 247-248: The Proxy traps for get and has in the selector wrapper
are exposing prototype properties (e.g., toString) because they call
Reflect.get(selector(), key) and use key in selector(), which walk the prototype
chain; update both traps in the Proxy returned by selector() to first check
ownership using Object.prototype.hasOwnProperty.call(selector(), key) (or
selector().hasOwnProperty guard) and only then return Reflect.get(selector(),
key) for get and true for has, otherwise return undefined (or Reflect.get
default) / false so inherited properties are not treated as map keys.

---

Outside diff comments:
In `@src/lib/data-transforms.ts`:
- Around line 292-297: The weekdayLabels generation (variable weekdayLabels)
formats UTC-built dates with Intl.DateTimeFormat using the user's local
timezone, which can shift the day and mislabel chart buckets; fix it by forcing
UTC in the formatter options (add timeZone: 'UTC' to the options passed to
Intl.DateTimeFormat in the weekdayLabels code that calls getCurrentLocale()), so
the formatter interprets the Date.UTC values in UTC and labels align with the
Monday=0 bucket logic.

---

Nitpick comments:
In `@src/components/charts/CostOverTime.tsx`:
- Around line 31-33: The code computes peak by cloning and sorting data (const
peak = [...data].sort((a, b) => b.cost - a.cost)[0]) which is O(n log n);
replace that with a single-pass max scan (e.g., use Array.prototype.reduce or a
simple loop) to find the item with the largest cost without cloning data, keep
the existing latest = data[data.length - 1] logic and the same peak variable
name so subsequent code is unchanged, and ensure the function still returns null
when either latest or peak is falsy.

In `@src/lib/data-transforms.ts`:
- Around line 13-408: This change touches many transforms
(recalculateDayFromBreakdowns, filterByModels, filterByProviders,
aggregateToDailyFormat, toModelCostChartData, toTokenChartData,
toRequestChartData, toWeekdayData and related helpers), so before merging run
the full verification gates and manual flows: run npm run verify, npm run
test:unit:coverage and fix any failing unit tests/coverage gaps, run npm run
test:e2e and resolve failing e2e tests, then manually exercise the dashboard
load, auto-import, JSON upload, filtering (by date/model/provider/month), export
and aggregation paths to confirm outputs (charts, weekday aggregation,
monthly/yearly aggregation) are correct; iterate on the affected functions above
until all gates and manual checks pass.
🪄 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: 9763b0d3-7641-49af-9ceb-8aba1111f9b9

📥 Commits

Reviewing files that changed from the base of the PR and between 1db830c and 8423700.

📒 Files selected for processing (41)
  • .github/workflows/release.yml
  • RELEASING.md
  • package.json
  • server/model-normalization.json
  • src/components/cards/MonthMetrics.tsx
  • src/components/cards/SecondaryMetrics.tsx
  • src/components/cards/TodayMetrics.tsx
  • src/components/charts/ChartCard.tsx
  • src/components/charts/CorrelationAnalysis.tsx
  • src/components/charts/CostByModelOverTime.tsx
  • src/components/charts/CostByWeekday.tsx
  • src/components/charts/CostOverTime.tsx
  • src/components/charts/CumulativeCost.tsx
  • src/components/charts/ModelMix.tsx
  • src/components/charts/TokenEfficiency.tsx
  • src/components/features/anomaly/AnomalyDetection.tsx
  • src/components/features/drill-down/DrillDownModal.tsx
  • src/components/features/forecast/CostForecast.tsx
  • src/components/features/limits/ProviderLimitsSection.tsx
  • src/components/features/settings/SettingsModal.tsx
  • src/components/layout/FilterBar.tsx
  • src/components/tables/ModelEfficiency.tsx
  • src/components/tables/ProviderEfficiency.tsx
  • src/components/tables/RecentDays.tsx
  • src/components/ui/card.tsx
  • src/lib/auto-import.ts
  • src/lib/data-transforms.ts
  • src/lib/formatters.ts
  • src/lib/help-content.ts
  • src/locales/de/common.json
  • src/locales/en/common.json
  • tests/frontend/chart-card.test.tsx
  • tests/frontend/filter-bar.test.tsx
  • tests/frontend/phase4-correctness.test.tsx
  • tests/frontend/provider-limits-section.test.tsx
  • tests/frontend/sortable-tables.test.tsx
  • tests/unit/analytics.test.ts
  • tests/unit/code-rabbit-phase1.test.ts
  • tests/unit/code-rabbit-phase4.test.ts
  • tests/unit/help-content.test.ts
  • tests/unit/model-normalization.test.ts
✅ Files skipped from review due to trivial changes (2)
  • src/components/features/anomaly/AnomalyDetection.tsx
  • server/model-normalization.json
🚧 Files skipped from review as they are similar to previous changes (20)
  • .github/workflows/release.yml
  • src/components/charts/CostByWeekday.tsx
  • RELEASING.md
  • src/components/features/forecast/CostForecast.tsx
  • src/lib/formatters.ts
  • src/components/charts/ModelMix.tsx
  • src/components/cards/SecondaryMetrics.tsx
  • src/components/charts/ChartCard.tsx
  • src/components/charts/TokenEfficiency.tsx
  • src/components/tables/ModelEfficiency.tsx
  • src/components/cards/TodayMetrics.tsx
  • src/components/ui/card.tsx
  • package.json
  • src/components/charts/CumulativeCost.tsx
  • src/components/cards/MonthMetrics.tsx
  • src/components/tables/RecentDays.tsx
  • src/components/layout/FilterBar.tsx
  • src/components/charts/CorrelationAnalysis.tsx
  • src/components/tables/ProviderEfficiency.tsx
  • src/components/features/limits/ProviderLimitsSection.tsx

Comment thread src/components/charts/CostByModelOverTime.tsx Outdated
Comment thread src/components/features/drill-down/DrillDownModal.tsx Outdated
Comment thread src/lib/data-transforms.ts
Comment thread src/lib/help-content.ts Outdated
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