CLAUDE.md#
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Instructions#
Decision Graph Workflow#
THIS IS MANDATORY. Log decisions IN REAL-TIME, not retroactively.
The Core Rule#
BEFORE you do something -> Log what you're ABOUT to do
AFTER it succeeds/fails -> Log the outcome
CONNECT immediately -> Link every node to its parent
AUDIT regularly -> Check for missing connections
Behavioral Triggers - MUST LOG WHEN:#
| Trigger | Log Type | Example |
|---|---|---|
| User asks for a new feature | goal with -p |
"Add dark mode" |
| Choosing between approaches | decision |
"Choose state management" |
| About to write/edit code | action |
"Implementing Redux store" |
| Something worked or failed | outcome |
"Redux integration successful" |
| Notice something interesting | observation |
"Existing code uses hooks" |
CRITICAL: Capture VERBATIM User Prompts#
Prompts must be the EXACT user message, not a summary. When a user request triggers new work, capture their full message word-for-word.
BAD - summaries are useless for context recovery:
# DON'T DO THIS - this is a summary, not a prompt
deciduous add goal "Add auth" -p "User asked: add login to the app"
GOOD - verbatim prompts enable full context recovery:
# Use --prompt-stdin for multi-line prompts
deciduous add goal "Add auth" -c 90 --prompt-stdin << 'EOF'
I need to add user authentication to the app. Users should be able to sign up
with email/password, and we need OAuth support for Google and GitHub. The auth
should use JWT tokens with refresh token rotation.
EOF
# Or use the prompt command to update existing nodes
deciduous prompt 42 << 'EOF'
The full verbatim user message goes here...
EOF
When to capture prompts:
- Root
goalnodes: YES - the FULL original request - Major direction changes: YES - when user redirects the work
- Routine downstream nodes: NO - they inherit context via edges
Updating prompts on existing nodes:
deciduous prompt <node_id> "full verbatim prompt here"
cat prompt.txt | deciduous prompt <node_id> # Multi-line from stdin
Prompts are viewable in the TUI detail panel (deciduous tui) and web viewer.
⚠️ CRITICAL: Maintain Connections#
The graph's value is in its CONNECTIONS, not just nodes.
| When you create... | IMMEDIATELY link to... |
|---|---|
outcome |
The action/goal it resolves |
action |
The goal/decision that spawned it |
option |
Its parent decision |
observation |
Related goal/action |
Root goal nodes are the ONLY valid orphans.
Node Lifecycle Management#
Every node has a lifecycle. Update status in REAL-TIME:
# 1. Create node (defaults to 'pending')
deciduous add action "Implementing feature X" -c 85
# 2. IMMEDIATELY link to parent (before doing anything else)
deciduous link <parent_id> <new_node_id> -r "Reason for connection"
# 3. Mark as in_progress BEFORE starting work
deciduous status <node_id> in_progress
# 4. Do the work...
# 5. Mark as completed IMMEDIATELY after work finishes
deciduous status <node_id> completed
Status Transitions:
pending→ Default state when createdin_progress→ Mark BEFORE starting work (only ONE at a time)completed→ Mark IMMEDIATELY when done (proven by git commit, test pass, etc.)
CRITICAL RULES:
- ✅ Link nodes IMMEDIATELY after creation (same command sequence)
- ✅ Update status to
completedas soon as work is done - ✅ Only ONE node should be
in_progressat a time - ✅ Verify link exists before moving on (check
deciduous edges) - ❌ NEVER leave completed work marked as
pending - ❌ NEVER create orphan nodes (except root goals)
- ❌ NEVER batch status updates - update immediately
Verification Workflow:
# After creating and linking a node, verify:
deciduous edges | grep <new_node_id> # Should show incoming edge
deciduous nodes | grep <new_node_id> # Check status is correct
Common Mistakes That Break the Graph:
-
Creating nodes without linking → Orphans
# WRONG deciduous add action "Fix bug" -c 85 # (forget to link, move on to next task) # RIGHT deciduous add action "Fix bug" -c 85 deciduous link 42 43 -r "Action to resolve goal #42" -
Leaving nodes as "pending" after work completes → Stale status
# WRONG git commit -m "fix: bug fixed" # (forget to update node status) # RIGHT git commit -m "fix: bug fixed" deciduous status 43 completed -
Batch-creating multiple nodes before linking → Connection gaps
# WRONG deciduous add action "Task 1" -c 85 deciduous add action "Task 2" -c 85 deciduous add action "Task 3" -c 85 # (now have to remember all IDs to link) # RIGHT deciduous add action "Task 1" -c 85 deciduous link 42 43 -r "First task" deciduous add action "Task 2" -c 85 deciduous link 42 44 -r "Second task" -
Not regenerating parent list during orphan checks → False positives
# WRONG # (generate parent list once) deciduous link X Y -r "fix orphan" # (check orphans with stale parent list) # RIGHT deciduous link X Y -r "fix orphan" # Regenerate parent list before checking again deciduous edges | tail -n+3 | awk '{print $3}' | sort -u > /tmp/has_parent.txt
Quick Commands#
deciduous add goal "Title" -c 90 -p "User's original request"
deciduous add action "Title" -c 85
deciduous link FROM TO -r "reason" # DO THIS IMMEDIATELY!
deciduous serve # View live (auto-refreshes every 30s)
deciduous sync # Export for static hosting
# Metadata flags
# -c, --confidence 0-100 Confidence level
# -p, --prompt "..." Store the user prompt (use when semantically meaningful)
# -f, --files "a.rs,b.rs" Associate files
# -b, --branch <name> Git branch (auto-detected)
# --commit <hash|HEAD> Link to git commit (use HEAD for current commit)
# Branch filtering
deciduous nodes --branch main
deciduous nodes -b feature-auth
⚠️ CRITICAL: Link Commits to Actions/Outcomes#
After every git commit, link it to the decision graph!
git commit -m "feat: add auth"
deciduous add action "Implemented auth" -c 90 --commit HEAD
deciduous link <goal_id> <action_id> -r "Implementation"
The --commit HEAD flag captures the commit hash and links it to the node. The web viewer will show commit messages, authors, and dates.
Git Commit Message Format#
Keep commit messages clean and concise:
- NO "Generated with Claude Code" or similar by-lines
- NO "Co-Authored-By:" lines
- NO "Files updated:" sections or file lists
- Use concise summary line describing the change
- Add additional details if needed, but keep it focused
CRITICAL: Commit separate concerns separately
- Each commit should address ONE specific fix or feature
- DO NOT bundle multiple unrelated changes into a single commit
- If fixing multiple bugs, create separate commits for each bug fix
- Example: Login typeahead fix, card spacing fix, and toast refactor = 3 commits
Example:
git commit -m "remove deprecated 'followed' field and cleanup codebase
Removed backward compatibility code for deprecated 'followed' field:
- Removed from AtprotoMatch type
- Updated 6 files to use only followStatus Record
- Replaced simple boolean check with multi-lexicon support
- Database schema preserved (no migration needed)
Also removed empty 'nul' file created accidentally."
Git History & Deployment#
CRITICAL: Include decision graph in code commits
Always run deciduous sync BEFORE committing code changes, then include the graph updates in the same commit:
# After making code changes and adding decision nodes:
deciduous sync # Export graph
git add src/your-file.ts docs/ # Stage code + graph
git commit -m "your change" # Single commit with both
DO NOT create separate commits for graph updates. The decision graph is part of the code history and should be committed together.
# Export graph AND git history for web viewer
deciduous sync
# This creates:
# - docs/graph-data.json (decision graph)
# - docs/git-history.json (commit info for linked nodes)
To deploy to GitHub Pages:
deciduous syncto export- Push to GitHub
- Settings > Pages > Deploy from branch > /docs folder
Your graph will be live at https://<user>.github.io/<repo>/
Branch-Based Grouping#
Nodes are auto-tagged with the current git branch. Configure in .deciduous/config.toml:
[branch]
main_branches = ["main", "master"]
auto_detect = true
Audit Checklist (Before Every Sync)#
Run these checks before deciduous sync:
-
Connection integrity: Does every non-goal node have a parent?
deciduous edges | tail -n+3 | awk '{print $3}' | sort -u > /tmp/has_parent.txt deciduous nodes | tail -n+3 | awk '{print $1}' > /tmp/all_nodes.txt while read id; do grep -q "^$id$" /tmp/has_parent.txt || echo "CHECK: $id"; done < /tmp/all_nodes.txt # Only root goals should appear -
Status accuracy: Are completed nodes marked
completed?deciduous nodes | grep pending # Review: is this work actually still pending, or is it done? -
Active work: Is there exactly ONE
in_progressnode?deciduous nodes | grep in_progress # Should see 0-1 nodes, not multiple -
Logical flow: Does every outcome link back to what caused it?
outcome→actionorgoalaction→goalordecisionobservation→ relatedgoaloraction
Session Start Checklist#
deciduous nodes # What decisions exist?
deciduous edges # How are they connected? Any gaps?
git status # Current state
Multi-User Sync#
Share decisions across teammates:
# Export your branch's decisions
deciduous diff export --branch feature-x -o .deciduous/patches/my-feature.json
# Apply patches from teammates (idempotent)
deciduous diff apply .deciduous/patches/*.json
# Preview before applying
deciduous diff apply --dry-run .deciduous/patches/teammate.json
PR workflow: Export patch → commit patch file → PR → teammates apply.
API Trace Capture#
Capture Claude API traffic to correlate decisions with actual API work:
# Run Claude through the deciduous proxy
deciduous proxy -- claude
# View traces in TUI (press 't' for Trace view)
deciduous tui
# View traces in web viewer (click "Traces" tab)
deciduous serve
Auto-linking: When running through deciduous proxy, any deciduous add commands automatically link to the active API span. You'll see output like:
Created action #42 "Implementing auth" [traced: span #7]
This lets you see exactly which API calls produced which decisions - perfect for "vibe coding" visibility.
Trace commands:
deciduous trace sessions # List all sessions
deciduous trace spans <session_id> # List spans in a session
deciduous trace link <session_id> <node_id> # Manual linking
deciduous trace prune --days 30 # Cleanup old traces
Project Overview#
ATlast 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.
Development Commands#
Monorepo Structure#
This project uses pnpm workspaces with three packages:
packages/web- React frontendpackages/functions- Netlify serverless functionspackages/shared- Shared TypeScript types
Local Development#
IMPORTANT: Due to Netlify CLI monorepo detection, you must use the --filter flag:
# Mock mode (frontend only, no backend/OAuth/database)
pnpm run dev:mock
# Full mode (with backend, OAuth, database) - RECOMMENDED
npx netlify-cli dev --filter @atlast/web
# Alternative: Update root package.json scripts to use npx netlify-cli
pnpm run dev # If scripts are updated
# Build for production
pnpm run build
# Initialize local database
pnpm run init-db
# Generate encryption keys for OAuth
pnpm run generate-key
Note: On Windows, netlify command may not work in Git Bash. Use npx netlify-cli instead, or run from Windows CMD/PowerShell.
Environment Configuration#
Two development modes supported:
Mock Mode (.env.mock):
- Set
VITE_LOCAL_MOCK=true - No database or OAuth required
- Uses MockApiAdapter for fake data
Full Mode (.env):
- Set
VITE_LOCAL_MOCK=false - Requires PostgreSQL (local or Neon)
- Requires OAuth keys and configuration
- Must use
http://127.0.0.1:8888(NOT localhost) for OAuth to work - See CONTRIBUTING.md for detailed setup
Architecture Overview#
Frontend (React + TypeScript + Vite)#
Core Structure:
src/pages/- Page components (Login, Upload, Results, etc.)src/components/- Reusable UI componentssrc/lib/parsers/- File parsing logic for different platformssrc/lib/api/- API client with adapter pattern (Real vs Mock)src/contexts/- React contexts (SettingsContext for theme/preferences)src/hooks/- Custom React hooks
API Client Pattern: The app uses an adapter pattern for the API layer:
src/lib/api/client.ts- Factory that returns Real or Mock adapter based on ENV.IS_LOCAL_MOCKsrc/lib/api/adapters/RealApiAdapter.ts- Calls Netlify Functionssrc/lib/api/adapters/MockApiAdapter.ts- Returns fake data for frontend developmentsrc/lib/api/IApiClient.ts- Interface both adapters implement
Platform Parsers:
src/lib/parsers/platformDefinitions.ts- Defines parsing rules for each platform (Instagram, TikTok, etc.)src/lib/parsers/fileExtractor.ts- Handles ZIP file uploads and extracts usernamessrc/lib/parsers/parserLogic.ts- Implements extraction logic (HTML regex, JSON path traversal, TEXT regex)
Each platform has multiple ParseRule entries defining:
zipPath- Location inside ZIP archiveformat- "HTML" | "TEXT" | "JSON"rule- Regex pattern string or JSON key path array
Backend (Netlify Functions)#
Function Structure:
netlify/functions/
├── core/
│ ├── middleware/ # Auth, error handling, session security
│ ├── types/ # Shared types
│ ├── errors/ # Custom error classes
│ └── config/ # Configuration
├── infrastructure/
│ ├── oauth/ # OAuth client factory, stores
│ ├── database/ # Database connection, service layer
│ ├── cache/ # Caching utilities
│ └── lexicons/ # AT Protocol lexicons
├── services/ # Business logic (SessionService, FollowService, etc.)
├── repositories/ # Data access layer
└── utils/ # Shared utilities
Key Functions:
oauth-start.ts/oauth-callback.ts- AT Protocol OAuth flowsession.ts- Session management and validationbatch-search-actors.ts- Searches multiple usernames on Bluesky (includes ranking algorithm)check-follow-status.ts- Checks if user follows specific DIDsbatch-follow-users.ts- Bulk follow operationssave-results.ts- Persists search results to databaseget-uploads.ts- Retrieves user's upload history
Authentication Pattern: All protected endpoints use:
withAuthErrorHandling()middleware wrapperAuthenticatedHandlertype (providescontext.sessionId,context.did,context.event)SessionService.getAgentForSession()to get authenticated AT Protocol agent
Database:
- PostgreSQL via Neon serverless
- Accessed through
DatabaseService(infrastructure/database/) - Repositories pattern for data access (repositories/)
OAuth:
- AT Protocol OAuth using
@atproto/oauth-client-node - OAuth client factory creates client with session/state stores
- Private key (ES256) stored in
OAUTH_PRIVATE_KEYenv var - Public JWK served at
/jwksendpoint - Must use
127.0.0.1(not localhost) for local OAuth redirects
UI/Styling#
Tailwind CSS with dual theme support:
- Light mode: purple/cyan color scheme
- Dark mode: cyan/purple inverted
- All components use
dark:variant classes
Color System (from CONTRIBUTING.md):
- Text: purple-950/cyan-50 (primary), purple-750/cyan-250 (secondary)
- Borders: cyan-500/purple-500 with opacity variants
- Buttons: orange-600 (primary CTA), slate-600/700 (secondary)
- Backgrounds: white/slate-900 (primary), purple-50/slate-900 (secondary)
- Selected states: cyan-50 border-cyan-500 / purple-950/30 border-purple-500
- Accents: orange-500/amber-500 (badges, progress)
Key Patterns:
- Mobile-first responsive design
- List virtualization with
@tanstack/react-virtualfor large result sets - Code splitting and lazy loading for pages
- Error boundaries throughout the app
- Loading skeletons for async operations
Search & Matching Algorithm#
Username Matching (batch-search-actors.ts): The search uses a scoring system (0-100):
- Exact handle match (before first dot): 100
- Exact full handle match: 90
- Exact display name match: 80
- Partial handle match (contains): 60
- Partial full handle match: 50
- Partial display name match: 40
- Reverse partial match: 30
All comparisons use normalized strings (lowercase, no special chars). Returns top 5 ranked results per username.
Result Enrichment:
- Fetches profiles in batches of 25 for post/follower counts
- Checks follow status using custom lexicons (default:
app.bsky.graph.follow) - Attaches follow status to each actor result
Key Technical Details#
AT Protocol Integration#
- Uses
@atproto/apifor Bluesky API interactions - Uses
@atcute/identity-resolverfor DID resolution - Supports custom lexicons for experimental features
- OAuth flow follows AT Protocol OAuth spec
File Upload Flow#
- User uploads ZIP file (Instagram/TikTok data export)
fileExtractor.tsreads ZIP in browser (using JSZip)- Matches file paths to platform rules from
platformDefinitions.ts parserLogic.tsextracts usernames (regex for HTML/TEXT, path traversal for JSON)- Deduplicates and returns username list
- Frontend calls
/batch-search-actorswith username batches (max 50) - Results stored in database via
/save-results
Session Management#
- Session IDs stored in httpOnly cookies
- Sessions linked to encrypted OAuth state stores
- Session validation middleware checks database and expiry
- Sessions tied to specific DIDs (user accounts)
Deployment#
- Hosted on Netlify
- Static frontend built with Vite
- Serverless functions for backend
- Database on Neon (PostgreSQL)
- OAuth redirects configured in netlify.toml
Adding New Features#
Adding a New Social Platform#
- Add parsing rules to
src/lib/parsers/platformDefinitions.ts:
export const PLATFORM_RULES: Record<string, ParseRule[]> = {
newplatform: [
{
zipPath: "path/in/zip/file.json",
format: "JSON",
rule: ["key", "path", "to", "username"],
},
],
};
- Test with real data export from that platform
- Update UI in platform selection components
Adding a New API Endpoint#
- Create function in
netlify/functions/your-endpoint.ts - Use
withAuthErrorHandling()for protected endpoints - Implement
AuthenticatedHandlertype - Add method to
IApiClient.tsinterface - Implement in both
RealApiAdapter.tsandMockApiAdapter.ts - Use via
apiClient.yourMethod()in components
Adding Database Models#
- Create migration script in
scripts/ - Run against local database
- Update repository in
netlify/functions/repositories/ - Update DatabaseService if needed
Important Notes#
- OAuth Localhost Issue: Must use
127.0.0.1:8888notlocalhost:8888for local OAuth to work - Batch Limits: Search endpoint limited to 50 usernames per request
- Profile Fetching: Batched in groups of 25 to avoid rate limits
- Normalization: All username comparisons use lowercase + special char removal
- Security: CSP headers configured in netlify.toml, session security middleware prevents CSRF
- Error Handling: Custom error classes (ValidationError, AuthenticationError, etc.) with proper HTTP status codes