agentleFS
NVIDIAKnown publisherNVIDIA/nvcf/src/uis/nvcf-ui/.claude/skills/add-feature/SKILL.md

add-feature

Skill261 starsChanged 12 days agoApache-2.0View on GitHub

Add a new UI feature to the nvcf-ui app — folder structure, routing, data loading, lazy-loading, testing, and completion checklist. Use when creating a new feature folder, adding a page or view, wiring routes, building a new screen, or deciding whether to extend an existing feature vs create a new one. Also use when the user asks to build something new in the UI, even if they don't say "feature" explicitly.

26 other projects use a version of this.

Adopted by

Most recently taken up by

The file

Show its rules and text

We split this skill into the rules it gives an agent

15 rules. 0 are common, 3 are distinctive, and 12 are very specific to this project.

  • 0

    Common

    Most of the 24 edited versions we read keep these.

  • 3

    Distinctive

    This author's own choices. Worth a look.

  • 12

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    All source files need Apache 2.0 license headers.

    line 27
  2. Very specific:

    Not every subfolder is required — add components/, hooks/, types/ only when needed

    line 49
  3. Very specific:

    Use generated Zod schemas for validateSearch when params map to API query params.

    line 81
  4. Very specific:

    Use suspense hook variants (useGetWidgetsSuspense) — the loader already ensured data, so these resolve immediately or suspend if cache is …

    line 104
  5. Very specific:

    Tabbed views: use a layout route with <Outlet />, each tab is a child route with its own loader and lazy-loaded component.

    line 118
  6. Distinctive:

    Check for existing components/patterns that could be reused or graduated before building new ones

    line 122
  7. Very specific:

    Create features/<name>/ with the relevant files — a view component and routes.tsx if the feature has routes, or just components//hooks/ if …

    line 123
  8. Very specific:

    If the feature has routes: define routes in routes.tsx using createRoute with .lazy(), then wire the subtree into router.tsx.

    line 124
  9. Distinctive:

    Use generated hooks and query options for data fetching

    line 125
  10. Very specific:

    Add tests for view components and any non-trivial logic.

    line 126
  11. Very specific:

    Add MSW scenarios in mocks/scenarios/<name>/ for key UI states

    line 127
  12. Very specific:

    Run task license:add to apply license headers to new files

    line 128
  13. Very specific:

    Run task ui:lint:fix and fix any errors

    line 132
  14. Very specific:

    Run task ui:test and verify tests pass

    line 133
  15. Distinctive:

    If either fails, fix the issues and rerun until clean

    line 134

“Common” is measured: kept by at least 6 in 10 of the 24 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Adding a UI Feature

For all component selection, layout decisions, and styling — invoke the kaizen-ui skill.

Gotchas

  • This project uses code-based routing, not file-based. Do not use createFileRoute or the TanStack Router Vite plugin — use createRoute and createLazyRoute.
  • View components are named by view type (e.g. WidgetsList, WidgetDetail), never with a Page suffix.
  • router.tsx is pure composition — it only imports and wires route trees. Do not put route definitions or loaders in router.tsx.
  • rootRoute.tsx is separate from router.tsx to avoid circular imports. Layout routes that span multiple features live at src/ root, not in a feature folder.
  • Generated hooks, schemas, and mocks live in ui/src/generated/ — never edit these files. If the endpoint you need doesn't have generated hooks, update the appropriate spec in spec/ (control-plane-openapi.yaml, nvcf-openapi.yaml, or sis-openapi.yaml) then run task generate.
  • All source files need Apache 2.0 license headers. Run task license:add before committing.

When to Create a New Feature Folder

  • New domain (new data, new concepts, new API surface) → new feature folder
  • Composition of existing domains (e.g. a home page assembling widgets) → new thin feature folder
  • Extension of an existing domain (e.g. a "Create Function" page) → new route/component in the existing feature folder

Feature Folder Structure

A feature is a domain boundary, not a route. Each feature is self-contained under ui/src/features/<name>/:

