···4849# Development files
50known-dids.txt
51+52+node_modules
53+web/static/app/assets
+1
BACKLOG.md
···16 - Private mode -- don't show in community feed (records are still public via pds api though)
17 - Dev mode -- show did, copy did in profiles (remove "logged in as <did>" from home page)
18 - Toggle for table view vs future post-style view
01920## Far Future Considerations
21
···16 - Private mode -- don't show in community feed (records are still public via pds api though)
17 - Dev mode -- show did, copy did in profiles (remove "logged in as <did>" from home page)
18 - Toggle for table view vs future post-style view
19+ - Toggle for "for" and "at" in pours view
2021## Far Future Considerations
22
+4-4
CLAUDE.md
···14## Project Structure
1516```
17-cmd/server/main.go # Application entry point
18internal/
19 atproto/ # AT Protocol integration
20 client.go # Authenticated PDS client (XRPC calls)
···112113```bash
114# Run server (uses firehose mode by default)
115-go run cmd/server/main.go
116117# Backfill known DIDs on startup
118-go run cmd/server/main.go --known-dids known-dids.txt
119120# Using nix
121nix run
···130### Build
131132```bash
133-go build -o arabica cmd/server/main.go
134```
135136## Command-Line Flags
···14## Project Structure
1516```
17+cmd/arabica-server/main.go # Application entry point
18internal/
19 atproto/ # AT Protocol integration
20 client.go # Authenticated PDS client (XRPC calls)
···112113```bash
114# Run server (uses firehose mode by default)
115+go run cmd/arabica-server/main.go
116117# Backfill known DIDs on startup
118+go run cmd/arabica-server/main.go --known-dids known-dids.txt
119120# Using nix
121nix run
···130### Build
131132```bash
133+go build -o arabica cmd/arabica-server/main.go
134```
135136## Command-Line Flags
+1-1
Dockerfile
···14COPY . .
1516# Build the binary
17-RUN CGO_ENABLED=0 GOOS=linux go build -o arabica cmd/server/main.go
1819# Runtime stage
20FROM alpine:3.23
···14COPY . .
1516# Build the binary
17+RUN CGO_ENABLED=0 GOOS=linux go build -o arabica cmd/arabica-server/main.go
1819# Runtime stage
20FROM alpine:3.23
···1+# Alpine.js → Svelte Migration Complete! 🎉
2+3+## What Changed
4+5+The entire frontend has been migrated from Alpine.js + HTMX + Go templates to a **Svelte SPA**.
6+7+### Before
8+- **Frontend**: Go HTML templates + Alpine.js + HTMX
9+- **State**: Alpine global components + DOM manipulation
10+- **Routing**: Server-side (Go mux)
11+- **Data**: Mixed (HTMX partials + JSON API)
12+13+### After
14+- **Frontend**: Svelte SPA (single-page application)
15+- **State**: Svelte stores (reactive)
16+- **Routing**: Client-side (navaid)
17+- **Data**: JSON API only
18+19+## Architecture
20+21+```
22+/
23+├── cmd/arabica-server/main.go # Go backend entry point
24+├── internal/ # Go backend (unchanged)
25+│ ├── handlers/
26+│ │ ├── handlers.go # Added /api/me and /api/feed-json
27+│ │ └── ...
28+│ └── routing/
29+│ └── routing.go # Added SPA fallback route
30+├── frontend/ # NEW: Svelte app
31+│ ├── src/
32+│ │ ├── App.svelte # Root component with router
33+│ │ ├── main.js # Entry point
34+│ │ ├── routes/ # Page components
35+│ │ │ ├── Home.svelte
36+│ │ │ ├── Login.svelte
37+│ │ │ ├── Brews.svelte
38+│ │ │ ├── BrewView.svelte
39+│ │ │ ├── BrewForm.svelte
40+│ │ │ ├── Manage.svelte
41+│ │ │ ├── Profile.svelte
42+│ │ │ ├── About.svelte
43+│ │ │ ├── Terms.svelte
44+│ │ │ └── NotFound.svelte
45+│ │ ├── components/ # Reusable components
46+│ │ │ ├── Header.svelte
47+│ │ │ ├── Footer.svelte
48+│ │ │ ├── FeedCard.svelte
49+│ │ │ └── Modal.svelte
50+│ │ ├── stores/ # Svelte stores
51+│ │ │ ├── auth.js # Authentication state
52+│ │ │ ├── cache.js # Data cache (replaces data-cache.js)
53+│ │ │ └── ui.js # UI state (notifications, etc.)
54+│ │ └── lib/
55+│ │ ├── api.js # Fetch wrapper
56+│ │ └── router.js # Client-side routing
57+│ ├── index.html
58+│ ├── vite.config.js
59+│ └── package.json
60+└── web/static/app/ # Built Svelte output (served by Go)
61+```
62+63+## Development
64+65+### Run Frontend Dev Server (with hot reload)
66+67+```bash
68+cd frontend
69+npm install
70+npm run dev
71+```
72+73+Frontend runs on http://localhost:5173 with Vite proxy to Go backend
74+75+### Run Go Backend
76+77+```bash
78+go run cmd/arabica-server/main.go
79+```
80+81+Backend runs on http://localhost:18910
82+83+### Build for Production
84+85+```bash
86+cd frontend
87+npm run build
88+```
89+90+This builds the Svelte app into `web/static/app/`
91+92+Then run the Go server normally:
93+94+```bash
95+go run cmd/arabica-server/main.go
96+```
97+98+The Go server will serve the built Svelte SPA from `web/static/app/`
99+100+## Key Features Implemented
101+102+### ✅ Authentication
103+- Login with AT Protocol handle
104+- Handle autocomplete
105+- User profile dropdown
106+- Persistent sessions
107+108+### ✅ Brews
109+- List all brews
110+- View brew details
111+- Create new brew
112+- Edit brew
113+- Delete brew
114+- Dynamic pours list
115+- Rating slider
116+117+### ✅ Equipment Management
118+- Tabs for beans, roasters, grinders, brewers
119+- CRUD operations for all entity types
120+- Inline entity creation from brew form
121+- Tab state persisted to localStorage
122+123+### ✅ Social Feed
124+- Community feed on homepage
125+- Feed items with author info
126+- Real-time updates (via API polling)
127+128+### ✅ Data Caching
129+- Stale-while-revalidate pattern
130+- localStorage persistence
131+- Automatic invalidation on writes
132+133+## API Changes
134+135+### New Endpoints
136+137+- `GET /api/me` - Current user info
138+- `GET /api/feed-json` - Feed items as JSON
139+140+### Existing Endpoints (unchanged)
141+142+- `GET /api/data` - All user data
143+- `POST /api/beans`, `PUT /api/beans/{id}`, `DELETE /api/beans/{id}`
144+- `POST /api/roasters`, `PUT /api/roasters/{id}`, `DELETE /api/roasters/{id}`
145+- `POST /api/grinders`, `PUT /api/grinders/{id}`, `DELETE /api/grinders/{id}`
146+- `POST /api/brewers`, `PUT /api/brewers/{id}`, `DELETE /api/brewers/{id}`
147+- `POST /brews`, `PUT /brews/{id}`, `DELETE /brews/{id}`
148+149+### Deprecated Endpoints (HTML partials, no longer needed)
150+151+- `GET /api/feed` (HTML)
152+- `GET /api/brews` (HTML)
153+- `GET /api/manage` (HTML)
154+- `GET /api/profile/{actor}` (HTML)
155+156+## Files to Delete (Future Cleanup)
157+158+These can be removed once you're confident the migration is complete:
159+160+```bash
161+# Old Alpine.js JavaScript
162+web/static/js/alpine.min.js
163+web/static/js/manage-page.js
164+web/static/js/brew-form.js
165+web/static/js/data-cache.js
166+web/static/js/handle-autocomplete.js
167+168+# Go templates (entire directory)
169+templates/
170+171+# Template rendering helpers
172+internal/bff/
173+```
174+175+## Testing Checklist
176+177+- [ ] Login with AT Protocol handle
178+- [ ] View homepage with feed
179+- [ ] Create new brew with dynamic pours
180+- [ ] Edit existing brew
181+- [ ] Delete brew
182+- [ ] Manage beans/roasters/grinders/brewers
183+- [ ] Tab navigation with localStorage persistence
184+- [ ] Inline entity creation from brew form
185+- [ ] Navigate between pages (client-side routing)
186+- [ ] Logout
187+188+## Browser Support
189+190+- Chrome/Edge (latest)
191+- Firefox (latest)
192+- Safari (latest)
193+194+## Performance
195+196+The Svelte bundle is **~136KB** (before gzip, ~35KB gzipped), which is excellent for a full-featured SPA.
197+198+Compared to Alpine.js (+ individual page scripts):
199+- **Before**: ~50KB Alpine + ~20KB per page = 70-90KB
200+- **After**: ~35KB gzipped for entire app
201+202+## Next Steps
203+204+1. Test thoroughly in development
205+2. Deploy to production
206+3. Monitor for any issues
207+4. Delete old template files once confident
208+5. Update documentation
209+210+## Notes
211+212+- OAuth flow still handled by Go backend
213+- Sessions stored in BoltDB (unchanged)
214+- User data stored in PDS via AT Protocol (unchanged)
215+- All existing Go handlers remain functional
+3-3
README.md
···32nix run
3334# Or with Go
35-go run cmd/server/main.go
36```
3738Access at http://localhost:18910
···93nix develop
9495# Run server
96-go run cmd/server/main.go
9798# Run tests
99go test ./...
100101# Build
102-go build -o arabica cmd/server/main.go
103```
104105## Deployment
···32nix run
3334# Or with Go
35+go run cmd/arabica-server/main.go
36```
3738Access at http://localhost:18910
···93nix develop
9495# Run server
96+go run cmd/arabica-server/main.go
9798# Run tests
99go test ./...
100101# Build
102+go build -o arabica cmd/arabica-server/main.go
103```
104105## Deployment
···1+<div class="max-w-4xl mx-auto">
2+ <div class="bg-white rounded-xl p-8 shadow-lg">
3+ <h1 class="text-3xl font-bold text-brown-900 mb-6">Terms of Service</h1>
4+5+ <div class="prose prose-brown max-w-none text-brown-800 space-y-4">
6+ <p class="text-sm text-brown-600 italic">
7+ Last updated: {new Date().toLocaleDateString()}
8+ </p>
9+10+ <h2 class="text-2xl font-bold text-brown-900 mt-8">
11+ 1. Acceptance of Terms
12+ </h2>
13+ <p>
14+ By accessing and using Arabica, you accept and agree to be bound by the
15+ terms and provision of this agreement.
16+ </p>
17+18+ <h2 class="text-2xl font-bold text-brown-900 mt-8">
19+ 2. Alpha Software Notice
20+ </h2>
21+ <p>
22+ Arabica is currently in alpha testing. Features, data structures, and
23+ functionality may change without notice. We recommend backing up your
24+ data regularly.
25+ </p>
26+27+ <h2 class="text-2xl font-bold text-brown-900 mt-8">3. Data Storage</h2>
28+ <p>
29+ Your brewing data is stored in your Personal Data Server (PDS) via the
30+ AT Protocol. Arabica does not store your brewing records on its servers.
31+ You are responsible for the security and backup of your PDS.
32+ </p>
33+34+ <h2 class="text-2xl font-bold text-brown-900 mt-8">
35+ 4. User Responsibilities
36+ </h2>
37+ <p>
38+ You are responsible for maintaining the confidentiality of your account
39+ credentials and for all activities that occur under your account.
40+ </p>
41+42+ <h2 class="text-2xl font-bold text-brown-900 mt-8">
43+ 5. Limitation of Liability
44+ </h2>
45+ <p>
46+ Arabica is provided "as is" without warranty of any kind. We are not
47+ liable for any data loss, service interruptions, or other damages
48+ arising from your use of the application.
49+ </p>
50+51+ <h2 class="text-2xl font-bold text-brown-900 mt-8">
52+ 6. Changes to Terms
53+ </h2>
54+ <p>
55+ We reserve the right to modify these terms at any time. Continued use of
56+ Arabica after changes constitutes acceptance of the modified terms.
57+ </p>
58+ </div>
59+ </div>
60+</div>
···91 if !ok || beanRef == "" {
92 return nil, fmt.Errorf("beanRef is required")
93 }
94- // Store the beanRef for later resolution
95- // For now, we'll just note it exists but won't resolve it here
0000000000000000000009697 // Required field: createdAt
98 createdAtStr, ok := record["createdAt"].(string)
···228 }
229 if description, ok := record["description"].(string); ok {
230 bean.Description = description
00000000231 }
232233 return bean, nil
···91 if !ok || beanRef == "" {
92 return nil, fmt.Errorf("beanRef is required")
93 }
94+ // Extract rkey from beanRef AT-URI
95+ if beanRef != "" {
96+ parsedBeanURI, err := syntax.ParseATURI(beanRef)
97+ if err == nil {
98+ brew.BeanRKey = parsedBeanURI.RecordKey().String()
99+ }
100+ }
101+102+ // Optional: grinderRef
103+ if grinderRef, ok := record["grinderRef"].(string); ok && grinderRef != "" {
104+ parsedGrinderURI, err := syntax.ParseATURI(grinderRef)
105+ if err == nil {
106+ brew.GrinderRKey = parsedGrinderURI.RecordKey().String()
107+ }
108+ }
109+110+ // Optional: brewerRef
111+ if brewerRef, ok := record["brewerRef"].(string); ok && brewerRef != "" {
112+ parsedBrewerURI, err := syntax.ParseATURI(brewerRef)
113+ if err == nil {
114+ brew.BrewerRKey = parsedBrewerURI.RecordKey().String()
115+ }
116+ }
117118 // Required field: createdAt
119 createdAtStr, ok := record["createdAt"].(string)
···249 }
250 if description, ok := record["description"].(string); ok {
251 bean.Description = description
252+ }
253+254+ // Optional: roasterRef
255+ if roasterRef, ok := record["roasterRef"].(string); ok && roasterRef != "" {
256+ parsedRoasterURI, err := syntax.ParseATURI(roasterRef)
257+ if err == nil {
258+ bean.RoasterRKey = parsedRoasterURI.RecordKey().String()
259+ }
260 }
261262 return bean, nil
+37
internal/atproto/records_test.go
···342 t.Error("RecordToBean() should error without name")
343 }
344 })
0000000000000000000000000000000000000345}
346347func TestRoasterToRecord(t *testing.T) {