Files
gpustack-ui/CLAUDE.md
T

7.0 KiB

Repo

This is the open source UI (gpustack-ui). Common components, hooks, and utils are published as @gpustack/core-ui and consumed throughout src.

Always prioritize reusing common components, hooks, and utils from @gpustack/core-ui.

Task-specific conventions live in skills: use create-crud-page when building a page module, form-patterns when building cascading/dependent forms.

React State and Request Patterns

Keep data flow explicit, predictable, and performant. The triggering action is the source of truth for UI updates — not effect-driven synchronization.

1. Avoid effect-driven requests

Do not use request functions as useEffect dependencies. Trigger requests explicitly from user actions or lifecycle entry points.

// Avoid
useEffect(() => {
  fetchData();
}, [fetchData]);

2. Form requests should be action-driven

  • Fetch form data (e.g. Select options) when the form first opens.
  • If later requests depend on interactions, trigger them inside the interaction handler.
  • Do not rely on useEffect dependency changes.
// Recommended
const handleOnChange = (value) => {
  fetchData(value);
};

When one action updates multiple related states, update them all directly in the handler. Do not sync via useEffect or derive indirectly via useMemo.

const handleOnChange = (value) => {
  setState1(...);
  setState2(...);
  buildState(...);
};

If multiple states always update together, use a single state object instead of multiple useState calls — fewer rerenders, more predictable transitions.

const [state, setState] = useState({ state1: ..., state2: ..., state3: ... });

5. Prefer explicit state flow

Keep request execution, state updates, and derived calculations close to the triggering action. Avoid chaining business logic through multiple useEffect hooks.

// Prefer
const handleAction = () => {
  fetchData();
  setTableData(...);
  setSelectedRow(...);
};

6. Avoid premature memoization

Do not use useMemo / useCallback unless there is a confirmed bottleneck. Overuse adds complexity, obscures state flow, and risks stale dependencies. Optimize only when necessary.

7. Keep request logic predictable

A user interaction should clearly show: what request fires, which states update, how the UI changes. Avoid indirect update chains from dependency-driven effects.

8. Prefer action-driven architecture

Prefer action-driven updates, explicit handlers, and localized state transitions over effect-driven synchronization, cross-hook implicit updates, and reactive chains between states.

Styles

Future direction (apply to all new code): avoid styled-components. Prefer:

  1. createStyles for component-scoped dynamic styles
  2. CSS Modules (xxx.module.less) for structured static styles

Existing styled-components usage is legacy tech debt — do not migrate it wholesale, but do not add new styled-components either. Theme tokens (var(--ant-color-*)) work in all three approaches.

Layout

Compose layout with Ant components, not hand-written display: flex.

  • 1D flex (row/column with gap, align, justify) → Flex. Do not write raw display: flex in new code.
  • Inline sequence of a few elements with uniform spacing → Space.
  • Page/grid columnsRow / Col.

Drive spacing with the theme scale (Flex/Space gap, or var(--ant-*) spacing tokens), not scattered px literals.

Naming conventions

A page module lives under src/pages/{module} with this sub-structure: components/, config/, forms/, hooks/, services/, index.tsx. File naming:

  • Create/edit modal: add-{feature}-modal.tsx (keep the -modal suffix even when built with FormDrawer).
  • Table columns hook: use-{feature}-columns.tsx.
  • Open/close & request hooks: use-{verb}-{noun}.ts (e.g. use-create-user.ts, use-query-user-list.ts).
  • Complex table cell: extract into {feature}-cell.tsx.

Config & types

  • config/types.ts — TypeScript types. Form shape → FormData; table/list row → ListItem.
  • config/index.ts — static constants, enums, and value/label maps (e.g. XxxStatusValueMap, XxxStatusLabelMap). Keep constants out of types.ts.
  • Select options that need i18n: set label to the message key and add locale: true on the option — the field translates it at render. Omit locale for options whose label is already final text. Ref src/pages/benchmark/config/index.ts.

Common components

Always check @gpustack/core-ui first. Frequently reused:

  • Drawer/Modal open/close: useBodyScroll.
  • Form drawer / footer: FormDrawer, ModalFooter.
  • Delete confirmation: DeleteModal.
  • Search + bulk actions bar: FilterBar.
  • Form fields: BaseSelect, Input (labeled).
  • Text overflow: AutoTooltip.
  • Icons: IconFont.
  • Tags & status (4 variants): see the section below.
  • Permission-gated visibility: Access / useAccess.
  • Request hooks: useRequest / useQueryData / useQueryDataList.
  • Table data fetching: useTableFetch.
  • Submit guard (prevent double-submit): useSubmitLock.
  • Tabbed forms: ScrollSpyTabs.

Tags & status indicators

Four core-ui components cover tag/status display in tables and lists. Pick by what the value means, not by how it looks — don't reach for a generic antd Tag:

  • StatusTag — semantic status with a dynamic message/detail (tooltip, download, extra content). Use when a row's status carries variable text, e.g. a failed job with an error message. Colors come from StatusColorMap (error/warning/transitioning/success/inactive).
  • StatusDot — colored dot + short label, no message. Use for a plain status/type cell where the value is a fixed enum (e.g. an event-type or log column). Same StatusColorMap palette; inactive dot is quaternary. If the status needs dynamic text, use StatusTag instead.
  • ThemeTag — a standalone category label (independent content, e.g. a permission scope or a model name). Default neutral; wraps antd Tag.
  • TextAttribute — a small neutral pill that is a subordinate annotation following a primary text (e.g. key-name [custom]), not a standalone tag. Manages its own leading margin. Two variants: filled (default) and outlined. Ref the name column in src/pages/api-keys/hooks/use-keys-columns.tsx.

Rule of thumb: semantic + dynamic text → StatusTag; semantic + fixed enum → StatusDot; independent category → ThemeTag; annotation of nearby text → TextAttribute.

Dynamic add-item form fields

When building a form, select the add-item component from the shape of the field's data (its schema). Match the schema, don't hand-roll a list UI:

  • Plain object (key→value map) → LabelSelector.
  • String arrayListInput. Ref src/pages/llmodels/forms/backend-parameters-list.tsx.
  • Object arrayMetadataList with a custom item renderer per entry. Ref src/pages/llmodels/forms/model-lora-list.tsx.