Skip to content

feat(studio): standardize guardrails empty state and add ui-design skill - #1268

Open
aahunt-nv wants to merge 6 commits into
mainfrom
astd-394-empty-states-ui-skill/aahunt
Open

feat(studio): standardize guardrails empty state and add ui-design skill#1268
aahunt-nv wants to merge 6 commits into
mainfrom
astd-394-empty-states-ui-skill/aahunt

Conversation

@aahunt-nv

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

Copy link
Copy Markdown
Contributor

Summary

Phase 1 of 2

Standardizes Studio empty states behind one shared primitive. Adds an EntityEmptyState component + entity registry in @nemo/common and wires the guardrails list onto it. Scoped intentionally to /workspaces/:name/guardrails to verify the pattern in one place before migrating the remaining ~27 callsites.

Also adds a ui-design agent skill (under web/.agents/skills/) whose empty-states reference documents the pattern.

  • Before: guardrails used a hand-rolled TableEmptyState with ad-hoc icon sizing and title-case copy, and the first-use state rendered no create CTA.
  • After: a standardized empty state with a create CTA and a filters-aware no-results state.

Screenshots

first-use no-results
entity-empty-state-first-use entity-empty-state-no-results

Related Issue

Linear: ASTD-394 (no linked GitHub issue).

Changes

  • Add EntityEmptyState component + ENTITY_EMPTY_STATES registry in @nemo/common (first-use / no-results variants; registry-driven copy, icon, CLI command, and skill prompt; "nemo CLI · Ask an Agent" CodeSnippet + SegmentedControl self-service help).
  • Migrate GuardrailsDataView onto it via the DataView renderEmptyState({ hasFiltersApplied, hasSearchApplied }) hook; keep renderErrorState on ErrorPanel with getErrorMessage(error).
  • Wire the guardrails create modal to the empty-state CTA (onCreate on GuardrailsRoute); drop the old emptyStateActions prop.
  • Add Storybook stories for the two variants.
  • Add the ui-design skill + empty-states reference.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with documentation updates
  • Documentation only
  • Contributor tooling or automation
  • CI, build, or test infrastructure

Quality Gates

  • Tests added or updated for changed behavior
  • Existing tests cover changed behavior — justification:
  • Tests not applicable — justification:
  • Documentation updated for user-visible behavior
  • Documentation not applicable — justification:

Verification

  • Pull request title follows the repository's Conventional Commit format
  • Every commit includes an appropriate Signed-off-by: trailer
  • uv run pre-commit run -a passes, or any blocked checks are identified below
  • Targeted tests pass, or tests are marked not applicable above
  • No secrets, API keys, or credentials are included

Targeted validation:

  • pnpm --filter @nemo/common typecheck — pass
  • pnpm --filter nemo-studio-ui typecheck — pass
  • pnpm --filter @nemo/common test EntityEmptyState — pass (5 tests: first-use heading/subheading, CLI↔Agent toggle, create CTA, CTA omission, no-results clear-filters)
  • pnpm --filter nemo-studio-ui test GuardrailsDataView — pass (2 files, 14 tests; incl. error-panel renders on request failure)
  • ESLint + Prettier on changed files — clean
  • Visual check via Storybook (Common/EntityEmptyState): first-use and no-results render as intended (see Screenshots).
  • uv run pre-commit run -a — all code hooks pass (ruff, ruff format, ty typechecks, config-reference, uv-lock-check drift, UI lint-staged, copyright headers, plugin-import guard, merge-conflict). Two hooks fail only on local tooling and are unrelated to this web-only diff (no Helm or pyproject/uv.lock changes; working tree unmodified by the run): helm-docs (binary not installed locally) and uv-lock (local uv 0.11.29 ≠ pinned 0.9.14; the separate uv-lock-check drift hook passes). CI runs both with correct tooling.

Adds a ui-design agent skill under web/.agents/skills that routes UI
changes to standardized references. First reference documents the
EntityEmptyState empty-state pattern (ASTD-394).

Signed-off-by: Aaron Hunt <aahunt@nvidia.com>
Add the shared EntityEmptyState component + entity registry in
@nemo/common (first-use / no-results / error variants, centralized copy,
CLI command and agent-prompt copy rows) and wire the guardrails list onto
it. Scoped to /workspaces/:name/guardrails to verify in one place before
migrating the remaining callsites (ASTD-394).

Signed-off-by: Aaron Hunt <aahunt@nvidia.com>
…help

