ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto
1# CLAUDE.md
2
3This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
5# Project Instructions
6
7## Decision Graph Workflow
8
9**THIS IS MANDATORY. Log decisions IN REAL-TIME, not retroactively.**
10
11### The Core Rule
12
13```
14BEFORE you do something -> Log what you're ABOUT to do
15AFTER it succeeds/fails -> Log the outcome
16CONNECT immediately -> Link every node to its parent
17AUDIT regularly -> Check for missing connections
18```
19
20### Behavioral Triggers - MUST LOG WHEN:
21
22| Trigger | Log Type | Example |
23|---------|----------|---------|
24| User asks for a new feature | `goal` **with -p** | "Add dark mode" |
25| Choosing between approaches | `decision` | "Choose state management" |
26| About to write/edit code | `action` | "Implementing Redux store" |
27| Something worked or failed | `outcome` | "Redux integration successful" |
28| Notice something interesting | `observation` | "Existing code uses hooks" |
29
30### CRITICAL: Capture VERBATIM User Prompts
31
32**Prompts must be the EXACT user message, not a summary.** When a user request triggers new work, capture their full message word-for-word.
33
34**BAD - summaries are useless for context recovery:**
35```bash
36# DON'T DO THIS - this is a summary, not a prompt
37deciduous add goal "Add auth" -p "User asked: add login to the app"
38```
39
40**GOOD - verbatim prompts enable full context recovery:**
41```bash
42# Use --prompt-stdin for multi-line prompts
43deciduous add goal "Add auth" -c 90 --prompt-stdin << 'EOF'
44I need to add user authentication to the app. Users should be able to sign up
45with email/password, and we need OAuth support for Google and GitHub. The auth
46should use JWT tokens with refresh token rotation.
47EOF
48
49# Or use the prompt command to update existing nodes
50deciduous prompt 42 << 'EOF'
51The full verbatim user message goes here...
52EOF
53```
54
55**When to capture prompts:**
56- Root `goal` nodes: YES - the FULL original request
57- Major direction changes: YES - when user redirects the work
58- Routine downstream nodes: NO - they inherit context via edges
59
60**Updating prompts on existing nodes:**
61```bash
62deciduous prompt <node_id> "full verbatim prompt here"
63cat prompt.txt | deciduous prompt <node_id> # Multi-line from stdin
64```
65
66Prompts are viewable in the TUI detail panel (`deciduous tui`) and web viewer.
67
68### ⚠️ CRITICAL: Maintain Connections
69
70**The graph's value is in its CONNECTIONS, not just nodes.**
71
72| When you create... | IMMEDIATELY link to... |
73|-------------------|------------------------|
74| `outcome` | The action/goal it resolves |
75| `action` | The goal/decision that spawned it |
76| `option` | Its parent decision |
77| `observation` | Related goal/action |
78
79**Root `goal` nodes are the ONLY valid orphans.**
80
81### Quick Commands
82
83```bash
84deciduous add goal "Title" -c 90 -p "User's original request"
85deciduous add action "Title" -c 85
86deciduous link FROM TO -r "reason" # DO THIS IMMEDIATELY!
87deciduous serve # View live (auto-refreshes every 30s)
88deciduous sync # Export for static hosting
89
90# Metadata flags
91# -c, --confidence 0-100 Confidence level
92# -p, --prompt "..." Store the user prompt (use when semantically meaningful)
93# -f, --files "a.rs,b.rs" Associate files
94# -b, --branch <name> Git branch (auto-detected)
95# --commit <hash|HEAD> Link to git commit (use HEAD for current commit)
96
97# Branch filtering
98deciduous nodes --branch main
99deciduous nodes -b feature-auth
100```
101
102### ⚠️ CRITICAL: Link Commits to Actions/Outcomes
103
104**After every git commit, link it to the decision graph!**
105
106```bash
107git commit -m "feat: add auth"
108deciduous add action "Implemented auth" -c 90 --commit HEAD
109deciduous link <goal_id> <action_id> -r "Implementation"
110```
111
112The `--commit HEAD` flag captures the commit hash and links it to the node. The web viewer will show commit messages, authors, and dates.
113
114### Git Commit Message Format
115
116**Keep commit messages clean and concise:**
117- NO "Generated with Claude Code" or similar by-lines
118- NO "Co-Authored-By:" lines
119- NO "Files updated:" sections or file lists
120- Use concise summary line describing the change
121- Add additional details if needed, but keep it focused
122
123**CRITICAL: Commit separate concerns separately**
124- Each commit should address ONE specific fix or feature
125- DO NOT bundle multiple unrelated changes into a single commit
126- If fixing multiple bugs, create separate commits for each bug fix
127- Example: Login typeahead fix, card spacing fix, and toast refactor = 3 commits
128
129**Example:**
130```bash
131git commit -m "remove deprecated 'followed' field and cleanup codebase
132
133Removed backward compatibility code for deprecated 'followed' field:
134- Removed from AtprotoMatch type
135- Updated 6 files to use only followStatus Record
136- Replaced simple boolean check with multi-lexicon support
137- Database schema preserved (no migration needed)
138
139Also removed empty 'nul' file created accidentally."
140```
141
142### Git History & Deployment
143
144**CRITICAL: Include decision graph in code commits**
145
146Always run `deciduous sync` BEFORE committing code changes, then include the graph updates in the same commit:
147
148```bash
149# After making code changes and adding decision nodes:
150deciduous sync # Export graph
151git add src/your-file.ts docs/ # Stage code + graph
152git commit -m "your change" # Single commit with both
153```
154
155**DO NOT create separate commits for graph updates.** The decision graph is part of the code history and should be committed together.
156
157```bash
158# Export graph AND git history for web viewer
159deciduous sync
160
161# This creates:
162# - docs/graph-data.json (decision graph)
163# - docs/git-history.json (commit info for linked nodes)
164```
165
166To deploy to GitHub Pages:
1671. `deciduous sync` to export
1682. Push to GitHub
1693. Settings > Pages > Deploy from branch > /docs folder
170
171Your graph will be live at `https://<user>.github.io/<repo>/`
172
173### Branch-Based Grouping
174
175Nodes are auto-tagged with the current git branch. Configure in `.deciduous/config.toml`:
176```toml
177[branch]
178main_branches = ["main", "master"]
179auto_detect = true
180```
181
182### Audit Checklist (Before Every Sync)
183
1841. Does every **outcome** link back to what caused it?
1852. Does every **action** link to why you did it?
1863. Any **dangling outcomes** without parents?
187
188### Session Start Checklist
189
190```bash
191deciduous nodes # What decisions exist?
192deciduous edges # How are they connected? Any gaps?
193git status # Current state
194```
195
196### Multi-User Sync
197
198Share decisions across teammates:
199
200```bash
201# Export your branch's decisions
202deciduous diff export --branch feature-x -o .deciduous/patches/my-feature.json
203
204# Apply patches from teammates (idempotent)
205deciduous diff apply .deciduous/patches/*.json
206
207# Preview before applying
208deciduous diff apply --dry-run .deciduous/patches/teammate.json
209```
210
211PR workflow: Export patch → commit patch file → PR → teammates apply.
212
213### API Trace Capture
214
215Capture Claude API traffic to correlate decisions with actual API work:
216
217```bash
218# Run Claude through the deciduous proxy
219deciduous proxy -- claude
220
221# View traces in TUI (press 't' for Trace view)
222deciduous tui
223
224# View traces in web viewer (click "Traces" tab)
225deciduous serve
226```
227
228**Auto-linking**: When running through `deciduous proxy`, any `deciduous add` commands automatically link to the active API span. You'll see output like:
229
230```
231Created action #42 "Implementing auth" [traced: span #7]
232```
233
234This lets you see exactly which API calls produced which decisions - perfect for "vibe coding" visibility.
235
236**Trace commands:**
237```bash
238deciduous trace sessions # List all sessions
239deciduous trace spans <session_id> # List spans in a session
240deciduous trace link <session_id> <node_id> # Manual linking
241deciduous trace prune --days 30 # Cleanup old traces
242```
243
244## Project Overview
245
246ATlast is a web application that helps users find their followed accounts from other social platforms (TikTok, Instagram, Twitter/X) on Bluesky/AT Protocol. Users upload their data export files, which are parsed for usernames, then searched in the AT Protocol network.
247
248## Development Commands
249
250### Local Development
251```bash
252# Mock mode (frontend only, no backend/OAuth/database)
253npm run dev:mock
254
255# Full mode (with backend, OAuth, database)
256npm run dev:full # or npm run dev
257
258# Build for production
259npm run build
260
261# Initialize local database
262npm run init-db
263
264# Generate encryption keys for OAuth
265npm run generate-key
266```
267
268### Environment Configuration
269
270Two development modes supported:
271
272**Mock Mode** (.env.mock):
273- Set `VITE_LOCAL_MOCK=true`
274- No database or OAuth required
275- Uses MockApiAdapter for fake data
276
277**Full Mode** (.env):
278- Set `VITE_LOCAL_MOCK=false`
279- Requires PostgreSQL (local or Neon)
280- Requires OAuth keys and configuration
281- Must use `http://127.0.0.1:8888` (NOT localhost) for OAuth to work
282- See CONTRIBUTING.md for detailed setup
283
284## Architecture Overview
285
286### Frontend (React + TypeScript + Vite)
287
288**Core Structure:**
289- `src/pages/` - Page components (Login, Upload, Results, etc.)
290- `src/components/` - Reusable UI components
291- `src/lib/parsers/` - File parsing logic for different platforms
292- `src/lib/api/` - API client with adapter pattern (Real vs Mock)
293- `src/contexts/` - React contexts (SettingsContext for theme/preferences)
294- `src/hooks/` - Custom React hooks
295
296**API Client Pattern:**
297The app uses an adapter pattern for the API layer:
298- `src/lib/api/client.ts` - Factory that returns Real or Mock adapter based on ENV.IS_LOCAL_MOCK
299- `src/lib/api/adapters/RealApiAdapter.ts` - Calls Netlify Functions
300- `src/lib/api/adapters/MockApiAdapter.ts` - Returns fake data for frontend development
301- `src/lib/api/IApiClient.ts` - Interface both adapters implement
302
303**Platform Parsers:**
304- `src/lib/parsers/platformDefinitions.ts` - Defines parsing rules for each platform (Instagram, TikTok, etc.)
305- `src/lib/parsers/fileExtractor.ts` - Handles ZIP file uploads and extracts usernames
306- `src/lib/parsers/parserLogic.ts` - Implements extraction logic (HTML regex, JSON path traversal, TEXT regex)
307
308Each platform has multiple ParseRule entries defining:
309- `zipPath` - Location inside ZIP archive
310- `format` - "HTML" | "TEXT" | "JSON"
311- `rule` - Regex pattern string or JSON key path array
312
313### Backend (Netlify Functions)
314
315**Function Structure:**
316```
317netlify/functions/
318├── core/
319│ ├── middleware/ # Auth, error handling, session security
320│ ├── types/ # Shared types
321│ ├── errors/ # Custom error classes
322│ └── config/ # Configuration
323├── infrastructure/
324│ ├── oauth/ # OAuth client factory, stores
325│ ├── database/ # Database connection, service layer
326│ ├── cache/ # Caching utilities
327│ └── lexicons/ # AT Protocol lexicons
328├── services/ # Business logic (SessionService, FollowService, etc.)
329├── repositories/ # Data access layer
330└── utils/ # Shared utilities
331```
332
333**Key Functions:**
334- `oauth-start.ts` / `oauth-callback.ts` - AT Protocol OAuth flow
335- `session.ts` - Session management and validation
336- `batch-search-actors.ts` - Searches multiple usernames on Bluesky (includes ranking algorithm)
337- `check-follow-status.ts` - Checks if user follows specific DIDs
338- `batch-follow-users.ts` - Bulk follow operations
339- `save-results.ts` - Persists search results to database
340- `get-uploads.ts` - Retrieves user's upload history
341
342**Authentication Pattern:**
343All protected endpoints use:
3441. `withAuthErrorHandling()` middleware wrapper
3452. `AuthenticatedHandler` type (provides `context.sessionId`, `context.did`, `context.event`)
3463. `SessionService.getAgentForSession()` to get authenticated AT Protocol agent
347
348**Database:**
349- PostgreSQL via Neon serverless
350- Accessed through `DatabaseService` (infrastructure/database/)
351- Repositories pattern for data access (repositories/)
352
353**OAuth:**
354- AT Protocol OAuth using `@atproto/oauth-client-node`
355- OAuth client factory creates client with session/state stores
356- Private key (ES256) stored in `OAUTH_PRIVATE_KEY` env var
357- Public JWK served at `/jwks` endpoint
358- Must use `127.0.0.1` (not localhost) for local OAuth redirects
359
360### UI/Styling
361
362**Tailwind CSS with dual theme support:**
363- Light mode: purple/cyan color scheme
364- Dark mode: cyan/purple inverted
365- All components use `dark:` variant classes
366
367**Color System (from CONTRIBUTING.md):**
368- Text: purple-950/cyan-50 (primary), purple-750/cyan-250 (secondary)
369- Borders: cyan-500/purple-500 with opacity variants
370- Buttons: orange-600 (primary CTA), slate-600/700 (secondary)
371- Backgrounds: white/slate-900 (primary), purple-50/slate-900 (secondary)
372- Selected states: cyan-50 border-cyan-500 / purple-950/30 border-purple-500
373- Accents: orange-500/amber-500 (badges, progress)
374
375**Key Patterns:**
376- Mobile-first responsive design
377- List virtualization with `@tanstack/react-virtual` for large result sets
378- Code splitting and lazy loading for pages
379- Error boundaries throughout the app
380- Loading skeletons for async operations
381
382### Search & Matching Algorithm
383
384**Username Matching (batch-search-actors.ts):**
385The search uses a scoring system (0-100):
3861. Exact handle match (before first dot): 100
3872. Exact full handle match: 90
3883. Exact display name match: 80
3894. Partial handle match (contains): 60
3905. Partial full handle match: 50
3916. Partial display name match: 40
3927. Reverse partial match: 30
393
394All comparisons use normalized strings (lowercase, no special chars).
395Returns top 5 ranked results per username.
396
397**Result Enrichment:**
398- Fetches profiles in batches of 25 for post/follower counts
399- Checks follow status using custom lexicons (default: `app.bsky.graph.follow`)
400- Attaches follow status to each actor result
401
402## Key Technical Details
403
404### AT Protocol Integration
405- Uses `@atproto/api` for Bluesky API interactions
406- Uses `@atcute/identity-resolver` for DID resolution
407- Supports custom lexicons for experimental features
408- OAuth flow follows AT Protocol OAuth spec
409
410### File Upload Flow
4111. User uploads ZIP file (Instagram/TikTok data export)
4122. `fileExtractor.ts` reads ZIP in browser (using JSZip)
4133. Matches file paths to platform rules from `platformDefinitions.ts`
4144. `parserLogic.ts` extracts usernames (regex for HTML/TEXT, path traversal for JSON)
4155. Deduplicates and returns username list
4166. Frontend calls `/batch-search-actors` with username batches (max 50)
4177. Results stored in database via `/save-results`
418
419### Session Management
420- Session IDs stored in httpOnly cookies
421- Sessions linked to encrypted OAuth state stores
422- Session validation middleware checks database and expiry
423- Sessions tied to specific DIDs (user accounts)
424
425### Deployment
426- Hosted on Netlify
427- Static frontend built with Vite
428- Serverless functions for backend
429- Database on Neon (PostgreSQL)
430- OAuth redirects configured in netlify.toml
431
432## Adding New Features
433
434### Adding a New Social Platform
4351. Add parsing rules to `src/lib/parsers/platformDefinitions.ts`:
436```typescript
437export const PLATFORM_RULES: Record<string, ParseRule[]> = {
438 newplatform: [
439 {
440 zipPath: "path/in/zip/file.json",
441 format: "JSON",
442 rule: ["key", "path", "to", "username"],
443 },
444 ],
445};
446```
4472. Test with real data export from that platform
4483. Update UI in platform selection components
449
450### Adding a New API Endpoint
4511. Create function in `netlify/functions/your-endpoint.ts`
4522. Use `withAuthErrorHandling()` for protected endpoints
4533. Implement `AuthenticatedHandler` type
4544. Add method to `IApiClient.ts` interface
4555. Implement in both `RealApiAdapter.ts` and `MockApiAdapter.ts`
4566. Use via `apiClient.yourMethod()` in components
457
458### Adding Database Models
4591. Create migration script in `scripts/`
4602. Run against local database
4613. Update repository in `netlify/functions/repositories/`
4624. Update DatabaseService if needed
463
464## Important Notes
465
466- **OAuth Localhost Issue**: Must use `127.0.0.1:8888` not `localhost:8888` for local OAuth to work
467- **Batch Limits**: Search endpoint limited to 50 usernames per request
468- **Profile Fetching**: Batched in groups of 25 to avoid rate limits
469- **Normalization**: All username comparisons use lowercase + special char removal
470- **Security**: CSP headers configured in netlify.toml, session security middleware prevents CSRF
471- **Error Handling**: Custom error classes (ValidationError, AuthenticationError, etc.) with proper HTTP status codes