Files
coder-server/site/AGENTS.md
2026-03-28 21:33:42 +00:00

14 KiB

Frontend Development Guidelines

TypeScript LSP Navigation (USE FIRST)

When investigating or editing TypeScript/React code, always use the TypeScript language server tools for accurate navigation:

  • Find component/function definitions: mcp__typescript-language-server__definition ComponentName
    • Example: mcp__typescript-language-server__definition LoginPage
  • Find all usages: mcp__typescript-language-server__references ComponentName
    • Example: mcp__typescript-language-server__references useAuthenticate
  • Get type information: mcp__typescript-language-server__hover site/src/pages/LoginPage.tsx 42 15
  • Check for errors: mcp__typescript-language-server__diagnostics site/src/pages/LoginPage.tsx
  • Rename symbols: mcp__typescript-language-server__rename_symbol site/src/components/Button.tsx 10 5 PrimaryButton
  • Edit files: mcp__typescript-language-server__edit_file for multi-line edits

Bash commands

  • pnpm dev - Start Vite development server
  • pnpm storybook --no-open - Start Storybook dev server
  • pnpm test:storybook - Run storybook story tests (play functions) via Vitest + Playwright
  • pnpm test:storybook src/path/to/component.stories.tsx - Run a single story file
  • pnpm test - Run jest unit tests
  • pnpm test -- path/to/specific.test.ts - Run a single test file
  • pnpm lint - Run complete linting suite (Biome + TypeScript + circular deps + knip)
  • pnpm lint:fix - Auto-fix linting issues where possible
  • pnpm playwright:test - Run playwright e2e tests. When running e2e tests, remind the user that a license is required to run all the tests
  • pnpm format - Format frontend code. Always run before creating a PR

Components

  • MUI components are deprecated - migrate away from these when encountered
  • Use shadcn/ui components first - check site/src/components for existing implementations.
  • Do not use shadcn CLI - manually add components to maintain consistency
  • The modules folder should contain components with business logic specific to the codebase.
  • Create custom components only when shadcn alternatives don't exist
  • Before creating any new component, search the codebase for existing implementations. Check site/src/components/ for shared primitives (Table, Badge, icons, error handlers) and sibling files for local helpers. Duplicating existing components wastes effort and creates maintenance burden.
  • Keep component files under ~500 lines. When a file grows beyond that, extract logical sections into sub-components or a folder with an index file.

Styling

  • Emotion CSS is deprecated. Use Tailwind CSS instead.
  • Use custom Tailwind classes in tailwind.config.js.
  • Tailwind CSS reset is currently not used to maintain compatibility with MUI
  • Responsive design - use Tailwind's responsive prefixes (sm:, md:, lg:, xl:)
  • Do not use dark: prefix for dark mode

Tailwind Best Practices

  • Group related classes
  • Use semantic color names from the theme inside tailwind.config.js including content, surface, border, highlight semantic tokens
  • Prefer Tailwind utilities over custom CSS when possible

General Code style

  • Use ES modules (import/export) syntax, not CommonJS (require)
  • Destructure imports when possible (eg. import { foo } from 'bar')
  • Prefer for...of over forEach for iteration
  • Biome handles both linting and formatting (not ESLint/Prettier)
  • Always use react-query for data fetching. Do not attempt to manage any data life cycle manually. Do not ever call an API function directly within a component.
  • Match existing patterns in the same file before introducing new conventions. For example, if sibling API methods use a shared helper like getURLWithSearchParams, do not manually build URLSearchParams. If sibling components initialize state with useMemo, don't switch to useState(initialFn) in the same file without reason.
  • Match errors by error code or HTTP status, never by comparing error message strings. String matching is brittle — messages change, get localized, or get reformatted.