Replace the custom copy rows in EntityEmptyState with a single KUI
CodeSnippet (built-in copy) whose slotActions hosts a tiny SegmentedControl
toggling between the NeMo CLI command and the Ask an Agent prompt. Update
the ui-design empty-states reference to match.

Signed-off-by: Aaron Hunt <aahunt@nvidia.com>
Wrap the SegmentedControl in a full-width Flex inside the CodeSnippet
slotActions so the nemo CLI / Ask an Agent toggle left-aligns above the
command, size it tiny, and align the ui-design reference label casing.

Signed-off-by: Aaron Hunt <aahunt@nvidia.com>
@github-actions github-actions Bot added the feat label Aug 12, 2026
EntityEmptyState now covers only first-use and no-results; the error
variant is removed so failed loads keep routing through ErrorPanel with
getErrorMessage(error), surfacing the real failure instead of hardcoded
generic copy. Updates the GuardrailsDataView error branch, tests, and the
ui-design empty-states reference to match.

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

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 32236/40906 78.8% 63.7%
Integration Tests 18646/38832 48.0% 20.7%

Adds first-use and no-results stories for EntityEmptyState so the two
governed empty-state variants render in isolation for visual review.

Signed-off-by: Aaron Hunt <aahunt@nvidia.com>
@aahunt-nv
aahunt-nv marked this pull request as ready for review August 12, 2026 23:30
@aahunt-nv
aahunt-nv requested review from a team as code owners August 12, 2026 23:30
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a shared EntityEmptyState component and registry for Studio. It documents integration rules, adds tests and Storybook stories, and replaces the Guardrails data-view empty-state branches with first-use and no-results variants.

Changes

Empty-state standardization

Layer / File(s) Summary
Empty-state implementation guidance
web/.agents/skills/ui-design/SKILL.md, web/.agents/skills/ui-design/references/empty-states.md
Documents shared variants, registry descriptors, DataView integration, error handling, testing, and migration rules.
Shared component and registry
web/packages/common/src/components/EntityEmptyState/index.tsx, web/packages/common/src/components/EntityEmptyState/registry.ts, web/packages/common/src/components/EntityEmptyState/EntityEmptyState.test.tsx, web/packages/common/src/components/EntityEmptyState/EntityEmptyState.stories.tsx
Adds EntityEmptyState, registry metadata, self-service help, callback actions, tests, and Storybook stories.
Guardrails data-view integration
web/packages/studio/src/components/dataViews/GuardrailsDataView/index.tsx, web/packages/studio/src/components/dataViews/GuardrailsDataView/GuardrailsDataView.test.tsx, web/packages/studio/src/routes/guardrails/GuardrailsRoute/index.tsx
Derives first-use or no-results, wires create and filter-reset actions, opens the creation modal, and updates assertions.

Sequence Diagram(s)

sequenceDiagram
  participant GuardrailsRoute
  participant GuardrailsDataView
  participant EntityEmptyState
  participant GuardrailCreateModal
  GuardrailsRoute->>GuardrailsDataView: pass onCreate handler
  GuardrailsDataView->>EntityEmptyState: pass entity and derived variant
  EntityEmptyState->>GuardrailsRoute: invoke create callback
  GuardrailsRoute->>GuardrailCreateModal: open creation modal
Loading

Suggested reviewers: steramae-nvidia

Mergeability Score: 🔵 Low · up to 25e96

The guardrails empty-state migration is localized and covered by passing checks, but the added guidance could lead to incorrect future integrations and the no-results API permits omitting its clear-filters action. The PR is mergeable with explicit owner awareness and follow-up.

🚥 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 summarizes the two main changes: standardized guardrails empty states and added the ui-design skill.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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 astd-394-empty-states-ui-skill/aahunt

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

@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: 3

🧹 Nitpick comments (1)
web/packages/studio/src/components/dataViews/GuardrailsDataView/GuardrailsDataView.test.tsx (1)

25-36: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the onCreate callback.

The helper supplies a mock, but the test checks only button presence. The test can pass if GuardrailsDataView ignores onCreate and EntityEmptyState uses its registry fallback. Pass a spy, click Create guardrail config, and assert that the spy was called. The supplied EntityEmptyState contract permits this fallback, so button presence alone does not prove callback wiring.

Also applies to: 104-108

