Coffee journaling on ATProto (alpha)
alpha.arabica.social
coffee
1# CLAUDE.md
2
3This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
5## Project Overview
6
7Arabica is a coffee brew tracking application built on AT Protocol. User data
8(beans, brews, cafes, drinks, etc.) lives in each user's Personal Data Server
9(PDS), not locally. The app authenticates via OAuth, then performs CRUD through
10XRPC calls to the user's PDS.
11
12## Build & Development Commands
13
14```bash
15# Run development server (debug logging, moderator config, known-dids backfill)
16just run
17
18# Run tests
19go test ./...
20
21# Run a single test
22go test ./internal/models/... -run TestBeanIsIncomplete
23
24# After editing .templ files — regenerate Go code
25templ generate # all files
26templ generate -f <file> # single file
27
28# Rebuild Tailwind CSS (required after CSS/class changes)
29just style
30
31# Verify after changes
32go vet ./...
33go build ./...
34```
35
36## Workflow Rules
37
38Do NOT spend more than 2-3 minutes exploring/reading files before beginning
39implementation. If the task is clear, start writing code immediately. Ask
40clarifying questions rather than endlessly reading the codebase. When given a
41specific implementation task, produce code changes in the same session.
42
43## Work Management
44
45This project uses **cells** for task tracking. See `.cells/AGENTS.md` for usage.
46
47- `./cells list` / `./cells list --status open` / `./cells show <cell-id>`
48- Do NOT use `./cells run` (spawns new agent session, humans only)
49
50## Dependencies
51
52Prefer standard library solutions over external dependencies. Only add a
53third-party dependency if stdlib genuinely cannot handle the requirement.
54
55## Task Agents
56
57Hard limit of 3 agents maximum. Each agent must have a clearly scoped
58deliverable. Do not poll agents in a loop. If agents aren't producing results
59within 5 minutes, fall back to doing the work directly.
60
61## Tech Stack
62
63- **Language:** Go 1.21+, stdlib `net/http` with Go 1.22 routing
64- **Storage:** AT Protocol PDS (user data), BoltDB (sessions), SQLite (firehose index)
65- **Frontend:** HTMX + Alpine.js + Tailwind CSS
66- **Templates:** [Templ](https://templ.guide/) (type-safe Go templates)
67- **Logging:** zerolog
68
69## Architecture
70
71### AT Protocol Integration
72
731. User authenticates via OAuth (indigo SDK handles PKCE/DPOP)
742. Handler creates `AtprotoStore` scoped to user's DID + session
753. Store methods make XRPC calls to user's PDS
764. Results rendered via Templ components or returned as JSON
77
78**Collections (NSIDs)** — defined in `internal/atproto/nsid.go`:
79
80- `social.arabica.alpha.bean` — Coffee beans (references roaster)
81- `social.arabica.alpha.roaster` — Roasters
82- `social.arabica.alpha.grinder` — Grinders
83- `social.arabica.alpha.brewer` — Brewing devices
84- `social.arabica.alpha.cafe` — Cafes (references roaster)
85- `social.arabica.alpha.brew` — Brew sessions (references bean, grinder, brewer, recipe)
86- `social.arabica.alpha.drink` — Drinks at cafes (references cafe, bean)
87- `social.arabica.alpha.recipe` — Recipes (references brewer)
88- `social.arabica.alpha.like` — Likes (strongRef to any record)
89- `social.arabica.alpha.comment` — Comments (strongRef to any record, optional parent for threads)
90
91Records reference each other via AT-URIs (`at://did/collection/rkey`). Record
92keys use TID format (timestamp-based identifiers).
93
94### Store Interface
95
96`internal/database/store.go` defines the `Store` interface with CRUD methods for
97all entity types. `AtprotoStore` is the production implementation backed by the
98user's PDS with witness cache and session cache layers.
99
100### Three-Layer Caching
101
1021. **SessionCache** (`internal/atproto/cache.go`) — per-user in-memory cache
103 (2-min TTL). Copy-on-write pattern, invalidated on writes. Dirty-collection
104 tracking skips witness cache after local writes until firehose catches up.
105
1062. **WitnessCache** (`internal/firehose/index.go`) — SQLite-backed local index
107 populated by the Jetstream firehose consumer. Provides fast reads without PDS
108 calls. Used as fallback when session cache misses.
109
1103. **PDS fallback** — direct XRPC calls to the user's PDS when both caches miss.
111
112Write path: PDS write -> write-through to witness cache -> invalidate session
113cache (mark dirty).
114
115### Firehose & Feed Pipeline
116
117`internal/firehose/` subscribes to AT Protocol's Jetstream relay for real-time
118events. Records are indexed into the SQLite feed index. The feed pipeline:
119
1201. **FeedIndex** (`firehose/index.go`) — SQLite store, `recordToFeedItem()`
121 converts indexed records to `FeedItem` structs with resolved references.
1222. **FeedIndexAdapter** (`firehose/adapter.go`) — converts firehose `FeedItem`
123 to feed `FirehoseFeedItem` to avoid import cycles.
1243. **Feed Service** (`feed/service.go`) — converts to `feed.FeedItem`, applies
125 moderation filtering, caching, and pagination.
126
127When adding a new entity type, all three layers need the new fields added.
128
129### Adding a New Entity Type (Checklist)
130
131The full stack for a new entity requires changes across many files. Follow the
132pattern of an existing entity (e.g., roaster for simple entities, brew for
133entities with references):
134
1351. **Lexicon JSON** in `lexicons/`
1362. **NSID constant** in `internal/atproto/nsid.go`
1373. **RecordType constant** in `internal/lexicons/record_type.go` (const + ParseRecordType + DisplayName)
1384. **Model + request types + validation** in `internal/models/models.go`
1395. **Record conversion** (`XToRecord`/`RecordToX`) in `internal/atproto/records.go`
1406. **Store interface methods** in `internal/database/store.go`
1417. **AtprotoStore implementation** in `internal/atproto/store.go` (CRUD + witness + cache)
1428. **Cache fields + Set/Invalidate methods** in `internal/atproto/cache.go`
1439. **OAuth scope** in `internal/atproto/oauth.go`
14410. **Firehose config** (collection list) in `internal/firehose/config.go`
14511. **Firehose FeedItem** fields + `recordToFeedItem` switch case in `internal/firehose/index.go`
14612. **Feed adapter** mapping in `internal/firehose/adapter.go`
14713. **Feed service** `FeedItem` + `FirehoseFeedItem` fields and both mapper sites in `internal/feed/service.go`
14814. **CRUD handlers** in `internal/handlers/entities.go` (also update `HandleManagePartial`, `HandleAPIListAll`, `HandleManageRefresh`)
14915. **View + OG image handlers** in `internal/handlers/entity_views.go`
15016. **Modal handlers** in `internal/handlers/modals.go`
15117. **Routes** in `internal/routing/routing.go` (page views, API CRUD, modals, OG images)
15218. **Templ view page** in `internal/web/pages/` (e.g., `cafe_view.templ`)
15319. **Templ record content** in `internal/web/components/` (e.g., `record_cafe.templ`)
15420. **Entity table component** in `internal/web/components/entity_tables.templ`
15521. **Dialog modal** in `internal/web/components/dialog_modals.templ` (+ `getStringValue` cases)
15622. **Manage partial** tab in `internal/web/components/manage_partial.templ`
15723. **My Coffee tab** in `internal/web/pages/my_coffee.templ`
15824. **Feed card** switch cases in `internal/web/pages/feed.templ` (card class, content, ActionText, share URL, title, delete URL)
15925. **OG card** function in `internal/ogcard/entities.go` (+ accent color in `brew.go`)
16026. **Suggestions** config in `internal/suggestions/suggestions.go` + handler map in `internal/handlers/suggestions.go`
16127. **Client-side cache** entity case in `static/js/combo-select.js` `getUserEntities()`
162
163### Templ Architecture
164
165**Tabs only in `.templ` files** — never use spaces for indentation. A post-edit
166hook runs `templ fmt` automatically. After editing `.templ` files, run
167`templ generate` to regenerate Go code.
168
169Pages (`internal/web/pages/`) accept `*components.LayoutData` + page-specific
170props. Components (`internal/web/components/`) are reusable building blocks.
171
172Pattern: `pages.PageName(layoutData, props).Render(r.Context(), w)`
173
174### Combo-Select Component System
175
176Entity selection dropdowns (bean, grinder, brewer, roaster, cafe) use a shared
177combo-select pattern with typeahead search, community suggestions, and inline
178creation:
179
180- **Go config**: `components.ComboSelectConfig()` in `components/combo_select.templ`
181 generates Alpine.js `x-data` with entity-specific label formatting and create
182 data mapping.
183- **Templ markup**: `components.ComboSelectInput()` renders the shared dropdown UI.
184- **JS behavior**: `static/js/combo-select.js` — Alpine.js component that
185 searches user records (from client-side cache), community suggestions (from
186 `/api/suggestions/{entity}`), and creates new entities inline via POST.
187- **Suggestions backend**: `internal/suggestions/suggestions.go` — entity configs
188 define searchable fields and dedup keys.
189
190To add a new entity to combo-select: add a case to `ComboSelectConfig`, add to
191`getUserEntities()` in `combo-select.js`, add entity config to
192`suggestions.go`, and add to the entity-to-NSID map in
193`handlers/suggestions.go`.
194
195### Entity View Handler Pattern
196
197View handlers (`HandleXView`) support both authenticated (own records) and
198public (via `?owner=` parameter) access. They:
199
2001. Try witness cache first, fall back to PDS
2012. Resolve references (e.g., roaster for cafe)
2023. Populate OG metadata for social sharing
2034. Fetch social data (likes, comments, moderation state)
2045. Render the templ page with all props
205
206### CSS Cache Busting
207
208When making CSS/style changes, bump the version query parameter in
209`internal/web/components/layout.templ`:
210
211```html
212<link rel="stylesheet" href="/static/css/output.css?v=0.1.3" />
213```
214
215## Testing Conventions
216
217All tests MUST use [testify/assert](https://github.com/stretchr/testify). Do
218NOT use `if` statements with `t.Error()`.
219
220```go
221assert.Equal(t, expected, actual)
222assert.NoError(t, err)
223assert.Contains(t, haystack, needle)
224assert.True(t, value)
225assert.Nil(t, value)
226```
227
228## Using Go Tooling
229
230- `go mod download -json MODULE` — get dependency source path
231- `go doc foo.Bar` — read package/type/function docs
232- `go run ./cmd/server` instead of `go build` to avoid artifacts
233
234## Design Context
235
236See `.impeccable.md` for the full design system reference. Key points:
237
238### Brand Personality
239**Cozy, social, inviting** — like a neighborhood specialty cafe. Warm, not
240clinical. The emotional goals are calm satisfaction, geeky delight, community
241belonging, and craft pride.
242
243### Visual References
244- Specialty coffee bag packaging (Counter Culture, Onyx) — craft labels, earthy
245 tones, confident type
246- Analog journals — Moleskine, handwritten brew logs, texture of paper and ink
247
248### Design Principles
2491. **Warmth over precision** — Brown paper, not graph paper
2502. **Quiet confidence** — Strong typography, restrained color, let content shine
2513. **Tactile texture** — Evoke the analog: ceramic, kraft, journal pages
2524. **Community as atmosphere** — Cafe conversations, not social media timelines
2535. **Respect the ritual** — No urgency, no gamification, intentional interactions
254
255### Typography
256Iosevka Patrick (custom monospace) is the core UI font. Open to pairing with a
257warmer display font for headings.