Skip to main content

add-feature

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.

Informations de source

Dépôt
NVIDIA/nvcf
Dernière activité de la source
15 septembre 2026 à 22:48
Langue détectée de SKILL.md
anglais
Étoiles
229
Forks
85

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
add-feature
description
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.
license
Apache-2.0
allowed-tools
["Bash","Read","Edit"]
# 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>/`: ```text 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. ```tsx 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. ```tsx 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
Voir sur GitHub