🤖 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/packages/studio/src/components/dataViews/GuardrailsDataView/GuardrailsDataView.test.tsx`
around lines 25 - 36, Update the GuardrailsDataView test helper and relevant
test to use an explicit onCreate spy, click the “Create guardrail config”
action, and assert the spy was called. Preserve the existing button-presence
assertion while ensuring the test verifies GuardrailsDataView wires onCreate
through to EntityEmptyState rather than relying on its registry fallback.
🤖 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/.agents/skills/ui-design/references/empty-states.md`:
- Around line 69-71: Update the createAction guidance in the empty-state
reference to use onCreate for imperative or modal-driven creation and omit to in
that case; do not instruct callsites to pass onClick, since
EmptyStateCreateAction does not define that field.

In
`@web/packages/common/src/components/EntityEmptyState/EntityEmptyState.test.tsx`:
- Line 9: Update the React import in the EntityEmptyState test to use a
type-only import for FC and ReactNode, since both symbols are used exclusively
as types.

In `@web/packages/common/src/components/EntityEmptyState/index.tsx`:
- Around line 23-34: Update EntityEmptyStateProps to a discriminated union keyed
by variant: require onClearFilters when variant is 'no-results', while keeping
it unavailable or optional for other variants. Preserve the existing onCreate
behavior and variant-specific constraints.

---

Nitpick comments:
In
`@web/packages/studio/src/components/dataViews/GuardrailsDataView/GuardrailsDataView.test.tsx`:
- Around line 25-36: Update the GuardrailsDataView test helper and relevant test
to use an explicit onCreate spy, click the “Create guardrail config” action, and
assert the spy was called. Preserve the existing button-presence assertion while
ensuring the test verifies GuardrailsDataView wires onCreate through to
EntityEmptyState rather than relying on its registry fallback.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 32d3995f-8010-4de5-b380-f4b3397b7e11

📥 Commits

Reviewing files that changed from the base of the PR and between 5f6569d and 25e969a.

📒 Files selected for processing (9)
  • web/.agents/skills/ui-design/SKILL.md
  • web/.agents/skills/ui-design/references/empty-states.md
  • web/packages/common/src/components/EntityEmptyState/EntityEmptyState.stories.tsx
  • web/packages/common/src/components/EntityEmptyState/EntityEmptyState.test.tsx
  • web/packages/common/src/components/EntityEmptyState/index.tsx
  • web/packages/common/src/components/EntityEmptyState/registry.ts
  • web/packages/studio/src/components/dataViews/GuardrailsDataView/GuardrailsDataView.test.tsx
  • web/packages/studio/src/components/dataViews/GuardrailsDataView/index.tsx
  • web/packages/studio/src/routes/guardrails/GuardrailsRoute/index.tsx

Comment on lines +69 to +71
- `createAction?` — **omit** for entities with no in-app create flow (e.g.
Agents, Members). Use `to` for route navigation, `onClick` for imperative
flows. Renders as `<Button color="brand">`.

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the imperative-create instruction.

EmptyStateCreateAction has no onClick field. Future registry entries that follow this instruction will fail type checking. Instruct callsites to pass onCreate and omit to for modal-driven creation.

🤖 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/.agents/skills/ui-design/references/empty-states.md` around lines 69 -
71, Update the createAction guidance in the empty-state reference to use
onCreate for imperative or modal-driven creation and omit to in that case; do
not instruct callsites to pass onClick, since EmptyStateCreateAction does not
define that field.

import { ToastProvider } from '@nemo/common/src/providers/toast/ToastProvider';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { FC, ReactNode } from 'react';

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use a type-only React import.

FC and ReactNode are used only as types. Import them with import type.

🤖 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/packages/common/src/components/EntityEmptyState/EntityEmptyState.test.tsx`
at line 9, Update the React import in the EntityEmptyState test to use a
type-only import for FC and ReactNode, since both symbols are used exclusively
as types.

Comment on lines +23 to +34
export interface EntityEmptyStateProps {
entity: EntityKey;
variant: EntityEmptyStateVariant;
/**
* Overrides the registry create action's handler (e.g. opens a create modal).
* When omitted, a `createAction.to` route is navigated to instead.
* `first-use` only.
*/
onCreate?: () => void;
/** Clears the active filters/search. `no-results` only. */
onClearFilters?: () => void;
className?: string;

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Require onClearFilters for no-results.

The optional callback permits a no-results state with no required “Clear filters” action. Model the props as a discriminated union so variant: 'no-results' requires onClearFilters.

🤖 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/packages/common/src/components/EntityEmptyState/index.tsx` around lines
23 - 34, Update EntityEmptyStateProps to a discriminated union keyed by variant:
require onClearFilters when variant is 'no-results', while keeping it
unavailable or optional for other variants. Preserve the existing onCreate
behavior and variant-specific constraints.

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