docs: update CLAUDE.md and roadmap for single-backend (apps/api) repo

This commit is contained in:
Bas van Rossem
2026-06-17 14:41:07 +02:00
parent 64f8b2fd2c
commit 384797df7d
4 changed files with 45 additions and 72 deletions

101
CLAUDE.md
View File

@@ -4,86 +4,61 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## What this is
A **time-tracking app for insole (orthotics) production**, built on the **"Create" / Anything AI** platform and exported to run locally. The UI is in **Dutch**. A worker picks an insole type (`Type zool`: Kurk / Berk / 3D), a handling/task (`Type handeling`), and a count (`Aantal zolen`, default 2), then runs a stopwatch (start / pause / stop & save / double-press discard). The History tab lists past sessions and exports CSV; the Settings tab manages handelingen per zooltype.
**SoleLog** — a greenfield backend for an **insole (orthotics) production time-tracking system**. Workers time the handling/activities they perform per insole type (`Type zool`: Kurk / Berk / 3D), with the backend as the source of truth. The UI is in **Dutch**, but no client ships yet — the planned mobile (Expo) and admin (Vite + React) clients are described in `docs/roadmap.md`.
It is a **two-app monorepo, not frontend-only** — the backend lives in `apps/web`:
The repo currently contains **only the backend service** (`apps/api`) and the shared contracts package (`packages/shared`). It is a clean, dependency-light rebuild.
- **`apps/mobile`** — the Expo Router app (iOS / Android / web client). This is what the user runs on their phone via Expo Go / EAS builds.
- **`apps/web`** — a **Next.js 16** app that serves the backend HTTP API (`/api/*`), authentication, and a near-empty web UI. The mobile app talks to it over `EXPO_PUBLIC_BASE_URL`. In production it is the deployed `*.created.app` site.
The database is **Neon serverless Postgres**, reached only from `apps/web` via `DATABASE_URL`.
> Historical note: this repo began as an export from the **Create / Anything AI** platform (a two-app Expo + Next.js monorepo on Neon Postgres). That legacy code (`apps/mobile`, `apps/web`, `publisher/`) was removed in a full cleanup on 2026-06-17 — preserved in git history and summarised in `docs/reference/`.
## Layout
Yarn 4 (Berry) monorepo, `node-modules` linker. Workspaces = `apps/*` **`apps/mobile`** and **`apps/web`**.
Yarn 4 (Berry) monorepo, `node-modules` linker. Workspaces = `apps/*` + `packages/*`.
- **`publisher/`** is NOT a workspace — it's a standalone OpenNext + AWS S3 tool (its own `yarn.lock`) for building/deploying `apps/web`. Rarely touched.
- **`apps/api`** — the backend service: **Hono** HTTP server + **better-auth** + **Drizzle ORM** over **libsql/SQLite**.
- **`packages/shared`** — `@solelog/shared`: TypeScript types + **zod** schemas that form the API contracts (e.g. `HealthResponse`, `MeResponse`, `PublicUser`).
- Future clients (`apps/mobile`, `apps/admin`) are planned per the roadmap but not yet present.
## Backend: API + data model
## Backend (`apps/api`)
API routes live in `apps/web/src/app/api/`. The DB layer is `apps/web/src/app/api/utils/sql.ts` — a Neon tagged-template `sql\`...\`` (parameterised; interpolations are bind params, not string concatenation). Auth is **better-auth** (`apps/web/src/lib/auth.ts`): cookie sessions for web, `Authorization: Bearer <token>` for mobile (token from `/api/auth/token`).
- **App factory.** `src/app.ts` exports `createApp(): Hono`. It mounts the health route, the better-auth handler at `POST|GET /api/auth/*`, and the protected `me` route. `src/index.ts` serves it via `@hono/node-server` on `env.PORT` (default 3000).
- **Routes.**
- `GET /health` — liveness, returns `{ status: 'ok' }` (`src/routes/health.ts`).
- `/api/auth/*` — better-auth (email+password, `bearer()` plugin for token auth) (`src/auth.ts`).
- `GET /api/me` — protected; reads the session via `auth.api.getSession()` and returns `{ user: { id, email, name } }`, else 401 (`src/routes/me.ts`).
- **Auth.** `src/auth.ts` configures better-auth with the Drizzle SQLite adapter, email+password (no email verification), and the bearer plugin so mobile clients can send `Authorization: Bearer <token>`.
- **Database.** `@libsql/client` + `drizzle-orm/libsql`. `src/db/client.ts` opens the connection from `env.DATABASE_URL`; `src/db/schema.ts` holds the better-auth core tables (`user`, `session`, `account`, `verification`) — generated by the better-auth CLI, do not hand-edit. Migrations live in `apps/api/drizzle/`, generated by **drizzle-kit** (`db:generate`) and applied by `runMigrations()` in `src/db/migrate.ts` (`db:migrate`, also runnable directly via `tsx`).
- **Config.** `src/env.ts` reads `DATABASE_URL`, `BETTER_AUTH_SECRET`, `BETTER_AUTH_URL`, `PORT` from the environment with dev-friendly defaults. See `apps/api/.env.example`.
Schema, reverse-engineered from the queries (no migrations file ships in the repo):
- **`production_tasks`** — `id`, `name`, `insole_types text[]` (subset of `Kurk` / `Berk` / `3D`)
- **`time_logs`** — `id`, `task_id` (FK → production_tasks), `start_time`, `end_time`, `duration_seconds`, `pair_count`, `insole_type`, `notes`
API surface the **mobile app expects** vs what `apps/web` **provides**:
| Endpoint | Method | Used by mobile | Exists in apps/web |
|---|---|---|---|
| `/api/tasks` | GET, POST | yes | ✅ `api/tasks/route.ts` |
| `/api/tasks/:id` | PUT, DELETE | yes | ✅ `api/tasks/[id]/route.ts` |
| `/api/logs` | GET, POST | yes | ❌ **MISSING** |
| `/api/export` | GET (CSV) | yes | ✅ `api/export/route.ts` |
| `/api/session`, `/api/auth/*` | — | (auth plumbing) | ✅ |
### ⚠ Known gap: `/api/logs` is missing
The mobile app reads the History list (`GET /api/logs`) and saves a finished session (`POST /api/logs`), but **no such route exists in `apps/web`** — only the CSV `/api/export`. Against this local backend, Stop & Save and the History tab 404. (The deployed `*.created.app` instance presumably has it; it just wasn't in this export.) Contract to rebuild it from the callers:
- `POST /api/logs` body: `{ task_id, start_time (ISO), end_time (ISO), duration_seconds, pair_count, insole_type }`
- `GET /api/logs` returns an array of `{ id, task_name (join on production_tasks.name), start_time, duration_seconds, pair_count, insole_type }`, and (for parity with export) ideally `end_time`, ordered by `start_time`.
The planned domain model (users / workbenches / activities / work-sessions) is **not yet implemented** — only the better-auth tables exist. The model and phased plan live in `docs/roadmap.md`.
## Commands
Run from the repo root unless noted. The root has **no** `start`/`lint`/`test` scripts — invoke the tools directly.
Run from the repo root unless noted.
```bash
yarn install # install everything (Yarn 4)
npx oxlint # lint (config: .oxlintrc.json) — the real linter
npx oxfmt # format (config: .oxfmtrc.json) — 2-space, single-quote, semi, width 100
yarn install # install everything (Yarn 4)
# Mobile (apps/mobile)
cd apps/mobile && npx expo start # dev server; press a/i/w or scan QR with Expo Go
cd apps/mobile && npx expo start --web # web target only
cd apps/mobile && npx tsc --noEmit # typecheck (strict; @/* -> src/*)
cd apps/mobile && yarn jest # tests (jest-expo); add a path or -t "name" for one
cd apps/mobile && eas build --profile <development|preview|production> --platform <android|ios>
# Backend (@solelog/api)
yarn workspace @solelog/api dev # tsx watch — dev server (default :3000)
yarn workspace @solelog/api start # tsx — run once
yarn workspace @solelog/api test # vitest run
yarn workspace @solelog/api typecheck # tsc --noEmit
yarn workspace @solelog/api db:generate # drizzle-kit generate (new migration from schema)
yarn workspace @solelog/api db:migrate # apply migrations (tsx src/db/migrate.ts)
# Web / backend (apps/web)
cd apps/web && yarn dev # Next.js dev on http://localhost:4000
cd apps/web && yarn build # next build (publisher wraps this for deploy)
cd apps/web && yarn typecheck # tsc --noEmit (next.config.js ignores TS errors at build time!)
cd apps/web && npx vitest # tests (vitest + jsdom)
# Lint / format (root)
npx oxlint # lint (config: .oxlintrc.json)
npx oxfmt # format (config: .oxfmtrc.json) — 2-space, single-quote, semi, width 100
# Docker (whole stack)
docker compose up --build # build + run the api container; health: curl http://localhost:3000/health
```
> `eslint` / `typescript-eslint` are in devDeps but there is **no eslint config** in the tree — use **oxlint**, not eslint.
> Use **oxlint**, not eslint — there is no eslint config in the tree.
## Environment
## Docs
Secrets are gitignored (`.env`). Mobile env is `apps/mobile/.env` (`EXPO_PUBLIC_*`): `EXPO_PUBLIC_BASE_URL` points the client at the web backend; `EXPO_PUBLIC_PROJECT_GROUP_ID`/`HOST` feed the patched global fetch. Web needs `DATABASE_URL` (Neon), plus `BETTER_AUTH_URL` and the trusted-origin URLs for auth. `EXPO_PUBLIC_CREATE_ENV` (`PRODUCTION`/`DEVELOPMENT`) + `__DEV__` gate analytics, Sentry, the in-app "anything-menu", and dev-only native module stubs.
## Architecture notes (mobile)
- **Routing:** Expo Router, file-based under `apps/mobile/src/app/`. `_layout.tsx` gates render on `useAuth().initiate()` + `isReady` (loads the persisted session first) and provides the React Query client. `(tabs)/` holds `index.tsx` (Stopwatch), `history.tsx` (Geschiedenis), `tasks.tsx` (Instellingen).
- **Entry points split by extension.** Native: `index.tsx` → `entrypoint.ts` → `App.tsx`. Web: `index.web.tsx` → `App.web.tsx` (adds the Create sandbox-iframe plumbing — postMessage handshake, navigation sync, screenshot, healthcheck).
- **Global `fetch` is monkey-patched.** `src/__create/polyfills.ts` swaps `global.fetch` for `src/__create/fetch.ts`, which rewrites first-party (`/api/...`) URLs onto `EXPO_PUBLIC_BASE_URL`, injects project/host headers, and attaches the SecureStore JWT. App code calls `fetch('/api/...')` and relies on this.
- **Web support is Metro module aliasing**, not separate code. `apps/mobile/metro.config.js` `resolveRequest` swaps many native modules for stubs in `polyfills/web/` when `platform === 'web'`. **Adding a native dependency imported on web requires a web polyfill alias here, or the web build breaks.**
- **Styling:** NativeWind/Tailwind config extends `@anythingai/app/tailwind.config`, but screens mostly use React Native `StyleSheet`/inline styles. Inter font.
## Conventions & gotchas
- **Do not edit platform-managed files.** Files under `apps/mobile/__create/` and `apps/mobile/src/__create/`, the auth files in `apps/mobile/src/utils/auth/`, and `apps/web/src/lib/auth.ts` carry `⚠ ANYTHING PLATFORM — DO NOT REWRITE` headers. They define the auth surface (`signIn`/`signUp`/`signOut`/`auth`/`isAuthenticated`/`isReady`), the fetch/sandbox plumbing, and the better-auth config (bearer plugin, name backfill, trusted origins, `sameSite:'none'` cookies). Rewriting them breaks auth or the Create preview. `apps/mobile/src/app/_layout.tsx` is editable except the `<AuthModal />` render and the `initiate()` + `isReady` gate.
- **Do not bump patched dependencies.** `react-native`, `expo-router`, `expo-store-review`, `react-native-purchases(-ui)`, `@expo/cli`, `@react-native-community/netinfo`, etc. use `patch:` entries backed by `.yarn/patches/`, pinned further by root `resolutions`/`overrides`. Upgrading discards the patch. When running `npx expo install --fix`, skip any `patch:` package.
- This is an exported template: versions are pinned exact (no `^`) deliberately; prefer minimal, targeted changes.
- **`docs/roadmap.md`** — vision, decisions, architecture, data model, and the phased roadmap.
- **`docs/plans/`** — per-phase implementation plans (e.g. `phase-0-foundation.md`).
- **`docs/reference/`** — reference extracted from the legacy Create/Anything export before cleanup (legacy backend, mobile app, lessons & gotchas).
- **`docs/sessions/`** — dated session logs.