···11+# CLAUDE.md
22+33+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
44+55+## Project
66+77+skystar.social — a favstar.fm-style Bluesky web app. Given a handle, shows that user's top-25 most-liked or most-reposted posts (all-time / last month). Live engagement comes from the Bluesky Jetstream firehose; first-lookup backfill hits the AppView API.
88+99+## Stack
1010+1111+- **TypeScript + AdonisJS v7** (web + workers, single repo, no monorepo — `apps/web` was flattened to root in commit c46cdbb)
1212+- **ClickHouse** — append-only store for engagement events + post snapshots
1313+- **SQLite** (via Lucid) — metadata: tracked users, jetstream cursor, backfill jobs
1414+- **Node 24**, **pnpm 10** (pinned in `mise.toml`; the repo migrated from npm in c46cdbb)
1515+- **Edge.js** templates, **Alpine.js** + **Vite** on the frontend
1616+- Ships as one Docker image with three process entrypoints.
1717+1818+## Commands
1919+2020+Preferred workflow is via `mise` tasks (see `mise.toml`):
2121+2222+```bash
2323+mise run dev # ClickHouse + migrations + web + jetstream + queue (parallel)
2424+mise run db:wait # Start ClickHouse, wait for /ping
2525+mise run db:migrate # Run Lucid + ClickHouse migrations
2626+mise run dev:web # node ace serve --hmr
2727+mise run dev:jetstream # node ace jetstream:consume
2828+mise run dev:queue # node ace queue:work
2929+```
3030+3131+Direct pnpm scripts:
3232+3333+```bash
3434+pnpm dev # node ace serve --hmr
3535+pnpm test # node ace test (Japa)
3636+pnpm lint # eslint .
3737+pnpm typecheck # tsc --noEmit
3838+pnpm build # node ace build
3939+```
4040+4141+Running a single test: `node ace test --files "tests/unit/clickhouse_store.spec.ts"` (Japa file filter). The Japa runner is configured in `adonisrc.ts`.
4242+4343+Ace commands live in `commands/` — notably `node ace clickhouse:migrate` (applies SQL files in `database/clickhouse/`) and `node ace jetstream:consume`.
4444+4545+## Architecture
4646+4747+Three process entrypoints, one codebase:
4848+4949+1. **HTTP web server** (`bin/server.ts`) — controllers in `app/controllers/`, routes in `start/routes.ts`. Profile pages (`/profile/:handle/likes|reposts`) are the main product surface. Health checks at `/health/live` (public) and `/health/ready` (gated by `HEALTH_CHECK_TOKEN` via `x-monitoring-secret` header).
5050+2. **Jetstream consumer** (`node ace jetstream:consume`) — `app/services/jetstream_consumer.ts` connects to the Bluesky Jetstream WebSocket, filters for tracked DIDs, writes engagement events to ClickHouse, and persists cursor position to SQLite via `jetstream_cursor_io.ts`. The consumer takes a `WebSocketLike` factory so tests can inject fakes.
5151+3. **Queue worker** (`node ace queue:work`, `@adonisjs/queue`) — runs `app/jobs/backfill_job.ts` which fetches historical posts/likes/reposts from the AppView on first lookup of a new handle.
5252+5353+### Data flow
5454+5555+- Handle → `HandleResolver` (SQLite lookup or AppView resolution) → persisted to `users` table (DID is the tracked identity)
5656+- First time we see a DID: enqueue a backfill job; Jetstream consumer also starts tracking it for live events
5757+- Queries for top-N posts read from ClickHouse (`app/lib/clickhouse/store.ts`) joining engagement events with post snapshots
5858+- Cursor checkpoint so Jetstream resumes after restart
5959+6060+### Key modules
6161+6262+- `app/lib/atproto/` — AppView client + Jetstream event parsers. Import via `#lib/atproto/index` (path alias). Previously lived in `packages/atproto`, flattened in commit 0cea00e.
6363+- `app/lib/clickhouse/` — ClickHouse client wrapper + query store. Import via `#lib/clickhouse/index`. **Tests must drain query streams** — see commit c46cdbb; undrained streams hang the test process.
6464+- `database/schema.ts` + `database/schema_rules.ts` — Lucid schema definitions; `database/migrations/` for SQLite; `database/clickhouse/*.sql` for ClickHouse DDL (numbered, applied in order).
6565+- `config/` — standard AdonisJS config (app, database, queue, session, shield, static, vite). `start/env.ts` defines required env vars via Vine schema.
6666+6767+### Path aliases
6868+6969+Defined in `package.json` `imports` — use `#controllers/*`, `#services/*`, `#models/*`, `#lib/*`, `#jobs/*`, `#database/*`, `#tests/*`, `#start/*`, `#config/*`. These map to the `.js` extension even in TS source (AdonisJS convention).
7070+7171+## Edge templates gotcha
7272+7373+Edge uses `{{ handle }}` for interpolation, **not** `{{{ handle }}}`. There was a recent bug (ac30713) where `@{{ handle }}` rendered as literal `@<handle>` because of Edge's `@` tag parsing — if you're rendering a handle preceded by `@`, escape the `@` or use `{{ '@' + handle }}`.
7474+7575+## Testing notes
7676+7777+- Japa runner (`@japa/runner`), configured in `adonisrc.ts`.
7878+- Tests split into `tests/unit/` and `tests/functional/`.
7979+- ClickHouse tests use real ClickHouse (docker compose). Never mock it — always drain query result streams to avoid hangs.
8080+- Fixtures live next to specs (e.g. `clickhouse_store_fixtures.ts`).