Skip to content

fix(studio): unify entity icons behind a single canonical registry [ASTD-447] - #1531

Open
aahunt-nv wants to merge 5 commits into
mainfrom
aahunt/entity-icon-registry
Open

fix(studio): unify entity icons behind a single canonical registry [ASTD-447]#1531
aahunt-nv wants to merge 5 commits into
mainfrom
aahunt/entity-icon-registry

Conversation

@aahunt-nv

@aahunt-nv aahunt-nv commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Resolves: ASTD-447

Summary

Implements Surface 3 from the audit — nav icon vs. empty-state icon divergence. (The two Agent sub-pages in Surface 1 are being removed separately.)

The audit found 6 match / 9 mismatch / 4 no-nav-icon across 19 entities. The cause is structural: there were two independent icon sources with nothing linking them.

  • web/packages/studio/src/routes/groups/*Routes.tsx — each route group imported its own lucide-react glyph for slotIcon.
  • web/packages/common/src/components/EntityEmptyState/registry.ts — each descriptor carried its own icon field.

Hand-fixing the 9 would drift again on the next entity added, so this makes the mismatch unrepresentable instead:

  1. New web/packages/common/src/constants/entityIcons.ts holds ENTITY_ICONS — the one canonical entity → glyph map.
  2. icon is removed from EmptyStateDescriptor. EntityEmptyState resolves ENTITY_ICONS[entity]. A descriptor can no longer specify a conflicting glyph.
  3. All 14 route groups and WorkspaceSideNav render from the same map. No slotIcon in Studio imports its glyph locally anymore.

ENTITY_ICONS is a superset of the empty-state registry, since an entity can have an icon before it has empty-state copy. EmptyStateEntityKey narrows EntityKey, and EntityEmptyState accepts only the narrower type — so "every empty state resolves an icon" stays a compile-time guarantee.

Icon decisions

Where an entity had a nav glyph, that glyph wins — it's the persistent, most-frequently-seen representation.

Entity Icon Change
agents Bot was HatGlasses
agentMonitorRuns Logs monitor runs are agent telemetry
baseModels Boxes was FolderOpen fallthrough
customModels Metronome was BrainCircuit
virtualModels Waypoints was Radar, which frees Radar for providers
datasets Database new entry — see below
filesets FileStack was Database
anonymizerJobs UserPen was VenetianMask
dataDesignerJobs Form was Lightbulb, which collided with Insights
safeSynthesizerJobs DatabaseCheck was ShieldCheck
guardrails, guardrailChecks ShieldCheck tests inherit the config glyph
evaluationResults, agentEvaluations, evaluationSessions ChartBar was FlaskConical
evalComparison GitCompare was ChartNetwork
telemetryTraces, insightTraces ListTree was Anchor
telemetrySpans Logs was Anchor

Unchanged: deployments (Rocket), inferenceProviders (Radar), experiments/insightExperiments (FlaskConical), optimizerInsights (Lightbulb), jobs (ListChecks), filesetFiles (FolderOpen), secrets (LockKeyhole), members (UsersRound).

Four calls worth surfacing:

  • Guardrails and its tests share ShieldCheck. A config and its test cases are one thing to the user, so the tests inherit the config glyph rather than claiming a second shield. This retires ShieldKeyhole and leaves the shield family meaning guardrails only — Safe Synthesizer moves to DatabaseCheck, its nav glyph.
  • jobs keeps ListChecks. It is a persistent sidebar item; guardrailChecks renders in exactly one empty state nested two levels into a config, so the nav item has the stronger claim.
  • agentMonitorRuns shares Logs with telemetrySpans. The Monitor list is populated from the nemo-agent-telemetry fileset, so it is the same underlying data — Bot is reserved for the agent itself.
  • datasets gets a canonical entry even though it is a nav grouping today, because it is becoming an entity in its own right. It would otherwise be the one nav glyph left outside the map.

Testing

  • New entityIcons.test.ts: every empty-state entity resolves an icon, aliases point at their parent, and no glyph is reused across unrelated entities — the regression guard for the ShieldCheck/Radar collisions. Intentional aliases are declared in one table with a reason each, so adding a collision means editing that table on purpose.
    Verified non-vacuous: pointing jobs at ShieldCheck fails with expected [['guardrails','jobs']] to deeply equal [].
  • pnpm --filter @nemo/common test — 1513 passed.
  • pnpm --filter nemo-studio-ui test — 2960 passed. AssistantChatRoute/api.test.ts fails with localStorage.clear is not a function; verified the identical 18 failures on main, so pre-existing and unrelated.
  • typecheck clean on both packages (catches any missed descriptor.icon), lint clean (catches orphaned lucide-react imports).

Icon stroke weight

.lucide in web/packages/studio/src/index.css already sizes every icon to 16px. This pins its stroke-width to 1 for a lighter weight against 14px text.

This reinstates the override that #1369 removed a week ago (which had dropped it to fall back to lucide's upstream default of 2). Calling that out so the flip-flop in git log is intentional and legible rather than looking like an accidental revert.

Worth knowing for anyone tuning this later: the adjacent vector-effect: non-scaling-stroke rule means the stroke is drawn in the scaled coordinate space, so the authored value is not the on-screen pixel value — lucide's geometry is a 24-unit viewBox scaled to 16px. An authored 1 therefore renders lighter than 1px. I was not able to measure the exact rendered width in a browser here, so if the weight looks off in review, that scaling is the thing to adjust against.

Summary by CodeRabbit

  • UI Improvements

    • Standardized icons across sidebar navigation and empty states for a more consistent visual experience.
    • Updated entity icons for agents, models, datasets, evaluations, deployments, jobs, and other areas.
    • Applied a consistent default stroke weight to interface icons.
  • Bug Fixes

    • Improved icon coverage and rendering for supported empty states.
    • Prevented unintended reuse of icons across unrelated entities.

Signed-off-by: Aaron Hunt <aahunt@nvidia.com>
@github-actions github-actions Bot added the fix label Aug 25, 2026
Nav icons and empty-state icons were two independent sources of truth with
nothing linking them, so they drifted: 9 of 19 entities showed a different
glyph in the sidebar than in their own empty state, and three fell through
to a FolderOpen default. ShieldCheck and Radar each covered two unrelated
entities, so neither glyph meant anything specific.

Add ENTITY_ICONS as the one canonical entity -> glyph map, and remove the
icon field from EmptyStateDescriptor so a descriptor can no longer carry a
conflicting glyph. Route groups and the workspace side nav now render from
the same map, making the mismatch unrepresentable rather than merely fixed.

Where an entity had a nav glyph, that glyph wins -- it is the persistent,
most-seen representation. Sub-entities inherit their parent's family, so
guardrail configs keep the keyhole shield and their tests take the check.

Refs: ASTD-447
Signed-off-by: Aaron Hunt <aahunt@nvidia.com>
@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 35426/45214 78.3% 62.8%
Integration Tests 21118/42989 49.1% 22.4%

Guardrails and its tests both take ShieldCheck -- a config and its test
cases are one thing to the user, so the tests inherit the config glyph
rather than claiming a second shield. Jobs keeps ListChecks; it is a
persistent nav item, where guardrailChecks appears only in one empty state
nested two levels into a config.

Datasets is becoming an entity in its own right, so it gets a canonical
entry now rather than staying the one nav glyph outside the map.

That makes ENTITY_ICONS a superset of the empty-state registry, so split
EmptyStateEntityKey out of EntityKey. EntityEmptyState accepts only the
narrower type, which keeps 'every empty state resolves an icon' a compile-
time guarantee while letting an entity have an icon before it has copy.

Refs: ASTD-447
Signed-off-by: Aaron Hunt <aahunt@nvidia.com>
Monitor runs are agent telemetry -- the list is populated from the
nemo-agent-telemetry fileset -- so they read as the same data spans are
drawn from, and take Logs rather than the Bot glyph that stands for the
agent itself.

Recorded as an intentional alias against telemetrySpans, so the no-reuse
guard still holds and the shared glyph is a stated decision rather than a
collision that slipped through.

Refs: ASTD-447
Signed-off-by: Aaron Hunt <aahunt@nvidia.com>
Icons render at 16px via the .lucide base rule, where lucide's native
stroke reads heavier than intended against 14px text. Pin the stroke to 1.

This restores the override removed in #1369, which had dropped it to fall
back to lucide's upstream default of 2. Reinstating it deliberately: the
lighter weight is the wanted look for Studio's 16px icons.

Note that the adjacent vector-effect: non-scaling-stroke rule means the
stroke is drawn in the scaled coordinate space, so the on-screen result is
lighter than the authored value.

Refs: ASTD-447
Signed-off-by: Aaron Hunt <aahunt@nvidia.com>
@aahunt-nv
aahunt-nv marked this pull request as ready for review August 25, 2026 22:32
@aahunt-nv
aahunt-nv requested review from a team as code owners August 25, 2026 22:32
@coderabbitai

coderabbitai Bot commented Aug 25, 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 477ed9bf-7037-4431-bdb6-025524bbc2f6

📥 Commits

Reviewing files that changed from the base of the PR and between d374599 and 88125bd.

📒 Files selected for processing (20)
  • web/packages/common/src/components/EntityEmptyState/index.tsx
  • web/packages/common/src/components/EntityEmptyState/registry.ts
  • web/packages/common/src/constants/entityIcons.test.ts
  • web/packages/common/src/constants/entityIcons.ts
  • web/packages/studio/src/index.css
  • web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.tsx
  • web/packages/studio/src/routes/groups/agentRoutes.tsx
  • web/packages/studio/src/routes/groups/anonymizerRoutes.tsx
  • web/packages/studio/src/routes/groups/customizationRoutes.tsx
  • web/packages/studio/src/routes/groups/dataDesignerRoutes.tsx
  • web/packages/studio/src/routes/groups/deploymentRoutes.tsx
  • web/packages/studio/src/routes/groups/evaluationRoutes.tsx
  • web/packages/studio/src/routes/groups/experimentRoutes.tsx
  • web/packages/studio/src/routes/groups/filesetRoutes.tsx
  • web/packages/studio/src/routes/groups/guardrailsRoutes.tsx
  • web/packages/studio/src/routes/groups/intakeRoutes.tsx
  • web/packages/studio/src/routes/groups/jobRoutes.tsx
  • web/packages/studio/src/routes/groups/optimizerRoutes.tsx
  • web/packages/studio/src/routes/groups/safeSynthesizerRoutes.tsx
  • web/packages/studio/src/routes/groups/virtualModelsRoutes.tsx

Included review availability: Your plan provides up to 12 included reviews per hour; 11 remain after this review.


📝 Walkthrough

Walkthrough

The PR adds a canonical entity icon registry, updates empty-state rendering to use it, and replaces direct Lucide icons across Studio navigation. It also adds registry tests and sets the default Lucide stroke width to 1.

Changes

Canonical entity icon adoption

Layer / File(s) Summary
Canonical icon registry and coverage tests
web/packages/common/src/constants/entityIcons.ts, web/packages/common/src/constants/entityIcons.test.ts
ENTITY_ICONS now defines canonical icons and EntityKey. Tests validate coverage, aliases, renderability, and glyph reuse.
Empty-state icon resolution
web/packages/common/src/components/EntityEmptyState/registry.ts, web/packages/common/src/components/EntityEmptyState/index.tsx
Empty-state descriptors no longer store icons. The component resolves icons from ENTITY_ICONS, and its entity type excludes datasets.
Studio navigation icon adoption
web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.tsx, web/packages/studio/src/routes/groups/*, web/packages/studio/src/index.css
Workspace and grouped navigation entries use canonical entity icons. Lucide icons default to a stroke width of 1.

Suggested reviewers: a2bondar

Merge Risk: ⚪ Minimal · up to 88125

The change unifies entity icons across navigation and empty states while preserving compile-time coverage and existing behavior outside icon presentation. No actionable merge-blocking risk remains after normal checks and review.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: consolidating entity icons behind a canonical registry in Studio.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 1…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 19 files. (1 skipped: 1 unsupported.)

✨ 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 aahunt/entity-icon-registry

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant