first
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user