Skip to content

Refine model color system and improve PDF report quality#14

Merged
tyl3r-ch merged 4 commits intomainfrom
v620
Apr 14, 2026
Merged

Refine model color system and improve PDF report quality#14
tyl3r-ch merged 4 commits intomainfrom
v620

Conversation

@tyl3r-ch
Copy link
Copy Markdown
Contributor

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

Summary by CodeRabbit

  • New Features

    • Centralized, theme-aware model color system applied across dashboard, filters, tables and PDF/report exports.
    • PDF reports now include document title metadata, chart alternative text and visible short summaries.
  • Bug Fixes

    • Preserved truthful cost-axis precision for small values.
    • Improved chart label handling and layout to reduce unnecessary truncation and whitespace in exports.
  • Localization

    • Added chart alt/summary/localized strings for EN/DE.
  • Tests

    • New unit and integration tests for color consistency, chart formatting, and PDF structure.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 14, 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: 9010ba2b-5551-4576-86a0-3d2a6f8cf7f2

📥 Commits

Reviewing files that changed from the base of the PR and between 4a70abd and 132279f.

📒 Files selected for processing (8)
  • server/report/chart-labels.js
  • server/report/charts.js
  • server/report/index.js
  • server/report/utils.js
  • shared/model-colors.js
  • tests/unit/model-colors.test.ts
  • tests/unit/report-charts.test.ts
  • tests/unit/report-utils.test.ts
✅ Files skipped from review due to trivial changes (2)
  • server/report/chart-labels.js
  • shared/model-colors.js
🚧 Files skipped from review as they are similar to previous changes (4)
  • tests/unit/model-colors.test.ts
  • tests/unit/report-charts.test.ts
  • server/report/index.js
  • tests/unit/report-utils.test.ts

📝 Walkthrough

Walkthrough

Centralizes model color resolution into a theme-aware shared module, updates frontend components to use alpha-aware color helpers, enhances report/chart generation (locale-aware cost axes, chart alt/summary, PDF metadata/layout), and adds unit/integration tests and truncation utilities for consistent labeling.

Changes

Cohort / File(s) Summary
Shared Model Colors
shared/model-colors.d.ts, shared/model-colors.js
Add theme-aware, HSL-based curated color rules, deterministic fallbacks, alpha handling, spec → string/RGB formatters, and theme normalization API.
App Model Utilities
src/lib/model-utils.ts, src/lib/constants.ts
Remove local MODEL_COLORS map and deterministic dynamicColor; delegate to shared color module; add theme resolution and new getModelColorAlpha export; update getModelColor signature.
Frontend Usage
src/components/layout/FilterBar.tsx, src/components/tables/ModelEfficiency.tsx, src/components/tables/RecentDays.tsx
Replace string-concatenated alpha hacks with getModelColorAlpha(...) for background styling; preserve color token usage for text/foreground.
Report generation & labeling
server/report/index.js, server/report/utils.js, server/report/charts.js, server/report/chart-labels.js
Introduce truncateTopModelChartLabel and increase horizontal-bar left margin cap; add locale-aware formatCostAxisValue, populate chartDescriptions (alt/summary/fullNamesNote), use shared color RGB in report assets, set PDF title/metadata and adjust template/chart panel layout.
Localization & Changelog
src/locales/en/common.json, src/locales/de/common.json, CHANGELOG.md
Add chart alt/summary/full-names/no-data localization keys and changelog entry for v6.2.0.
Tests
tests/unit/model-colors.test.ts, tests/unit/report-charts.test.ts, tests/unit/report-utils.test.ts, tests/integration/server.test.ts
Add unit tests for curated/shared color behavior, truncation limits, locale-aware cost formatting, and report chart descriptions; extend integration test to assert PDF structural markers and accessibility metadata.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Server
  participant ReportModule
  participant SharedColors
  participant PDF
  Client->>Server: Request report (locale, filters)
  Server->>ReportModule: buildReportData(filters, locale)
  ReportModule->>SharedColors: getModelColorRgb(name, {theme: light})
  SharedColors-->>ReportModule: rgb(...) / hsl spec
  ReportModule->>ReportModule: compute chartDescriptions, truncated labels
  ReportModule->>ReportModule: createChartAssets(chart data, formatCostAxisValue)
  ReportModule->>SharedColors: getModelColorRgb(...) for chart assets
  ReportModule->>PDF: build Typst template (title, alt, summaries, charts)
  PDF-->>Server: bytes with metadata (/Title, /Alt, /Figure, /StructTreeRoot)
  Server-->>Client: PDF response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

