Files
gpustack-ui/.claude/skills/create-crud-page/SKILL.md
T
jialinandjialin 6509f2a4ff docs: consolidate component conventions into CLAUDE.md and skill
- Add "Dynamic add-item form fields" guidance to CLAUDE.md (pick
  component by field schema: LabelSelector / ListInput / MetadataList)
- Fold StatusTag status-display recipe into create-crud-page skill
- Remove DESIGN.md (content migrated to the above)
2026-06-29 19:44:28 +08:00

5.5 KiB

name, description
name description
create-crud-page Scaffold a CRUD list/table page module in the gpustack-ui monorepo. Use when creating a new page module, building a list/table page, adding a create/edit drawer, or setting up the components/config/forms/hooks/services structure for a feature.

Create a CRUD Table Page

Inputs (do this first)

  • The argument passed to this skill is the module name (e.g. /create-crud-page api-keys → module api-keys). If no name was given, ask for it.

  • Always ask the user for the API documentation before generating any code, even if a module name was provided:

    Where is the API documentation for this module? (OpenAPI/Swagger URL, schema file path, or an interface description)

  • Wait for the answer, then read/fetch it. Derive config/types.ts (FormData, ListItem), the services request hooks, and form fields from that schema. Do not guess field names or endpoints — if the doc is missing details, ask.

  • Also ask which form layout to scaffold:

    Should the form use tabs? (1) a plain form without tabs, or (2) a tabbed form

    Choose the form structure in section 3 accordingly. Default to no tabs unless the user picks tabs or the schema clearly has many grouped sections.


Before anything: reuse common components, hooks, and utils from @gpustack/core-ui whenever possible.

Reference implementation for sections below: src/pages/model-routes.

Module structure

Create the module under src/pages/{module}:

{module}
├── components
├── config
├── forms
├── hooks
├── index.tsx
└── services

1. components

Module-specific components.

  • The create/edit form component is named add-xxx-modal.tsx (repo convention — keep the -modal suffix even though it is built with FormDrawer).
  • Use FormDrawer from @gpustack/core-ui.
  • If a table cell's render logic/structure is complex, extract it into xxx-cell.tsx.

2. config

config
├── index.ts   # static configs & constants
└── types.ts   # TypeScript types

Naming: form types → FormData; table list item types → ListItem.

3. forms

Main form component goes in forms/index.tsx.

  • Complex interactions (Form.Item split across components): create a dedicated Form Context and wrap with FormContext.Provider.
  • Tab-based forms: use ScrollSpyTabs from @gpustack/core-ui, wrapping the Form or FormContext.Provider. Do not use tabs unless necessary.
  • Required-field validation: use getRuleMessage for standard input/select.
  • For cascading selectors and async race protection, follow the form-patterns skill.

4. hooks

  • Table columns → use-xxx-columns.tsx.
  • Open/close hooks for add-xxx-modal.tsxuse-create-xxx.ts.

5. index.tsx (list page entry)

  • Data fetching: useTableFetch from @gpustack/core-ui.
  • Data display:
    • Standard table → Ant Design Table. Ref: src/pages/users/index.tsx.
    • Expandable/collapsible rows → Table from @gpustack/core-ui. Ref: src/pages/model-routes/index.tsx.
    • Card-style lists → use InfiniteScrollerProvider. Ref: src/pages/backends/index.tsx.

6. services

request is injected via a provider — do not create a centralized apis directory like in gpustack-ui. Define request hooks directly in services.

  • Use useRequest from @gpustack/core-ui, or useQueryData (same underlying method).
  • Ref: src/pages/gpu-service/storage-types/services/use-create-storage-type.ts.

7. Empty data

  • Page table lists → NoResult.
  • Simple (non-page) tables → Empty with image={Empty.PRESENTED_IMAGE_SIMPLE}.

Common UI conventions

  • Drawer/Modal open/close: use useBodyScroll from @gpustack/core-ui. Ref: src/pages/model-routes/hooks/use-create-route.ts.
  • Status display (success/failed/processing/warning): use StatusTag, never Tag from antd directly. See Status display below. Ref: src/pages/llmodels/components/table-list.tsx.
  • Permission-gated visibility: use Access / useAccess. Ref: src/pages/access/index.tsx.
  • Styles: avoid styled-components for complex/large styling. Prefer createStyles for component-scoped dynamic styles, CSS Modules (xxx.module.less) for static structured styles.

Status display

Use StatusTag from @gpustack/core-ui for status display. Do not use Tag from antd directly.

  1. Define the mapping from business status values to UI status values in the module's config/index.ts:
import { StatusMaps } from '@/config';
import { StatusType } from '@/config/types';

export const XxxStatusValueMap = {
  Running: 'running',
  Pending: 'pending',
  Failed: 'failed'
};

export const XxxStatusLabelMap: Record<string, string> = {
  [XxxStatusValueMap.Running]: 'Running',
  [XxxStatusValueMap.Pending]: 'Pending',
  [XxxStatusValueMap.Failed]: 'Failed'
};

export const status: Record<string, StatusType> = {
  [XxxStatusValueMap.Running]: StatusMaps.success,
  [XxxStatusValueMap.Pending]: StatusMaps.transitioning,
  [XxxStatusValueMap.Failed]: StatusMaps.error
};
  1. In table columns, pass only the UI status value and display text required by StatusTag:
<StatusTag
  statusValue={{
    status: status[value],
    text: XxxStatusLabelMap[value] || value,
    message: record.state_message
  }}
/>

statusValue.status must be a value mapped from StatusMaps (success, transitioning, warning, error, inactive). Do not pass business status values such as running or pending directly.