TypeScript Type Safety

  • Never use as unknown as X double assertions. They bypass TypeScript's type system entirely and hide real type incompatibilities. If types don't align, fix the types at the source.
  • Prefer type annotations over as casts. When narrowing is needed, use type guards or conditional checks instead of assertions.
  • Avoid the non-null assertion operator (!.). If a value could be null/undefined, add a proper guard or narrow the type. If it can never be null, fix the upstream type definition to reflect that.
  • Use generated types from api/typesGenerated.ts for all API/server types. Never manually re-declare types that already exist in generated code — duplicated types drift out of sync with the backend.
  • If a component's implementation depends on a prop being present, make that prop required in the type definition. Optional props that are actually required create a false sense of flexibility and hide bugs.
  • Avoid // @ts-ignore and // eslint-disable. If they seem necessary, document why and seek a better-typed alternative first.

React Query Patterns

  • Query keys must nest under established parent key hierarchies. For example, use ["chats", "costSummary", ...] not ["chatCostSummary"]. Flat keys that break hierarchy prevent queryClient.invalidateQueries(parentKey) from correctly invalidating related queries.
  • When you don't need to await a mutation result, use mutate() with onSuccess/onError callbacks — not mutateAsync() wrapped in try/catch with an empty catch block. Empty catch blocks silently swallow errors. mutate() automatically surfaces errors through react-query's error state.

Accessibility

  • Every <table> / <Table> must have an aria-label or <caption> so screen readers can distinguish between multiple tables on a page.
  • Every element with tabIndex={0} must have a semantic role attribute (e.g., role="button", role="row") so assistive technology can communicate what the element is.
  • When hiding an interactive element visually (e.g., opacity-0, pointer-events-none), you must also remove it from the keyboard tab order and accessibility tree. Add tabIndex={-1} and aria-hidden="true", or better yet, conditionally render the element so it's not in the DOM at all. pointer-events: none only suppresses mouse/touch — keyboard and screen readers still reach the element.

Testing Patterns

  • Assert observable behavior, not CSS class names. In Storybook play functions and tests, use queries like queryByRole, toBeVisible(), or not.toBeVisible() — not assertions on class names like opacity-0. Asserting class names couples tests to the specific Tailwind/CSS technique and breaks when the styling mechanism changes without user-visible regression.
  • Use data-testid for test element lookup when an element has no semantic role or accessible name (e.g., scroll containers, wrapper divs). Never use CSS class substring matches like querySelector("[class*='flex-col-reverse']") — these break silently on class renames or Tailwind output changes.
  • Don't depend on behavior: "smooth" scroll in tests. Smooth scrolling is async and implementation-defined — in test environments, scrollTo may not produce native scroll events at all. Use behavior: "instant" in test contexts or mock the scroll position directly.
  • When modifying a component's visual appearance or behavior, update or add Storybook stories to capture the change. Stories must stay current as components evolve — stale stories hide regressions.

Robustness

  • When rendering user-facing text from nullable/optional data, always provide a visible fallback (e.g., "Untitled", "N/A", em-dash). Never render a blank cell or element.
  • When converting strings to numbers (e.g., Number(apiValue)), guard against NaN and non-finite results before formatting. For example, Number("abc").toFixed(2) produces "NaN".
  • When using toLocaleString(), always pass an explicit locale (e.g., "en-US") for deterministic output across environments. Without a locale, 1234 formats as "1.234" in de-DE but "1,234" in en-US.

Performance

  • src/pages/AgentsPage/ (including components/ChatElements/) is opted into React Compiler via babel-plugin-react-compiler. The compiler automatically memoizes values, callbacks, and JSX at build time. Do not add useMemo, useCallback, or memo() in these directories — the compiler handles it. The only exception is memo() on list-item components rendered in a .map() (e.g. ChatMessageItem, Tool, ChatTreeNode, LazyFileDiff) because the compiler does not add React.memo() behavior across component boundaries.
  • When adding state that changes frequently (scroll position, hover, animation frame), extract the state and its dependent UI into a child component rather than keeping it in a parent that renders a large subtree. This prevents React from re-rendering the entire subtree on every state change.
  • Throttle high-frequency event handlers (scroll, resize, mousemove) that call setState. Use requestAnimationFrame or a throttle utility. Even when React skips re-renders for identical state, the handler itself still runs on every frame (60Hz+).

