Files
platform/CLAUDE.md
T
pastilhasandClaude Opus 4.8 a82ce9ecb0 drop inherited architecture docs that no longer match the code
AUTOMATION_CONTEXT.md and SIDECAR.md described the multi-tenant scope
model, the seed/ tree, the marketplace, and a single sidecar owning the
queue — all superseded. Fix the stale doc pointers in opencode.json and
CLAUDE.md, and point at TODO.md as the source of truth on direction.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 20:25:44 +01:00

7.8 KiB

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

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:

// ✅ 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
type ExperimentCardProps = {
  experiment: Experiment;
  onSelect: (id: number) => void;
};

Database Types

Use Drizzle-inferred types, never manual type definitions in schema files:

// 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):

// ✅ 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:

// ✅ 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
// 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
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:

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:

// 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
bun format       # Format all files
bun format:check # Check without writing

Development

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/officer-web/Screens/Dashboard/Automation/CLAUDE.md - Automation screen specifics

Backend:

  • src/databases/CLAUDE.md - Database schemas and patterns

Project-wide:

  • CONVENTIONS.md - Detailed code patterns with rationale (component organization, state management, React patterns)
  • TODO.md - Current direction and deferred work. Takes precedence over this file where they disagree.