Files
platform/src/apps/CLAUDE.md
T
2026-02-16 19:34:35 +00:00

7.4 KiB

Frontend Apps

Shared patterns and conventions for all frontend applications (dashboard, editor, runtime).

UI Components

shadcn/ui (Base)

Location: src/workspaces/components/ui/

Standard shadcn/ui components with Tailwind CSS. Import via:

import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";

Custom Components

Location: src/workspaces/components/

Built on top of shadcn primitives:

  • Avatar.tsx - User avatars
  • Card.tsx - Custom card wrapper
  • Combobox.tsx - Searchable select
  • ColorPicker.tsx - Color selection
  • DataTable/ - Table with sorting, filtering, pagination (see below)
  • Dialogs/ - Common dialog patterns
  • ErrorDialogs/ - Error display dialogs
  • MetricCard.tsx - Stats display card
  • SearchInput.tsx - Search with debounce
  • Select.tsx - Enhanced select
  • Slider/ - Custom slider
  • Tabs/ - Enhanced tabs

DataTable + useDataControl

A complete data table solution with sorting, filtering, and pagination.

Pattern:

import { DataTable, useDataControl } from '@/components/DataTable';

function ExperimentsLibrary() {
  const { experiments } = useExperimentsList();
  const dataController = useDataControl<Experiment>(experiments || []);

  return (
    <>
      {/* Wire search input to controller */}
      <SearchInput
        value={dataController.searchQuery}
        handleSearch={dataController.setSearchQuery}
      />

      <DataTable<Experiment>
        dataController={dataController}
        pageSize={10}
        columns={[
          { field: 'id', label: 'ID', sortKey: 'id' },
          { field: 'name', label: 'Name', sortKey: 'name' },
          {
            field: 'status',
            label: 'Status',
            condition: view === 'All',  // conditional column
            format: ({ value, item }) => <Badge>{value}</Badge>
          },
          {
            label: 'Actions',
            format: ({ item }) => <ActionsCell experiment={item} />
          },
        ]}
      />
    </>
  );
}

useDataControl returns:

  • data - Current page of filtered/sorted data
  • rawData - Original unfiltered data
  • searchQuery, setSearchQuery - Search state
  • searchKeys, setSearchKeys - Which fields to search
  • sortedBy, setSortedBy, sortBy - Sort state
  • sortKey, sortDirection - Parsed sort info
  • currentPage, changePage, pageCount - Pagination
  • pageSize, setPageSize - Items per page
  • setCustomSort - Custom sort function

Column options:

  • field - Key in data object
  • label / labelMobile - Header text
  • sortKey - Enable sorting on this column
  • format - Custom render: ({ value, item, data, idx }) => ReactNode
  • condition - Show/hide column: boolean | () => boolean
  • tooltip - Header tooltip text
  • headerClassName / cellClassName - Styling

Features:

  • Auto-generates columns from data if not specified
  • Resets to page 1 on search/sort change
  • Search strips diacritics for accent-insensitive matching
  • Built-in pagination bar (shows when data exceeds pageSize)

Hooks

Location: src/workspaces/hooks/src/

Data Fetching - useClient

HTTP client with auth token handling:

const client = useClient();
const data = await client.get<User[]>("/users");
await client.post("/experiments", payload);

Methods: get, getText, getBlob, post, put, patch, delete

Auto-attaches Bearer token from localStorage/sessionStorage.

Forms - useForm

Custom form hook (not react-hook-form):

const { state, formRef, update, reset, isValid } = useForm<FormState>(
  initialState,
  validateFn
);

return <form ref={formRef}>...</form>;

Features:

  • Auto-syncs form inputs with state via name attribute
  • Handles checkboxes, radios, selects, number inputs
  • Nested object support via data attributes
  • Validation function support

Other Hooks

  • useAuth/ - Authentication state and methods
  • useDebounce - Debounced values
  • useDragAndDrop - Drag and drop functionality
  • useFullscreen/ - Fullscreen API wrapper
  • useIsMobile - Responsive breakpoint detection
  • useLocalStorageState - Persisted state
  • useMounted - Component mount status
  • usePopover - Popover state management
  • useTimeout - Timeout management
  • useTimer - Interval-based timer
  • useWebsockets - WebSocket connection

State Management

Uses React Query cache as both server and client state manager.

Global State - useGlobal

Uses React Query cache as a global state store (no Context providers needed):

const [value, setValue, refresh, reset] = useGlobal<string>("SIDEBAR_STATE", "expanded");

// Any component using the same key shares state and reacts to changes
setValue("collapsed");

How it works:

  • enabled: false + staleTime: Infinity = never fetches, just stores
  • Automatic re-renders when state changes
  • Visible in React Query DevTools
  • Supports functional updates: setValue(prev => ...)

URL Query State - useQueryState

Syncs state with URL query parameters:

const [page, setPage, reset, clear] = useQueryState<number>("page", 1);
const [filter, setFilter] = useQueryState<string>("filter", null, true); // isGlobal = true

Features:

  • Updates URL via history.replaceState (no page reload)
  • isGlobal = true: persists across navigation, uses useGlobal internally
  • isGlobal = false: local to component, uses useState
  • Auto-rebuilds URL when pathname changes (preserves global query state)
  • Type coercion based on defaultValue type

Server State

Domain-specific hooks in state/ directories wrap React Query:

// src/apps/dashboard/state/experiments/useExperiment.ts
const { data, isLoading } = useExperiment(experimentId);

When to Use What

Scenario Hook
API data Domain hooks (useExperiment, etc.)
Shared UI state useGlobal
URL-driven state (filters, pagination) useQueryState
Component-only state useState
Persisted to localStorage useLocalStorageState

Event Handlers

Always use ev for event parameters, not e:

// ✅ Good
onChange={(ev) => setName(ev.target.value)}
onKeyDown={(ev) => {
  if (ev.key === 'Enter') {
    ev.preventDefault();
    handleSubmit();
  }
}}
const handleSubmit = async (ev: React.FormEvent) => {
  ev.preventDefault();
  // ...
};

// ❌ Bad
onChange={(e) => setName(e.target.value)}

React 19 Patterns

RefObject includes null: In React 19, useRef returns RefObject<T | null>. Update prop types accordingly:

// ✅ Good - allow null in ref type
type PanelProps = {
  triggerRef: React.RefObject<HTMLButtonElement | null>;
};

// ❌ Bad - will error when passing useRef result
type PanelProps = {
  triggerRef: React.RefObject<HTMLButtonElement>;
};

Hook return types: Use ReturnType<typeof hookName> for typing hook returns in props:

import { useExperimentsList } from '@/state/experiments/useExperimentsList';

type TopHeaderProps = {
  manager: ReturnType<typeof useExperimentsList>;
};

Error Handling

Toast notifications via Sonner:

import { toast } from "sonner";

toast.error("Something went wrong");
toast.success("Saved successfully");

API errors automatically trigger via useClient.config.onError.

App-Specific Docs