How we write code, organize files, and keep things consistent.
| Tool | Version | Purpose |
|---|---|---|
| TypeScript | 5.8 | All application code. Strict mode enabled. |
| React | 19 | UI layer. Auto-JSX (no import React needed). |
| Vite | 7 | Bundler, dev server, MPA page generation. |
| Tailwind CSS | 4 | Styling. Utility-first with oklch color tokens. |
| ESLint | 9 | Flat config. Pragmatic rule set — enforces critical rules only. |
| Prettier | 3 | Formatting. No manual style debates. |
| Vitest | 3 | Unit and integration tests. jsdom environment. |
| Playwright | 1.55 | E2E browser tests. |
src/
├── app/ App shell, providers, router, initialization
├── components/ Shared UI components (not feature-specific)
├── config/ Product constants, API endpoints
├── features/ Independent feature modules (see below)
├── hooks/ Shared custom React hooks
├── i18n/ Translations and language utilities
├── layouts/ Page layout templates
├── lib/ Business logic, services, utilities
├── pages/ Route-level page components
├── stores/ Zustand stores (Yjs-backed)
├── styles/ Global CSS, Tailwind config
├── test/ Test files (mirrors src/ structure)
├── tools/ Tool plugin definitions
├── types/ Shared TypeScript type definitions
└── workers/ Web Worker scripts
Each feature in src/features/ is self-contained:
features/{name}/
├── components/ Feature-specific UI
├── hooks/ Feature-specific hooks
├── stores/ Feature-specific state (if needed)
├── lib/ Feature-specific logic
├── pages/ Feature routes (if any)
├── types/ Feature types
└── i18n/ Feature translations
Features import from src/lib/, src/stores/, and src/types/ but never from other features.
| Type | Pattern | Example |
|---|---|---|
| React component | PascalCase.tsx |
AgentCard.tsx |
| Hook | use{Name}.ts |
useThreads.ts |
| Store | {name}Store.ts |
agentStore.ts |
| Service / utility | kebab-case.ts |
context-broker.ts |
| Types | index.ts in types/ |
src/types/index.ts |
| Test | {name}.test.ts |
agent-store.test.ts |
| Translation | {lang}.ts in i18n/ |
i18n/en.ts |
- Components: PascalCase (
AgentAvatar,ThreadList) - Hooks: camelCase with
useprefix (useAgents,useLiveMap) - Store functions: camelCase verbs (
createAgent,deleteConversation) - Constants: SCREAMING_SNAKE for true constants, camelCase for config objects
- Types/Interfaces: PascalCase, no
Iprefix (Agent, notIAgent) - Enums: Prefer union types (
'light' | 'dark') over TypeScript enums
Yjs is the single source of truth. All persistent data is written to Yjs maps. Zustand stores are thin wrappers that provide React hooks.
// Store exports two things: write functions and read hooks
// 1. Write function — direct Yjs mutation
export function createAgent(data: AgentData): Agent {
const agent = { ...data, id: nanoid(), createdAt: new Date() }
agents.set(agent.id, agent)
return agent
}
// 2. Read hook — reactive Yjs observation
export function useAgents(): Agent[] {
return useLiveMap(agents).filter(a => !a.deletedAt)
}Entities are soft-deleted by setting deletedAt. Read hooks filter them out. This simplifies P2P sync and enables undo.
Space-scoped entities carry spaceId?: string. The default space matches undefined, '', or 'default'. Specific spaces require exact match.
HeroUI v2 is the primary component library. HeroUI v3 (beta) is installed for gradual migration. Do not mix v2 and v3 components on the same page.
When HeroUI component default styling are not enough, one can use Tailwind utility classes. No CSS modules. No styled-components.
// Good
<div className="flex items-center gap-2 rounded-lg bg-default-100 p-4">
// Avoid
<div style={{ display: 'flex', alignItems: 'center' }}>- Primary color scale (50-900) generated at runtime from user preference
- oklch color space for perceptual uniformity
- Dark mode via CSS class toggle (
.dark) - No hard-coded color values in components — always use tokens (
bg-primary,text-default-500)
- Never hardcode user-facing strings. All text goes through the i18n system.
- Apostrophes use
'(the HTML entity) instead of'in translation strings. - Per-component translations live in
i18n/subdirectories alongside the component. - Supported languages: English, French, German, Spanish, Arabic, Korean (Portuguese in some modules).
- RTL languages (Arabic) are handled via
<html dir="rtl">set at app init.
// i18n/en.ts
export default {
greeting: 'Hello',
agentCreated: 'Agent "{name}" has been created',
}
// Component
const { t } = useI18n()
return <p>{t('greeting')}</p>All new code in src/lib/ and src/stores/ must follow TDD:
- Red — Write a failing test
- Green — Minimal code to pass
- Refactor — Clean up, keep tests green
- Verify —
npm run test:coverage
| Directory | Target | Priority |
|---|---|---|
src/lib/** |
60%+ | Critical |
src/stores/** |
60%+ | Critical |
src/components/** |
30%+ | Medium |
src/pages/** |
20%+ | Low |
- Unit tests:
src/test/{category}/{name}.test.ts - E2E tests:
tests/e2e/{scenario}.spec.ts - Run:
npm run test:run(once),npm run test:watch(dev),npm run test:e2e(browser)
- Implement
LLMProviderInterfaceinsrc/lib/llm/providers/{name}.ts - Register in
src/lib/llm/index.ts - Add model definitions to
src/lib/llm/models.ts - Tool calling uses OpenAI function-calling format as the canonical schema
The agent runner executes tools iteratively (up to max_tool_iterations rounds). Each round: send messages → check for tool_calls → execute tools → append results → repeat until the model returns content without tool calls.
export const myTool = createToolPlugin({
metadata: {
name: 'my_tool',
displayName: 'My Tool',
shortDescription: 'Does a thing',
icon: 'Wrench',
category: 'utility',
tags: ['tag1'],
enabledByDefault: true,
estimatedDuration: 1000,
},
definition: {
type: 'function',
function: {
name: 'my_tool',
description: 'Does a thing',
parameters: { type: 'object', properties: { ... }, required: [...] },
},
},
handler: async (args, context) => { ... },
})Add the plugin to src/tools/plugins/index.ts. Tools are lazy-registered at first chat invocation.
- Commit messages: imperative mood, concise (
Add agent memory export, notAdded agent memory export feature) - Branch naming:
feature/short-description,fix/short-description - PRs: Summary + test plan
- Business logic errors: return structured results, never throw
- UI errors: caught by React ErrorBoundary at app root
- LLM failures: retry with exponential backoff, then graceful degradation
- Orchestration failures: mark task as failed, emit error event, continue other tasks
- Toast notifications for user-facing errors
- Never log credentials. ESLint rules enforce this in connector code.
- Encrypt at rest. API keys stored via Web Crypto API (AES-GCM).
- Sandbox extensions. All third-party code runs in iframes with a message bridge.
- No
eval. Code execution uses QuickJS (WebAssembly sandbox).
Related: ARCHITECTURE.md, DECISIONS.md