Skip to content

refactor(dashboard): adopt native Svelte class syntax and slim the icon bundle - #648

Merged
SantiagoDePolonia merged 2 commits into
mainfrom
refactor/classes
Aug 4, 2026
Merged

refactor(dashboard): adopt native Svelte class syntax and slim the icon bundle#648
SantiagoDePolonia merged 2 commits into
mainfrom
refactor/classes

Conversation

@SantiagoDePolonia

@SantiagoDePolonia SantiagoDePolonia commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #646.

Issue #646 asked to research a class-name library — the answer is that no library is needed: since Svelte 5.16 the class attribute natively accepts objects and arrays with clsx semantics (docs), and the dashboard is on Svelte 5.35. This PR adopts that syntax everywhere hand-built class strings added noise, and folds in two Icon.svelte cleanups spotted along the way.

Class syntax (17 components)

  • The issue's example, ChatMessage.svelte: the articleClass() filter/join helper is gone; the <article> declares class={[base, msg.roleClass, { "is-anchor": …, "is-after-anchor": … }]} inline.
  • Multi-line ternaries in interpolations, {x || ''} guards, and string concatenation (WorkflowChart, AuditPaneTabs, AuditEntryMetadata, ContributionCalendar, McpServerList, EditorDialog, RuntimeRefresh) → array form; falsy values drop automatically.
  • Heavy class: stacks → object form: DatePickerCalendar (6 directives → one object) and ProviderStatusCardDetails (3 directives that compared breakerClass to literals just to re-apply it → class={["provider-status-health-state", breakerClass]}).
  • The 7 shared atoms with a className prop (Spinner, TableActionButton, CopyButton, DialogCloseButton, SegmentedControl, FilterInput, LoadingState) use array composition; no more trailing space when the prop is empty.
  • displayRowClass() returns a class array consumed directly by the class attribute (tests updated).

Deliberately untouched: single/double class: directives (still doc-idiomatic), class="base {helper()}" badge sites where helpers return one token, and imperative document.body.classList calls (body is outside any template).

Icon bundle: −32%

Icon.svelte imported lucide's full icons map and looked icons up dynamically, defeating tree-shaking — the dashboard uses 26 static + 30 dynamically-referenced icons out of ~1,600 shipped. A curated 56-entry registry (icons.js) now backs the lookup, and the hand-rolled SVG serializer + {@html} is replaced with a recursive <svelte:element> snippet. Minified bundle: 1,176 KB → 800 KB (gzip 234 KB); Vite's chunk-size warning is gone.

One subtlety worth reviewer attention: snippet bodies don't inherit the SVG namespace from the surrounding <svg>, so the svelte:element carries an explicit xmlns (without it, paths compile as HTML elements and icons render blank — verified in compiled output).

User-visible impact

None intended: identical rendered classes (minus cosmetic trailing spaces in class attributes), identical icons, smaller dashboard payload.

Testing

  • vite build clean, svelte-check 0 errors/0 warnings (315 files), 435/435 dashboard unit tests pass.
  • Headless-Chrome screenshot of the running dashboard confirms all icons render correctly (SVG namespace verified at runtime, not just compile time).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • UI Improvements

    • Improved consistency and reliability of dynamic styling across dashboard controls, calendars, badges, dialogs, workflow charts, and status indicators.
    • Preserved existing visual states while preventing empty or malformed CSS classes.
    • Streamlined icon rendering with a curated set of dashboard icons, maintaining supported icon appearances.
  • Tests

    • Updated virtual model display tests to reflect the revised class handling without changing expected behavior.

…on bundle

Replace hand-built class strings (filter/join helpers, ternaries in
interpolations, || '' guards, string concatenation, heavy class:
directive stacks) with the clsx-style class={[...]} / class={{...}}
syntax built into Svelte 5.16+ (svelte.dev/docs/svelte/class). No
library needed. displayRowClass now returns a class array consumed
directly by the class attribute.

Icon.svelte now renders from a curated 56-icon registry instead of
lucide's full icons map (whose dynamic lookup defeated tree-shaking),
and uses a recursive svelte:element snippet instead of a hand-rolled
SVG serializer with @html. Minified bundle: 1,176 KB -> 800 KB.

Closes #646

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 4, 2026 13:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d3823b2e-858f-4ef9-ae41-b6c9504e40da

📥 Commits

Reviewing files that changed from the base of the PR and between 607b233 and 2202e44.

