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 import | Use instead | Why |
|---|---|---|
from 'zod' | from 'zod/v4' | We use Zod v4 API exclusively |
from 'clsx' | cn() from @/lib/utils | Single utility for class merging |
from 'tailwind-merge' | cn() from @/lib/utils | Same — 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/40withhover:bg-muted/60for interactive cards - Status dots:
size-1.5 rounded-fullwith 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-numson 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() { ... }