OpenClaw Swarm

Conventions

Enforced code patterns and ESLint rules

OpenClaw Swarm enforces project conventions through ESLint rules with zero warnings allowed. Every rule has a clear error message telling you exactly what to do instead.

Import restrictions

Banned importUse insteadWhy
from 'zod'from 'zod/v4'We use Zod v4 API exclusively
from 'clsx'cn() from @/lib/utilsSingle utility for class merging
from 'tailwind-merge'cn() from @/lib/utilsSame — cn() wraps both

No custom query hooks

useQuery, useMutation, useInfiniteQuery, and useSuspenseQuery must not be wrapped in custom hooks. Use them directly in components:

// Correct — direct in component
function GatewayList() {
  const { data } = useQuery(orpc.gateway.list.queryOptions())
  // ...
}

// Blocked by ESLint in src/hooks/
function useGateways() {
  return useQuery(orpc.gateway.list.queryOptions())
}

This keeps query keys colocated, makes cache invalidation obvious, and avoids unnecessary abstraction.

No React.FC

Use plain function components with typed props:

// Correct
function StatusBadge(props: StatusBadgeProps) { ... }

// Blocked by ESLint
const StatusBadge: React.FC<StatusBadgeProps> = (props) => { ... }

No console in Electron code

All logging in electron/ must go through the Logger interface from electron/logger/. Direct console.* calls are blocked by ESLint. See Logging for full details.

// In classes — use the injected logger
this.logger.info('connected', { gatewayId })
this.logger.error('request failed', err.message)

// In oRPC procedures — use context.logger
context.logger.info('handling request')

UI patterns

  • Card styling: bg-muted/40 with hover:bg-muted/60 for interactive cards
  • Status dots: size-1.5 rounded-full with semantic colors (emerald=connected, amber=connecting/pairing, destructive=auth-failed, muted-foreground/50=offline)
  • Stat labels: text-[0.625rem] font-medium text-muted-foreground uppercase tracking-wider
  • Monospace IDs: font-mono text-[0.625rem] for keys, device IDs, session keys
  • Tabular numbers: tabular-nums on all numeric displays
  • Empty states: Empty + EmptyHeader + EmptyMedia + EmptyTitle + EmptyDescription
  • shadcn/ui: add new components with bunx shadcn@latest add <name>, never copy-paste

Route files

Routes use TanStack Router's file-based convention. Never edit src/routeTree.gen.ts — it is auto-generated.

export const Route = createFileRoute('/dashboard/example/')({
  component: ExamplePage,
})
function ExamplePage() { ... }

On this page