⛔ Files ignored due to path filters (2)
  • internal/admin/dashboard/static/dist/assets/index-DzO__mgM.js is excluded by !**/dist/**
  • internal/admin/dashboard/static/dist/index.html is excluded by !**/dist/**
📒 Files selected for processing (2)
  • web/dashboard/src/lib/components/atoms/icons.js
  • web/dashboard/src/pages/overview/ProviderStatusCardDetails.svelte

📝 Walkthrough

Walkthrough

The dashboard replaces interpolated CSS class strings with array-based Svelte bindings, adds a curated Lucide icon registry, renders icon nodes recursively without raw HTML injection, and updates virtual-model class output tests.

Changes

Dashboard rendering changes

Layer / File(s) Summary
Icon registry and recursive SVG rendering
web/dashboard/src/lib/components/atoms/icons.js, web/dashboard/src/lib/components/atoms/Icon.svelte
The dashboard maps supported icon names to imported Lucide icons and renders their SVG nodes recursively.
Array-based class composition
web/dashboard/src/lib/components/atoms/*.svelte, web/dashboard/src/lib/components/molecules/*.svelte, web/dashboard/src/lib/components/organisms/EditorDialog.svelte, web/dashboard/src/pages/audit-logs/*.svelte, web/dashboard/src/pages/mcp-servers/McpServerList.svelte, web/dashboard/src/pages/overview/*.svelte, web/dashboard/src/pages/settings/RuntimeRefresh.svelte, web/dashboard/src/pages/workflows/WorkflowChart.svelte
Components and pages use array class bindings while preserving existing base, state, and custom classes.
Derived class output and test updates
web/dashboard/src/pages/models/virtualModelsLogic.js, web/dashboard/tests/models-virtual-models.test.js
displayRowClass returns class arrays, and tests assert the updated output.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: copilot

Poem

A rabbit trims classes, neat and light,
While icons bloom in SVG flight.
No raw HTML hides in the nest,
Arrays keep each style expressed.
The dashboard hops along just right.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The icon registry and SVG rendering cleanup are unrelated to the linked issue's class-name handling objective. Move the icon bundle and SVG rendering changes to a separate PR or link an issue that explicitly covers those objectives.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the native Svelte class syntax changes and icon bundle reduction.
Description check ✅ Passed The description explains the changes, rationale, impact, and testing, although it uses Summary instead of the template's Description heading.
Linked Issues check ✅ Passed The PR researches the requested library and applies native Svelte class syntax as the simpler solution for issue #646.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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 refactor/classes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
web/dashboard/src/pages/overview/ProviderStatusCardDetails.svelte (1)

6-9: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve the breaker-state CSS selectors through template class tokens.

providerBreakerStateClass(provider) produces one dynamic class, so there is no static .provider-status-health-state.is-healthy, .provider-status-health-state.is-degraded, or .provider-status-health-state.is-unhealthy token in this template. Keep the scoped styles with fixed class tokens, or mark only the non-static state parts/global as needed, and change the header comment so it no longer says breaker-state palette classes are applied via class: directives.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/dashboard/src/pages/overview/ProviderStatusCardDetails.svelte` around
lines 6 - 9, Update the breaker-state styling in ProviderStatusCardDetails so
the healthy, degraded, and unhealthy selector tokens remain discoverable despite
providerBreakerStateClass(provider) returning a dynamic class. Preserve the
scoped CSS by adding fixed template class tokens or narrowly adjusting
scope/global handling, and revise the header comment to accurately describe the
implementation instead of claiming class: directives apply the palette classes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/dashboard/src/lib/components/atoms/icons.js`:
- Around line 86-89: Add the missing Inbox icon import and register it as the
"inbox" entry in iconRegistry alongside the existing icon mappings, so
EmptyState.svelte's default icon resolves correctly.

---

Outside diff comments:
In `@web/dashboard/src/pages/overview/ProviderStatusCardDetails.svelte`:
- Around line 6-9: Update the breaker-state styling in ProviderStatusCardDetails
so the healthy, degraded, and unhealthy selector tokens remain discoverable
despite providerBreakerStateClass(provider) returning a dynamic class. Preserve
the scoped CSS by adding fixed template class tokens or narrowly adjusting
scope/global handling, and revise the header comment to accurately describe the
implementation instead of claiming class: directives apply the palette classes.
🪄 Autofix

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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3f2aa89e-6e70-454c-906e-3b2235474cfa

📥 Commits

Reviewing files that changed from the base of the PR and between 29d9a31 and 607b233.

⛔ Files ignored due to path filters (3)
  • internal/admin/dashboard/static/dist/assets/index-BTMyFhIF.js is excluded by !**/dist/**
  • internal/admin/dashboard/static/dist/assets/index-DwpOmyPo.js is excluded by !**/dist/**
  • internal/admin/dashboard/static/dist/index.html is excluded by !**/dist/**
📒 Files selected for processing (21)
  • web/dashboard/src/lib/components/atoms/CopyButton.svelte
  • web/dashboard/src/lib/components/atoms/DialogCloseButton.svelte
  • web/dashboard/src/lib/components/atoms/Icon.svelte
  • web/dashboard/src/lib/components/atoms/SegmentedControl.svelte
  • web/dashboard/src/lib/components/atoms/Spinner.svelte
  • web/dashboard/src/lib/components/atoms/TableActionButton.svelte
  • web/dashboard/src/lib/components/atoms/icons.js
  • web/dashboard/src/lib/components/molecules/DatePickerCalendar.svelte
  • web/dashboard/src/lib/components/molecules/FilterInput.svelte
  • web/dashboard/src/lib/components/molecules/LoadingState.svelte
  • web/dashboard/src/lib/components/organisms/EditorDialog.svelte
  • web/dashboard/src/pages/audit-logs/AuditEntryMetadata.svelte
  • web/dashboard/src/pages/audit-logs/AuditPaneTabs.svelte
  • web/dashboard/src/pages/audit-logs/ChatMessage.svelte
  • web/dashboard/src/pages/mcp-servers/McpServerList.svelte
  • web/dashboard/src/pages/models/virtualModelsLogic.js
  • web/dashboard/src/pages/overview/ContributionCalendar.svelte
  • web/dashboard/src/pages/overview/ProviderStatusCardDetails.svelte
  • web/dashboard/src/pages/settings/RuntimeRefresh.svelte
  • web/dashboard/src/pages/workflows/WorkflowChart.svelte
  • web/dashboard/tests/models-virtual-models.test.js

Comment thread web/dashboard/src/lib/components/atoms/icons.js
@greptile-apps

greptile-apps Bot commented Aug 4, 2026

Copy link
Copy Markdown

Confidence Score: 4/5

Not ready to merge until the shared EmptyState default icon is restored.

The dashboard's default empty-state rendering was exercised through compiled Svelte SSR and consistently produced an empty SVG, while a registered icon control produced drawable SVG elements.

Files Needing Attention: web/dashboard/src/lib/components/atoms/EmptyState.svelte and web/dashboard/src/lib/components/atoms/icons.js need matching default-name and registry updates.

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex produced proofs for two posted P2 findings.
  • T-Rex produced a proof for a posted P1 finding.
  • T-Rex ran the requested verification, but its local artifact references were not uploaded.
  • T-Rex documented the validation results, including default and registered captures, and noted a Playwright runtime blocker due to a missing Chromium binary.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (2)

  1. web/dashboard/src/lib/components/atoms/EmptyState.svelte, line 3 (link)

    P2 Default EmptyState icon is not registered

    EmptyState defaults icon to "inbox", but the curated iconRegistry has no inbox entry. Icon therefore resolves the name to an empty node array, so default empty states render an SVG wrapper without any drawable icon content. Register Lucide's Inbox icon, or change the default to a registered name.

    T-Rex Ran code and verified through T-Rex

  2. General comment

    P1 EmptyState default renders a blank icon

    • Bug
      • Rendering EmptyState without an icon prop emits the SVG wrapper but no drawable elements, so the default empty-state icon is visually blank.
    • Cause
      • EmptyState.svelte:3 defaults the icon name to inbox, but icons.js:5-121 does not import or register inbox. Icon.svelte:9 consequently resolves it to [], and its SVG loop emits no icon nodes.
    • Fix
      • Import Lucide's Inbox icon in web/dashboard/src/lib/components/atoms/icons.js and add inbox: Inbox to iconRegistry (or change the EmptyState default to an already registered icon).

    T-Rex Ran code and verified through T-Rex

Reviews (1): Last reviewed commit: "refactor(dashboard): adopt native Svelte..." | Re-trigger Greptile

Review follow-up: the curated registry missed "inbox" — used only as
EmptyState's icon prop default, a source the usage sweep didn't cover —
so default empty states rendered a blank SVG. Also rewrites a stale
ProviderStatusCardDetails header comment that still described the
class: directives replaced by the class array.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 4, 2026 13:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@SantiagoDePolonia
SantiagoDePolonia merged commit adbbe0e into main Aug 4, 2026
20 checks passed
SantiagoDePolonia added a commit that referenced this pull request Aug 4, 2026
…ngs (#649)

* refactor(dashboard): pass lucide icons as values instead of name strings

Follow-up to #648. Icon took a kebab-case name and resolved it through a
curated registry, so an unregistered name rendered a blank SVG instead of
failing — exactly the EmptyState "inbox" bug found in review there.

Callers now import the icon and pass it (<Icon icon={Pencil} />), which
deletes the registry: there is no list to keep in sync, and tree-shaking
follows the imports structurally rather than from an enumerated map.
Config-driven icons (sidebar nav, theme toggle, confirm dialogs, budget
periods) hold the icon itself instead of a name.

The bundler does not warn on missing named exports and jsconfig runs with
checkJs:false, so a typo would still slip through silently. A guard test
resolves every lucide import in the source tree against the package,
failing with the offending file name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(dashboard): convert the icon call sites the codemod missed

Review follow-up. Three EditorDialog callers passed submitIcon as a name
string, and budgetPeriodIcon() still returned names — both render a blank
SVG now that Icon takes the icon itself. The codemod only rewrote <Icon>
tags and icon: config literals, so neither shape was touched.

Adds two guards: one asserts no icon binding is assigned a name string,
one asserts budgetPeriodIcon returns a drawable icon. Both were confirmed
to fail on the exact bugs above.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

Using a front-end library for class names presence filtering

3 participants