···1+# Arabica Production Configuration
2+# Copy this file to .env and update with your values
3+4+# Your domain name (required for production)
5+DOMAIN=arabica.example.com
6+ACME_EMAIL=admin@example.com
7+8+LOG_LEVEL=info
9+LOG_FORMAT=json
10+SERVER_PUBLIC_URL=https://${DOMAIN}
11+SECURE_COOKIES=true
···1+# Development Workflow
2+3+## Setting Up Firehose Feed with Known DIDs
4+5+For development and testing, you can populate your local feed with known Arabica users:
6+7+### 1. Create a Known DIDs File
8+9+Create `known-dids.txt` in the project root:
10+11+```bash
12+cat > known-dids.txt << 'EOF'
13+# Known Arabica users for development
14+# Add one DID per line
15+16+# Example (replace with real DIDs):
17+# did:plc:abc123xyz
18+# did:plc:def456uvw
19+20+EOF
21+```
22+23+### 2. Find DIDs to Add
24+25+You can find DIDs of Arabica users in several ways:
26+27+**From Bluesky profiles:**
28+- Visit a user's profile on Bluesky
29+- Check the URL or profile metadata for their DID
30+31+**From authenticated sessions:**
32+- After logging into Arabica, check your browser cookies
33+- The `did` cookie contains your DID
34+35+**From AT Protocol explorer tools:**
36+- Use tools like `atproto.blue` to search for users
37+38+### 3. Run Server with Backfill
39+40+```bash
41+# Start server with firehose and backfill
42+go run cmd/server/main.go --firehose --known-dids known-dids.txt
43+44+# Or with nix (requires adding flags to flake.nix)
45+nix run -- --firehose --known-dids known-dids.txt
46+```
47+48+### 4. Monitor Backfill Progress
49+50+Watch the logs for backfill activity:
51+52+```
53+{"level":"info","count":3,"file":"known-dids.txt","message":"Loaded known DIDs from file"}
54+{"level":"info","did":"did:plc:abc123xyz","message":"backfilling user records"}
55+{"level":"info","total":5,"success":5,"message":"Backfill complete"}
56+```
57+58+### 5. Verify Feed Data
59+60+Once backfilled, check:
61+- Home page feed should show brews from backfilled users
62+- `/feed` endpoint should return feed items
63+- Database should contain indexed records
64+65+## File Format Notes
66+67+The `known-dids.txt` file supports:
68+69+- **Comments**: Lines starting with `#`
70+- **Empty lines**: Ignored
71+- **Whitespace**: Automatically trimmed
72+- **Validation**: Non-DID lines logged as warnings
73+74+Example valid file:
75+76+```
77+# Coffee enthusiasts to follow
78+did:plc:user1abc
79+80+# Another user
81+did:plc:user2def
82+83+did:web:coffee.example.com # Web DID example
84+```
85+86+## Security Note
87+88+⚠️ **Important**: The `known-dids.txt` file is gitignored by default. Do not commit DIDs unless you have permission from the users.
89+90+For production deployments, rely on organic discovery via firehose rather than manual DID lists.
···1+# HTMX + Alpine.js Integration Pattern
2+3+## Problem: "Alpine Expression Error: [variable] is not defined"
4+5+When HTMX swaps in content containing Alpine.js directives (like `x-show`, `x-if`, `@click`), Alpine may not automatically process the new DOM elements, resulting in console errors like:
6+7+```
8+Alpine Expression Error: activeTab is not defined
9+Expression: "activeTab === 'brews'"
10+```
11+12+## Root Cause
13+14+HTMX loads and swaps content into the DOM after Alpine has already initialized. The new elements contain Alpine directives that reference variables in a parent Alpine component's scope, but Alpine doesn't automatically bind these new elements to the existing component.
15+16+## Solution
17+18+Use HTMX's `hx-on::after-swap` event to manually tell Alpine to initialize the new DOM tree:
19+20+```html
21+<div id="content"
22+ hx-get="/api/data"
23+ hx-trigger="load"
24+ hx-swap="innerHTML"
25+ hx-on::after-swap="Alpine.initTree($el)">
26+</div>
27+```
28+29+### Key Points
30+31+- `hx-on::after-swap` - HTMX event that fires after content swap completes
32+- `Alpine.initTree($el)` - Tells Alpine to process all directives in the swapped element
33+- `$el` - HTMX provides this as the target element that received the swap
34+35+## Common Scenario
36+37+**Parent template** (defines Alpine scope):
38+```html
39+<div x-data="{ activeTab: 'brews' }">
40+ <!-- Static content with tab buttons -->
41+ <button @click="activeTab = 'brews'">Brews</button>
42+43+ <!-- HTMX loads dynamic content here -->
44+ <div id="content"
45+ hx-get="/api/tabs"
46+ hx-trigger="load"
47+ hx-swap="innerHTML"
48+ hx-on::after-swap="Alpine.initTree($el)">
49+ </div>
50+</div>
51+```
52+53+**Loaded partial** (uses parent scope):
54+```html
55+<div x-show="activeTab === 'brews'">
56+ <!-- Brew content -->
57+</div>
58+<div x-show="activeTab === 'beans'">
59+ <!-- Bean content -->
60+</div>
61+```
62+63+Without `Alpine.initTree($el)`, the `x-show` directives won't be bound to the parent's `activeTab` variable.
64+65+## Alternative: Alpine Morph Plugin
66+67+For more complex scenarios with nested Alpine components, use the Alpine Morph plugin:
68+69+```html
70+<script src="https://cdn.jsdelivr.net/npm/@alpinejs/morph@3.x.x/dist/cdn.min.js"></script>
71+<div hx-swap="morph"></div>
72+```
73+74+This preserves Alpine state during swaps but requires the plugin.
75+76+## When to Use
77+78+Apply this pattern whenever:
79+1. HTMX loads content containing Alpine directives
80+2. The loaded content references variables from a parent Alpine component
81+3. You see "Expression Error: [variable] is not defined" in console
82+4. Alpine directives in HTMX-loaded content don't work (no reactivity, clicks ignored, etc.)
+8-5
BACKLOG.md
···24 - If adding mobile apps, third-party API consumers, or microservices architecture, revisit this
25 - For now, monolithic approach is appropriate for HTMX-based web app with decentralized storage
260027## Fixes
2829-- [Future work]: adjust timing of caching in feed, maybe use firehose and a sqlite database since we are only storing a few anyway
30- - Goal: reduce pings to server when idling
3132-- Non-authed home page feed shows different from what the firehose version shows (should be cached and show same db contents I think)
33-34-- After adding a bean via add brew, that bean does not show up in the drop down until after a refresh
0
···24 - If adding mobile apps, third-party API consumers, or microservices architecture, revisit this
25 - For now, monolithic approach is appropriate for HTMX-based web app with decentralized storage
2627+- Backfill seems to be called when user hits homepage, probably only needs to be done on startup
28+29## Fixes
3031+- After adding a bean via add brew, that bean does not show up in the drop down until after a refresh
32+ - Happens with grinders and likely brewers also
3334+- Adding a grinder via the new brew page does not populate fields correctly other than the name
35+ - Also seems to happen to brewers
36+ - To solve this issue and the above, we likely should consolidate creation to use the same popup as the manage page uses,
37+ since that one works, and should already be a template partial.
+32-9
CLAUDE.md
···100### Run Development Server
101102```bash
0103go run cmd/server/main.go
104-# or
0000000105nix run
106```
107···117go build -o arabica cmd/server/main.go
118```
1190000000000000120## Environment Variables
121122-| Variable | Default | Description |
123-| ------------------- | --------------------------------- | ---------------------------- |
124-| `PORT` | 18910 | HTTP server port |
125-| `SERVER_PUBLIC_URL` | - | Public URL for reverse proxy |
126-| `ARABICA_DB_PATH` | ~/.local/share/arabica/arabica.db | BoltDB path |
127-| `SECURE_COOKIES` | false | Set true for HTTPS |
128-| `LOG_LEVEL` | info | debug/info/warn/error |
129-| `LOG_FORMAT` | console | console/json |
00130131## Code Patterns
132
···100### Run Development Server
101102```bash
103+# Basic mode (polling-based feed)
104go run cmd/server/main.go
105+106+# With firehose (real-time AT Protocol feed)
107+go run cmd/server/main.go --firehose
108+109+# With firehose + backfill known DIDs
110+go run cmd/server/main.go --firehose --known-dids known-dids.txt
111+112+# Using nix
113nix run
114```
115···125go build -o arabica cmd/server/main.go
126```
127128+## Command-Line Flags
129+130+| Flag | Type | Default | Description |
131+| --------------- | ------ | ------- | ----------------------------------------------------- |
132+| `--firehose` | bool | false | Enable real-time firehose feed via Jetstream |
133+| `--known-dids` | string | "" | Path to file with DIDs to backfill (one per line) |
134+135+**Known DIDs File Format:**
136+- One DID per line (e.g., `did:plc:abc123xyz`)
137+- Lines starting with `#` are comments
138+- Empty lines are ignored
139+- See `known-dids.txt.example` for reference
140+141## Environment Variables
142143+| Variable | Default | Description |
144+| --------------------------- | --------------------------------- | ---------------------------------- |
145+| `PORT` | 18910 | HTTP server port |
146+| `SERVER_PUBLIC_URL` | - | Public URL for reverse proxy |
147+| `ARABICA_DB_PATH` | ~/.local/share/arabica/arabica.db | BoltDB path (sessions, registry) |
148+| `ARABICA_FEED_INDEX_PATH` | ~/.local/share/arabica/feed-index.db | Firehose index BoltDB path |
149+| `ARABICA_PROFILE_CACHE_TTL` | 1h | Profile cache duration |
150+| `SECURE_COOKIES` | false | Set true for HTTPS |
151+| `LOG_LEVEL` | info | debug/info/warn/error |
152+| `LOG_FORMAT` | console | console/json |
153154## Code Patterns
155
···1+# Arabica Deployment Guide
2+3+Quick guide to deploy Arabica to a VPS with Docker and automatic HTTPS.
4+5+## Prerequisites
6+7+- VPS with Docker and Docker Compose installed
8+- Domain name pointing to your VPS IP address (A record)
9+- Ports 80 and 443 open in firewall
10+11+## Quick Start (Production)
12+13+1. **Clone the repository:**
14+ ```bash
15+ git clone <repository-url>
16+ cd arabica
17+ ```
18+19+2. **Configure your domain:**
20+ ```bash
21+ cp .env.example .env
22+ nano .env
23+ ```
24+25+ Update `.env` with your domain:
26+ ```env
27+ DOMAIN=arabica.yourdomain.com
28+ ACME_EMAIL=your-email@example.com
29+ ```
30+31+3. **Deploy:**
32+ ```bash
33+ docker compose up -d
34+ ```
35+36+That's it! Caddy will automatically:
37+- Obtain SSL certificates from Let's Encrypt
38+- Renew certificates before expiry
39+- Redirect HTTP to HTTPS
40+- Proxy requests to Arabica
41+42+4. **Check logs:**
43+ ```bash
44+ docker compose logs -f
45+ ```
46+47+5. **Visit your site:**
48+ ```
49+ https://arabica.yourdomain.com
50+ ```
51+52+## Local Development
53+54+To run locally without a domain:
55+56+```bash
57+docker compose up
58+```
59+60+Then visit `http://localhost` (Caddy will serve on port 80).
61+62+## Updating
63+64+```bash
65+git pull
66+docker compose down
67+docker compose build
68+docker compose up -d
69+```
70+71+## Troubleshooting
72+73+### Certificate Issues
74+75+If Let's Encrypt can't issue a certificate:
76+- Ensure your domain DNS is pointing to your VPS
77+- Check ports 80 and 443 are accessible
78+- Check logs: `docker compose logs caddy`
79+80+### View Arabica logs
81+82+```bash
83+docker compose logs -f arabica
84+```
85+86+### Reset everything
87+88+```bash
89+docker compose down -v # Warning: deletes all data
90+docker compose up -d
91+```
92+93+## Production Checklist
94+95+- [ ] Domain DNS pointing to VPS
96+- [ ] Ports 80 and 443 open in firewall
97+- [ ] `.env` file configured with your domain
98+- [ ] Valid email set for Let's Encrypt notifications
99+- [ ] Regular backups of `arabica-data` volume
100+101+## Backup
102+103+To backup user data:
104+105+```bash
106+docker compose exec arabica cp /data/arabica.db /data/arabica-backup.db
107+docker cp $(docker compose ps -q arabica):/data/arabica-backup.db ./backup-$(date +%Y%m%d).db
108+```
109+110+## Advanced Configuration
111+112+### Custom Caddyfile
113+114+Edit `Caddyfile` directly for advanced options like:
115+- Rate limiting
116+- Custom headers
117+- IP whitelisting
118+- Multiple domains
119+120+### Environment Variables
121+122+All available environment variables in `.env`:
123+124+| Variable | Default | Description |
125+| ------------------- | ------------------------------------ | ------------------------------- |
126+| `DOMAIN` | localhost | Your domain name |
127+| `ACME_EMAIL` | (empty) | Email for Let's Encrypt |
128+| `LOG_LEVEL` | info | debug/info/warn/error |
129+| `LOG_FORMAT` | json | console/json |
130+| `SERVER_PUBLIC_URL` | https://${DOMAIN} | Override public URL |
131+| `SECURE_COOKIES` | true | Use secure cookies |
132+133+## Support
134+135+For issues, check the logs first:
136+```bash
137+docker compose logs
138+```
+30-2
README.md
···4344## Configuration
4546-Environment variables:
000004748- `PORT` - Server port (default: 18910)
49- `SERVER_PUBLIC_URL` - Public URL for reverse proxy deployments (e.g., https://arabica.example.com)
50- `ARABICA_DB_PATH` - BoltDB path (default: ~/.local/share/arabica/arabica.db)
0051- `OAUTH_CLIENT_ID` - OAuth client ID (optional, uses localhost mode if not set)
52- `OAUTH_REDIRECT_URI` - OAuth redirect URI (optional)
53- `SECURE_COOKIES` - Set to true for HTTPS (default: false)
···5859- Track coffee brews with detailed parameters
60- Store data in your AT Protocol Personal Data Server
61-- Community feed of recent brews from registered users
62- Manage beans, roasters, grinders, and brewers
63- Export brew data as JSON
64- Mobile-friendly PWA design
0000000000000000000006566## Architecture
67
···4344## Configuration
4546+### Command-Line Flags
47+48+- `--firehose` - Enable real-time feed via AT Protocol Jetstream (default: false)
49+- `--known-dids <file>` - Path to file with DIDs to backfill on startup (one per line)
50+51+### Environment Variables
5253- `PORT` - Server port (default: 18910)
54- `SERVER_PUBLIC_URL` - Public URL for reverse proxy deployments (e.g., https://arabica.example.com)
55- `ARABICA_DB_PATH` - BoltDB path (default: ~/.local/share/arabica/arabica.db)
56+- `ARABICA_FEED_INDEX_PATH` - Firehose index BoltDB path (default: ~/.local/share/arabica/feed-index.db)
57+- `ARABICA_PROFILE_CACHE_TTL` - Profile cache duration (default: 1h)
58- `OAUTH_CLIENT_ID` - OAuth client ID (optional, uses localhost mode if not set)
59- `OAUTH_REDIRECT_URI` - OAuth redirect URI (optional)
60- `SECURE_COOKIES` - Set to true for HTTPS (default: false)
···6566- Track coffee brews with detailed parameters
67- Store data in your AT Protocol Personal Data Server
68+- Community feed of recent brews from registered users (polling or real-time firehose)
69- Manage beans, roasters, grinders, and brewers
70- Export brew data as JSON
71- Mobile-friendly PWA design
72+73+### Firehose Mode
74+75+Enable real-time feed updates via AT Protocol's Jetstream:
76+77+```bash
78+# Basic firehose mode
79+go run cmd/server/main.go --firehose
80+81+# With known DIDs for backfill
82+go run cmd/server/main.go --firehose --known-dids known-dids.txt
83+```
84+85+**Known DIDs file format:**
86+```
87+# Comments start with #
88+did:plc:abc123xyz
89+did:plc:def456uvw
90+```
91+92+The firehose automatically indexes **all** Arabica records across the AT Protocol network. The `--known-dids` flag allows you to backfill historical records from specific users on startup (useful for development/testing).
9394## Architecture
95
···1run:
2- @LOG_LEVEL=debug LOG_FORMAT=console go run cmd/server/main.go
34run-production:
5- @LOG_FORMAT=json SECURE_COOKIES=true go run cmd/server/main.go
6-7-run-firehose:
8- @LOG_LEVEL=debug LOG_FORMAT=console go run cmd/server/main.go -firehose
910test:
11 @go test ./... -cover -coverprofile=cover.out
···1run:
2+ @LOG_LEVEL=debug LOG_FORMAT=console go run cmd/server/main.go -firehose -known-dids known-dids.txt
34run-production:
5+ @LOG_FORMAT=json SECURE_COOKIES=true go run cmd/server/main.go -firehose
00067test:
8 @go test ./... -cover -coverprofile=cover.out
+18
known-dids.txt.example
···000000000000000000
···1+# Known DIDs for Development Backfill
2+#
3+# This file contains DIDs that should be backfilled on startup when using
4+# the --known-dids flag. This is useful for development and testing to
5+# populate the feed with known coffee enthusiasts.
6+#
7+# Format: One DID per line
8+# Lines starting with # are comments
9+# Empty lines are ignored
10+#
11+# Example DIDs (replace with real DIDs):
12+# did:plc:example1234567890abcdef
13+# did:plc:another1234567890abcdef
14+#
15+# To use this file:
16+# 1. Copy this file to known-dids.txt
17+# 2. Add real DIDs (one per line)
18+# 3. Run: ./arabica --firehose --known-dids known-dids.txt