Possibly related PRs

Poem

🐰 I nibbled hues from shared-held seeds,
HSL whispers stitched into color deeds,
Charts wear titles, alt text sings aloud,
PDFs tidy, no whitespace proud,
Hop—now every model blooms, both light and dark—how proud! 🎨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 accurately and concisely summarizes the two primary objectives of the changeset: refining the model color system (centralizing colors into shared module) and improving PDF report quality (adding metadata, descriptions, better formatting).

✏️ 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 v620

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.

Caution

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

⚠️ Outside diff range comments (1)
server/report/index.js (1)

330-342: ⚠️ Potential issue | 🟠 Major

Use the locale-aware cost formatter for top-models.svg.

cost-trend.svg now uses formatCostAxisValue, but top-models.svg still hardcodes toFixed(). That leaves German reports with a different decimal separator and a different rounding policy between the two cost charts.

💡 Proposed fix
   return {
     'cost-trend.svg': lineChart(costTrend, {
       title: reportData.text.charts.costTrend,
       valueKey: 'cost',
       secondaryKey: reportData.meta.filterSummary.viewModeKey === 'daily' ? 'ma7' : null,
       formatter: (value) => formatCostAxisValue(value, reportData.meta.language),
     }),
     'top-models.svg': horizontalBarChart(topModels, {
       title: reportData.text.charts.topModels,
       getValue: (entry) => entry.cost,
       getLabel: (entry) => entry.name,
       getColor: (entry) => entry.color,
-      formatter: (value) => `$${value.toFixed(value >= 100 ? 0 : 2)}`,
+      formatter: (value) => formatCostAxisValue(value, reportData.meta.language),
     }),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/report/index.js` around lines 330 - 342, Replace the hardcoded
formatter for 'top-models.svg' with the locale-aware cost formatter: update the
horizontalBarChart call's formatter (the inline function currently using
value.toFixed) to call formatCostAxisValue(value, reportData.meta.language) so
the bar chart uses the same locale-aware formatting and rounding as the
cost-trend chart; ensure you reference the same reportData.meta.language and
formatCostAxisValue function used by the lineChart block.
🧹 Nitpick comments (3)
shared/model-colors.js (1)

167-170: Return a fresh spec from getModelColorSpec.

For known models this returns the exact object stored in MODEL_COLOR_RULES, so one caller mutating h / s / l will change the shared palette globally. Clone the spec before returning.

🛡️ Proposed fix
 function getModelColorSpec(name, options = {}) {
   const theme = normalizeTheme(options.theme)
   const known = findKnownColor(name)
-  return known ? known[theme] : fallbackColor(name, theme)
+  return known ? { ...known[theme] } : fallbackColor(name, theme)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shared/model-colors.js` around lines 167 - 170, getModelColorSpec currently
returns the exact object from MODEL_COLOR_RULES for known models, allowing
external callers to mutate shared h/s/l values; modify getModelColorSpec so that
when findKnownColor(name) returns a spec you return a shallow clone (e.g., new
object with copied properties) instead of the original object, preserving
normalized theme logic and leaving fallbackColor(name, theme) behavior
unchanged; reference getModelColorSpec, findKnownColor, and MODEL_COLOR_RULES
when locating where to clone the spec before returning.
server/report/utils.js (1)

470-473: Keep fullNamesNote tied to the same truncation rule.

This note is meant to expand shortened labels, but it hardcodes > 30 instead of deriving that from truncateLabel(). That makes the note easy to desync from whatever the chart actually shortens.

♻️ Suggested refactor
   const topChartModels = modelRows.slice(0, 8);
   const truncatedTopModelNames = topChartModels
-    .filter((entry) => entry.name.length > 30)
+    .filter((entry) => truncateLabel(entry.name) !== entry.name)
     .map((entry) => entry.name);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/report/utils.js` around lines 470 - 473, fullNamesNote is hardcoded to
check name.length > 30 while the chart uses truncateLabel(), which can desync;
update the logic that builds truncatedTopModelNames/fullNamesNote to derive
truncation from truncateLabel() instead of a magic number — e.g., call
truncateLabel(entry.name) and include the original name when
truncateLabel(entry.name) !== entry.name; adjust references around
topChartModels, truncatedTopModelNames and wherever fullNamesNote is composed so
the note reflects the actual truncation rule.
tests/unit/model-colors.test.ts (1)

19-74: Cover the omitted-theme path too.

This suite only checks explicit 'light' / 'dark', but src/lib/model-utils.ts now has DOM-dependent fallback behavior when theme is omitted. A small case around document.documentElement.classList would protect that new default path from regressing.

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/unit/model-colors.test.ts` around lines 19 - 74, The tests miss the
codepath where theme is omitted and model-utils.ts falls back to reading
document.documentElement.classList; add focused tests that call getModelColor
(and optionally getModelColorAlpha/getModelColorRgb) without passing the theme
to cover both fallback outcomes: mock or stub
document.documentElement.classList.contains('dark') to true and assert
getModelColor('GPT-5.4') equals getModelColor('GPT-5.4','dark'), then mock
contains('dark') false (or contains('light') true) and assert it equals the
explicit 'light' result, and also include a deterministic unknown-model case to
ensure the DOM-dependent default is stable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@server/report/index.js`:
- Around line 330-342: Replace the hardcoded formatter for 'top-models.svg' with
the locale-aware cost formatter: update the horizontalBarChart call's formatter
(the inline function currently using value.toFixed) to call
formatCostAxisValue(value, reportData.meta.language) so the bar chart uses the
same locale-aware formatting and rounding as the cost-trend chart; ensure you
reference the same reportData.meta.language and formatCostAxisValue function
used by the lineChart block.

---

Nitpick comments:
In `@server/report/utils.js`:
- Around line 470-473: fullNamesNote is hardcoded to check name.length > 30
while the chart uses truncateLabel(), which can desync; update the logic that
builds truncatedTopModelNames/fullNamesNote to derive truncation from
truncateLabel() instead of a magic number — e.g., call truncateLabel(entry.name)
and include the original name when truncateLabel(entry.name) !== entry.name;
adjust references around topChartModels, truncatedTopModelNames and wherever
fullNamesNote is composed so the note reflects the actual truncation rule.

In `@shared/model-colors.js`:
- Around line 167-170: getModelColorSpec currently returns the exact object from
MODEL_COLOR_RULES for known models, allowing external callers to mutate shared
h/s/l values; modify getModelColorSpec so that when findKnownColor(name) returns
a spec you return a shallow clone (e.g., new object with copied properties)
instead of the original object, preserving normalized theme logic and leaving
fallbackColor(name, theme) behavior unchanged; reference getModelColorSpec,
findKnownColor, and MODEL_COLOR_RULES when locating where to clone the spec
before returning.

In `@tests/unit/model-colors.test.ts`:
- Around line 19-74: The tests miss the codepath where theme is omitted and
model-utils.ts falls back to reading document.documentElement.classList; add
focused tests that call getModelColor (and optionally
getModelColorAlpha/getModelColorRgb) without passing the theme to cover both
fallback outcomes: mock or stub
document.documentElement.classList.contains('dark') to true and assert
getModelColor('GPT-5.4') equals getModelColor('GPT-5.4','dark'), then mock
contains('dark') false (or contains('light') true) and assert it equals the
explicit 'light' result, and also include a deterministic unknown-model case to
ensure the DOM-dependent default is stable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dfd44ea3-6fd4-45ae-8ad0-8b6c0f95ecb1

📥 Commits

Reviewing files that changed from the base of the PR and between 9845b40 and 4a70abd.

📒 Files selected for processing (17)
  • CHANGELOG.md
  • server/report/charts.js
  • server/report/index.js
  • server/report/utils.js
  • shared/model-colors.d.ts
  • shared/model-colors.js
  • src/components/layout/FilterBar.tsx
  • src/components/tables/ModelEfficiency.tsx
  • src/components/tables/RecentDays.tsx
  • src/lib/constants.ts
  • src/lib/model-utils.ts
  • src/locales/de/common.json
  • src/locales/en/common.json
  • tests/integration/server.test.ts
  • tests/unit/model-colors.test.ts
  • tests/unit/report-charts.test.ts
  • tests/unit/report-utils.test.ts
💤 Files with no reviewable changes (1)
  • src/lib/constants.ts

@tyl3r-ch tyl3r-ch merged commit 560b2ff into main Apr 14, 2026
8 checks passed
@tyl3r-ch tyl3r-ch deleted the v620 branch April 14, 2026 21:19
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