features/<name>/
├── components/         # Smaller building blocks (badges, cards)
├── hooks/              # Feature-scoped hooks
├── types/              # Feature-scoped types
├── routes.tsx          # Route definitions, loaders, search validation (optional)
├── <View>.tsx          # Route-level view components (PascalCase, named by view type)
└── utils.ts            # Feature-scoped helpers (when needed)
  • Not every subfolder is required — add components/, hooks/, types/ only when needed
  • A feature may not have routes at all — it may only export hooks and components for other features
  • Helpers go inline in the component file by default. Extract to a feature-root file only when shared across multiple files in the same feature

Route Splitting Pattern

Each route is split across two files:

routes.tsx — route definition, search params, loader. In the main bundle.

import { createRoute } from "@tanstack/react-router";
import { appLayoutRoute } from "~/appLayout";
import { queryClient } from "~/lib/queryClient";
import { getGetWidgetsQueryOptions } from "~/generated/api/widgets/widgets";
import { GetWidgetsParams } from "~/generated/model/getWidgetsParams.zod";

const widgetsRoute = createRoute({
  getParentRoute: () => appLayoutRoute,
  path: "widgets",
  validateSearch: GetWidgetsParams.catch({ status: "active" }),
  loaderDeps: ({ search }) => search,
  loader: ({ deps }) =>
    queryClient.ensureQueryData({
      ...getGetWidgetsQueryOptions(deps),
      revalidateIfStale: true,
    }),
}).lazy(() => import("./WidgetsList").then((m) => m.WidgetsListRoute));

export const widgetsRouteTree = widgetsRoute;
  • Use generated Zod schemas for validateSearch when params map to API query params. For UI-only search params, define inline.
  • loaderDeps declares which search params the loader depends on — the loader re-runs when these change.
  • ensureQueryData with revalidateIfStale: true avoids flashing stale content.
  • For multiple data sources: ensureQueryData (awaited) for primary data, prefetchQuery (no await) for below-the-fold data.

<View>.tsx — lazy-loaded component, pending, and error states.

import { createLazyRoute } from "@tanstack/react-router";
import { useGetWidgetsSuspense } from "~/generated/api/widgets/widgets";

function WidgetsList() {
  const { status } = widgetsRoute.useSearch();
  const { data } = useGetWidgetsSuspense({ status });
  // render...
}

export const WidgetsListRoute = createLazyRoute("/widgets")({
  component: WidgetsList,
  pendingComponent: () => <div>Loading...</div>,
});
  • Use suspense hook variants (useGetWidgetsSuspense) — the loader already ensured data, so these resolve immediately or suspend if cache is stale.
  • For prefetched (non-blocking) data, wrap the consumer in a <Suspense> boundary with a fallback.

Lazy-Loading Rules

  • Page routes with view code → always lazy-load via createLazyRoute
  • Layout routes that just render <Outlet /> → keep in main bundle
  • Components within a page → never lazy-load; already in the route's chunk

Layout Routes

  • Single feature → in that feature's routes.tsx
  • Multiple features → at src/ root alongside rootRoute.tsx

Tabbed views: use a layout route with <Outlet />, each tab is a child route with its own loader and lazy-loaded component.

Steps

  1. Check for existing components/patterns that could be reused or graduated before building new ones
  2. Create features/<name>/ with the relevant files — a view component and routes.tsx if the feature has routes, or just components//hooks/ if routeless
  3. If the feature has routes: define routes in routes.tsx using createRoute with .lazy(), then wire the subtree into router.tsx. If it introduces a new top-level route, add a NavItem entry in rootRoute.tsx
  4. Use generated hooks and query options for data fetching
  5. Add tests for view components and any non-trivial logic. Use renderWithRouter from ~/testing/render for route-level views, standard render for standalone components
  6. Add MSW scenarios in mocks/scenarios/<name>/ for key UI states
  7. Run task license:add to apply license headers to new files

Validate Before Finishing

  1. Run task ui:lint:fix and fix any errors
  2. Run task ui:test and verify tests pass
  3. If either fails, fix the issues and rerun until clean

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.