Workflow

  • Be sure to typecheck when you're done making a series of code changes
  • Prefer running single tests, and not the whole test suite, for performance
  • Some e2e tests require a license from the user to execute
  • Use pnpm format before creating a PR
  • ALWAYS use TypeScript LSP tools first when investigating code - don't manually search files

Pre-PR Checklist

  1. pnpm check - Ensure no TypeScript errors
  2. pnpm lint - Fix linting issues
  3. pnpm format - Format code consistently
  4. pnpm test - Run affected unit tests
  5. Visual check in Storybook if component changes

Migration (MUI → shadcn) (Emotion → Tailwind)

Migration Strategy

  • Identify MUI components in current feature
  • Find shadcn equivalent in existing components
  • Create wrapper if needed for missing functionality
  • Update tests to reflect new component structure
  • Remove MUI imports once migration complete

Migration Guidelines

  • Use Tailwind classes for all new styling
  • Replace Emotion css prop with Tailwind classes
  • Leverage custom color tokens: content-primary, surface-secondary, etc.
  • Use className with clsx for conditional styling

React Rules

1. Purity & Immutability

  • Components and custom Hooks must be pure and idempotent—same inputs → same output; move side-effects to event handlers or Effects.
  • Never mutate props, state, or values returned by Hooks. Always create new objects or use the setter from useState.

2. Rules of Hooks

  • Only call Hooks at the top level of a function component or another custom Hook—never in loops, conditions, nested functions, or try / catch.
  • Only call Hooks from React functions. Regular JS functions, classes, event handlers, useMemo, etc. are off-limits.

3. React orchestrates execution

  • Don't call component functions directly; render them via JSX. This keeps Hook rules intact and lets React optimize reconciliation.
  • Never pass Hooks around as values or mutate them dynamically. Keep Hook usage static and local to each component.

4. State Management

  • After calling a setter you'll still read the previous state during the same event; updates are queued and batched.
  • Use functional updates (setX(prev ⇒ …)) whenever next state depends on previous state.
  • Pass a function to useState(initialFn) for lazy initialization—it runs only on the first render.
  • If the next state is Object.is-equal to the current one, React skips the re-render.

5. Effects

  • An Effect takes a setup function and optional cleanup; React runs setup after commit, cleanup before the next setup or on unmount.
  • The dependency array must list every reactive value referenced inside the Effect, and its length must stay constant.
  • Effects run only on the client, never during server rendering.
  • Use Effects solely to synchronize with external systems; if you're not "escaping React," you probably don't need one.
  • Never use useEffect to derive state from props or other state. If a value can be computed during render, use useMemo or a plain variable. A useEffect that reads state A and calls setState(B) on every change is a code smell — it causes an extra render cycle and adds unnecessary complexity.

6. Lists & Keys

  • Every sibling element in a list needs a stable, unique key prop. Never use array indexes or Math.random(); prefer data-driven IDs.
  • Keys aren't passed to children and must not change between renders; if you return multiple nodes per item, use <Fragment key={id}>
  • Never use key={String(booleanState)} to force remounts. When the boolean flips, React unmounts and remounts the component synchronously, killing exit animations (e.g., dialog close transitions) and wasting renders. Use a monotonically increasing counter or avoid key for this pattern entirely.

7. Refs & DOM Access

  • useRef stores a mutable .current without causing re-renders.
  • Don't call Hooks (including useRef) inside loops, conditions, or map(). Extract a child component instead.
  • Avoid reading or mutating refs during render; access them in event handlers or Effects after commit.

8. Element IDs

  • Use React.useId() to generate unique IDs for form elements, labels, and ARIA attributes. Never hard-code string IDs — they collide when a component is rendered multiple times on the same page.

9. Component Testability

  • When a component depends on a dynamic value like the current time or date, accept it as a prop (or via context) rather than reading it internally (e.g., new Date(), Date.now()). This makes the component deterministic and testable in Storybook without mocking globals.