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

269 lines
7.8 KiB
Markdown

# CLAUDE.md
## Project Overview
Officer is an AI-assisted personal backend and frontend for life management.
## Monorepo Structure
```
src/
├── apps/
│ ├── dashboard/ # Main UI (React 19)
│ └── editor/ # Visual editor
├── servers/
│ ├── api/ # REST API (Hono, port 5000)
│ └── tracking/ # Event collection (port 5001)
├── databases/
│ ├── officer_db/ # Main app data
│ └── ephemeral_db/ # Cache & temporary data
└── workspaces/ # Shared: components, hooks, helpers, types, config, etc.
```
**Path aliases**: `@/` → dashboard, `@@/` → servers
## Tech Stack
- **Runtime**: Bun
- **Language**: TypeScript (strict), some raw SQL
- **Frontend**: React 19, React Router, React Query + Context
- **UI**: shadcn/ui base + custom components, Tailwind CSS
- **Backend**: Hono framework
- **Database**: PostgreSQL, Drizzle ORM + raw SQL
## Code Style
- **Paradigm**: Functional - pure functions, immutability, composition
- **TypeScript**: Strict - no `any`, proper types everywhere
- **Comments**: Minimal - code should be self-documenting
- **Async**: Always async/await
- **Errors**: Try/catch (detailed patterns in section-specific docs)
## TypeScript Patterns
### Imports
Order imports as: types → external → workspace → relative
```ts
import type { Experiment, User } from 'types';
import { useState, useEffect } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useClient } from 'hooks/useClient';
import { formatDate } from 'helpers/formatters';
import { useWebsites } from '../useWebsites';
```
**Type-only imports**: With `verbatimModuleSyntax` enabled, types must use type-only imports:
```ts
// ✅ Good - inline type import
import { ActionModals, type ActionModalsTypes } from './ActionModals';
// ✅ Good - separate type import
import type { FormEvent } from 'react';
import { useState } from 'react';
// ❌ Bad - will error with verbatimModuleSyntax
import { ActionModals, ActionModalsTypes } from './ActionModals';
```
### Types
- Prefer `type` over `interface`
- Shared types go in `workspaces/types` - re-exports from database types
- Database types live in their respective `db/types.ts` files
- Component props: colocated as named type
```ts
type ExperimentCardProps = {
experiment: Experiment;
onSelect: (id: number) => void;
};
```
### Database Types
Use Drizzle-inferred types, never manual type definitions in schema files:
```ts
// In officerdb/types.ts
import * as Schema from './schema';
// Simple table - just Select/Insert
export type Screenshot = typeof Schema.Screenshots.$inferSelect;
export type ScreenshotInsert = typeof Schema.Screenshots.$inferInsert;
// Table with relations - Select + extended type for API responses
export type UserSelect = typeof Schema.Users.$inferSelect;
export type UserInsert = typeof Schema.Users.$inferInsert;
export type User = UserSelect & {
company: Company;
passkeys: Passkey[];
};
```
**In app code**, import types from `'types'` (the workspace re-exports everything):
```ts
// ✅ Good - in apps/dashboard, apps/editor, etc.
import type { User, Experiment, VariantStat } from 'types';
// ❌ Bad - don't import directly from database packages in app code
import type { User } from 'officerdb/types';
import type { VariantStat } from 'statisticsdb/types';
```
**In database/server code**, import from the specific package:
```ts
// ✅ Good - in servers/, databases/
import type { User } from 'officerdb/types';
```
### Functions
- Arrow functions for simple/one-liners
- Regular functions for complex logic
- Named exports only (no default exports)
- **No multiline parameter definitions** - extract params type when signature gets long
```ts
// Simple utility - arrow
export const formatDate = (value: number) => new Date(value).toLocaleDateString();
// Complex logic - regular function
export function calculateStatistics(data: DataPoint[]) {
// multi-line logic
}
// ❌ Bad - multiline params
export function processData(
body: Record<string, unknown> | undefined,
query: Record<string, string | undefined>,
): Result { ... }
// ✅ Good - extract params type
type ProcessDataParams = {
body: Record<string, unknown> | undefined;
query: Record<string, string | undefined>;
};
export function processData({ body, query }: ProcessDataParams): Result { ... }
```
### Null Handling
- Early returns for guards
- Optional chaining for property access
- Use `?? undefined` to convert `null` to `undefined` for props expecting `string | undefined`
- Use `?? 0` or `?? ''` for fallback values with state setters
```ts
function processExperiment(exp: Experiment | null) {
if (!exp) return null; // guard
const websiteName = exp.website?.name ?? 'Unknown'; // access
// ...
}
// Converting null to undefined for props
<AvatarImage src={user.avatar ?? undefined} /> // avatar is string | null, src expects string | undefined
// Fallback for state setters expecting non-null
setWebsite(website?.id ?? 0); // id might be undefined, setter expects number
// Array access with known valid index - use non-null assertion
const items = ['a', 'b', 'c'];
if (items.length > 0) {
const first = items[0]!; // We know index 0 exists after length check
}
```
## React Patterns
See `CONVENTIONS.md` for detailed patterns (feature folders, manager pattern, state management, React 19 rules).
### Components
Arrow function with named props type:
```tsx
type ExperimentCardProps = {
experiment: Experiment;
onSelect: (id: number) => void;
};
export const ExperimentCard = ({ experiment, onSelect }: ExperimentCardProps) => {
return (/* ... */);
};
```
### Hooks
Return objects for complex hooks, tuples for simple state:
```tsx
// Complex - return object
export const useExperiment = (id: number) => {
return { experiment, isLoading, update, delete: deleteExp };
};
// Simple state - return tuple (like useState)
export const useGlobal = <T>(key: string, initial: T) => {
return [value, setValue, refresh, reset] as const;
};
```
## Directory Structure
### Naming Conventions
- **Components**: `PascalCase.tsx`
- **Everything else**: `kebab-case.ts`
- **Hooks**: `use-*.ts` or `useFeatureName.ts`
## Formatting
Prettier config in `.prettierrc`:
- Semicolons: always
- Quotes: single (JS), double (JSX)
- Trailing commas: all
- Indent: 2 spaces
- Line width: 120
```bash
bun format # Format all files
bun format:check # Check without writing
```
## Development
```bash
bun dev # Runs dashboard + API server
bun dev:tracking # Runs tracking server separately if needed
bunx tsgo # TypeScript check (not npx tsc)
```
**New features**: Usually database-first (schema → API → UI)
**Environment variables**:
- Backend: `process.env.X` directly
- Frontend: Use existing config patterns
**Logging**: `console.log` for debugging (Signoz integration coming)
## Git
Simple lowercase commit messages, no prefixes.
## Working With Me
- **Ask first**: Confirm approach before significant changes
- **Explore thoroughly**: Read related files for full context
- **Keep it simple**: No over-engineering or premature abstractions
- **Be explicit**: No magic or implicit behavior
- **Stay focused**: Note unrelated issues but don't fix them
- **Detailed output**: Provide full breakdown of changes when completing tasks
## Section-Specific Docs
Detailed patterns for each area live in their respective directories:
**Frontend:**
- `src/apps/CLAUDE.md` - Shared frontend patterns (components, hooks, state)
- `src/apps/dashboard/CLAUDE.md` - Dashboard specifics
- `src/apps/editor/CLAUDE.md` - Editor specifics
**Backend:**
- `src/servers/CLAUDE.md` - API and tracking servers
- `src/databases/CLAUDE.md` - Database schemas and patterns
**Project-wide:**
- `CONVENTIONS.md` - Detailed code patterns with rationale (component organization, state management, React patterns)