experiments in a post-browser web
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

Consolidate documentation and refactor AGENTS.md

Documentation consolidation:
- Move docs to docs/: cmd.md, mobile.md, themes.md
- Flatten notes/research/ into notes/
- Update README with condensed summary + design notes

AGENTS.md refactored:
- Reference generic workflow from ~/sync/Dev/agent-workflow
- Keep only Peek-specific content: protected dirs, process mgmt,
Railway deployment, iOS/Tauri mobile, better-sqlite3 tips

Removed:
- MULTI_AGENT_WORKFLOW.md (now in agent-workflow repo)
- Duplicate docs (extensions/README.md, docs/MOBILE.md)

+1043 -1682
+1 -1
.gitignore
··· 24 24 25 25 # Claude Code local settings 26 26 .claude/ 27 - CLAUDE.md 27 + **/CLAUDE.md 28 28 29 29 # Backup files 30 30 *.bak
+237
AGENTS.md
··· 1 + # Peek Agent Instructions 2 + 3 + This project uses the multi-agent workflow. See `~/sync/Dev/agent-workflow/` for: 4 + - `WORKFLOW.md` - Full workflow documentation 5 + - `AGENT_RULES.md` - Universal agent rules 6 + - `multi-agent.zsh` - Shell commands reference 7 + 8 + **Remotes:** 9 + - Tangled: `git@tangled.sh:burrito.space/peek` (primary) 10 + - GitHub: `git@github.com:autonome/peek` 11 + 12 + --- 13 + 14 + ## Quick Reference 15 + 16 + ```bash 17 + mnext # Start agent on next Today item (enters plan mode) 18 + magent <name> "prompt" # Create workspace + window + start claude 19 + mclean <name> # Remove workspace/window 20 + mmerge <name> "msg" # Squash + cleanup 21 + mpush # Push to remotes 22 + ``` 23 + 24 + --- 25 + 26 + ## Peek-Specific Rules 27 + 28 + ### Protected Directories 29 + 30 + **NEVER modify files in `./app` without explicit user approval.** The `app/` directory is backend-agnostic - it must work unchanged with both Electron and Tauri backends. All backend-specific code belongs in `backend/{electron,tauri}/`. If you think `app/` needs changes, ASK FIRST. 31 + 32 + ### Process Management 33 + 34 + **Only use `yarn kill` to kill dev processes.** NEVER use generic pkill commands like `pkill -f "Peek"` or `pkill -f "electron"` - these will kill the user's production app. 35 + 36 + ```bash 37 + yarn kill # ONLY way to kill dev Peek 38 + ``` 39 + 40 + **Testing without UI**: When testing startup, logs, or non-interactive behavior, use headless mode: 41 + ```bash 42 + ./scripts/test-headless.sh 8 # Run headless for 8 seconds, auto-kills 43 + yarn test:electron # Run automated tests 44 + ``` 45 + 46 + ### Command Pattern: Use scratch.sh 47 + 48 + For any command beyond simple `yarn` scripts: 49 + 50 + 1. Write commands to `scratch.sh` 51 + 2. Run `./scratch.sh` 52 + 53 + This keeps commands visible, auditable, and prevents `&&` chaining. 54 + 55 + ```bash 56 + # Example workflow 57 + echo '#!/bin/bash 58 + cargo build --target aarch64-apple-ios 59 + cp target/aarch64-apple-ios/release/lib*.a gen/apple/Externals/ 60 + ' > scratch.sh 61 + chmod +x scratch.sh 62 + ./scratch.sh 63 + ``` 64 + 65 + --- 66 + 67 + ## Development Resources 68 + 69 + - See `DEVELOPMENT.md` for architecture, commands, and common pitfalls 70 + - See `docs/api.md` for the Peek API reference 71 + - See `docs/extensions.md` for extension development 72 + - See `docs/mobile.md` for iOS/Android development 73 + 74 + --- 75 + 76 + ## Database Migrations 77 + 78 + ### Migration Ordering 79 + When adding columns via migration, ensure indexes on those columns are created AFTER the column migration: 80 + 81 + **Wrong:** 82 + ```javascript 83 + // CREATE TABLE includes index on column that doesn't exist yet 84 + db.exec(` 85 + CREATE TABLE IF NOT EXISTS items (...); 86 + CREATE INDEX IF NOT EXISTS idx_sync_id ON items(sync_id); // FAILS if table exists without column 87 + `); 88 + migrateAddSyncColumns(db); // Too late! 89 + ``` 90 + 91 + **Correct:** 92 + ```javascript 93 + db.exec(`CREATE TABLE IF NOT EXISTS items (...)`); 94 + migrateAddSyncColumns(db); // Adds sync_id column 95 + db.exec(`CREATE INDEX IF NOT EXISTS idx_sync_id ON items(sync_id)`); // Now safe 96 + ``` 97 + 98 + --- 99 + 100 + ## iOS/Tauri Mobile 101 + 102 + ### Build Both Libraries 103 + iOS requires BOTH debug and release Rust libraries: 104 + - **Debug** (aarch64-apple-ios-sim): For simulator testing 105 + - **Release** (aarch64-apple-ios): For device testing 106 + 107 + ```bash 108 + # Debug for simulator 109 + cargo tauri build --target aarch64-apple-ios-sim --debug 110 + cp target/aarch64-apple-ios-sim/debug/deps/lib*.a gen/apple/Externals/arm64/Debug/libapp.a 111 + 112 + # Release for device 113 + cargo tauri build --target aarch64-apple-ios 114 + cp target/aarch64-apple-ios/release/deps/lib*.a gen/apple/Externals/arm64/Release/libapp.a 115 + ``` 116 + 117 + ### Assets Symlink 118 + Xcode needs frontend assets linked: 119 + ```bash 120 + ln -s ../../../dist gen/apple/assets 121 + ``` 122 + 123 + ### Build in Xcode GUI 124 + Never use `xcodebuild` from terminal - use Xcode GUI for iOS builds. 125 + 126 + ### Xcode Build Hang Workaround 127 + The "Build Rust Code" pre-build script in Xcode can hang indefinitely. Pre-build the Rust library outside Xcode first, then open Xcode and build normally. 128 + 129 + --- 130 + 131 + ## Railway Deployment (Peek Server) 132 + 133 + ### Quick Reference 134 + 135 + | Item | Value | 136 + |------|-------| 137 + | Project | `amusing-courtesy` | 138 + | Service | `peek-node` | 139 + | Production URL | https://peek-node.up.railway.app/ | 140 + | Directory | `backend/server/` | 141 + 142 + ### Complete Deployment Workflow 143 + 144 + ```bash 145 + # 1. Navigate to server directory 146 + cd backend/server 147 + 148 + # 2. Run tests first (ALWAYS) 149 + npm test 150 + 151 + # 3. Link to Railway (one-time per workspace) 152 + railway link -p amusing-courtesy 153 + # Select: peek-node service, production environment 154 + 155 + # 4. Deploy (detached mode) 156 + railway up -d --service peek-node 157 + 158 + # 5. Check deployment logs 159 + railway logs -n 50 --service peek-node 160 + 161 + # 6. Health check 162 + curl https://peek-node.up.railway.app/ 163 + ``` 164 + 165 + ### Pre-Deployment Checklist 166 + 167 + Before deploying, verify: 168 + - [ ] `npm test` passes (all 92+ tests) 169 + - [ ] No breaking API changes (check if mobile app compatibility affected) 170 + - [ ] If database schema changed, migration handles existing data safely 171 + 172 + ### Creating Users and API Keys 173 + 174 + Users are managed via the `users.js` module. Run from `backend/server/`: 175 + 176 + ```javascript 177 + // In Node REPL: node -e "..." 178 + // Create a new user (returns API key ONCE - save it!) 179 + node -e "const u = require('./users'); console.log(u.createUser('username'))" 180 + 181 + // List all users 182 + node -e "const u = require('./users'); console.log(u.listUsers())" 183 + 184 + // Regenerate API key for existing user 185 + node -e "const u = require('./users'); console.log(u.regenerateApiKey('username'))" 186 + ``` 187 + 188 + **Important:** The raw API key is only shown once when created. Store it securely. 189 + 190 + ### Testing Against Production 191 + 192 + ```bash 193 + # Set environment variables 194 + export PEEK_PROD_KEY=<your-api-key> 195 + export PEEK_PROD_URL=https://peek-node.up.railway.app 196 + 197 + # Run production API tests 198 + npm run test:api:prod 199 + 200 + # Or test locally first 201 + export PEEK_LOCAL_KEY=<local-test-key> 202 + npm run test:api:local 203 + ``` 204 + 205 + ### Troubleshooting 206 + 207 + | Problem | Solution | 208 + |---------|----------| 209 + | Service not starting | Check `railway logs -n 100 --service peek-node` for errors | 210 + | Database errors | Ensure volume is mounted at `DATA_DIR` path | 211 + | Node module errors | Nixpacks rebuilds on each deploy; check Node version in logs | 212 + | 401 Unauthorized | Verify API key with health check first; check key format | 213 + | Deploy stuck | Cancel with Ctrl+C, check Railway dashboard for status | 214 + | Old code running | Railway caches; force rebuild via dashboard or redeploy | 215 + 216 + ### Server + Mobile Deployment Order 217 + 218 + When updating both server and mobile: 219 + 1. **Server first** - stateless, auto-migrations run on first request 220 + 2. **Mobile second** - works offline, adapts to server changes 221 + 222 + --- 223 + 224 + ## Electron/Desktop 225 + 226 + ### better-sqlite3 Node vs Electron Version Mismatch 227 + Native modules compile for specific Node versions. Electron uses a different version than system Node. 228 + 229 + ```bash 230 + # For Node.js (server tests, sync E2E tests) 231 + npm rebuild better-sqlite3 232 + 233 + # For Electron (desktop app) 234 + npx electron-rebuild -f -w better-sqlite3 235 + ``` 236 + 237 + If you see `NODE_MODULE_VERSION` mismatch errors, rebuild for the correct runtime.
+23 -3
DEVELOPMENT.md
··· 171 171 - Running from source (`yarn start`) uses `dev` profile 172 172 - `PROFILE` env var overrides automatic detection 173 173 174 + ## Critical Rules 175 + 176 + ### Protected Directories 177 + 178 + **NEVER modify files in `./app` without explicit review.** The `app/` directory is backend-agnostic - it must work unchanged with both Electron and Tauri backends. All backend-specific code belongs in `backend/{electron,tauri}/`. 179 + 180 + ### Process Management 181 + 182 + **Only use `yarn kill` to kill dev processes.** NEVER use generic pkill commands like `pkill -f "Peek"` or `pkill -f "electron"` - these will kill the production app if it's running. 183 + 184 + ```bash 185 + yarn kill # ONLY way to kill dev Peek 186 + ``` 187 + 188 + **Testing without UI**: When testing startup, logs, or non-interactive behavior, use headless mode: 189 + ```bash 190 + ./scripts/test-headless.sh 8 # Run headless for 8 seconds, auto-kills 191 + yarn test:electron # Run automated tests 192 + ``` 193 + 174 194 ## Common Pitfalls 175 195 176 196 1. Don't use relative paths in peek:// URLs - use absolute paths ··· 181 201 6. Check if items are enabled (`item.enabled == true`) before registering shortcuts 182 202 7. Datastore API returns `{ success, data }` - always check `result.success` 183 203 8. Pubsub subscriptions are keyed by source - same source subscribing twice overwrites 184 - 9. **Never put backend-specific code in `app/`** 185 - 10. **Run `yarn install` after Electron upgrades** - Native modules (better-sqlite3) must be recompiled for the new Electron ABI. The postinstall script handles this automatically, but you must run `yarn install` to trigger it. System Node and Electron have different ABIs even at the same major version (e.g., Node 24 = ABI 137, Electron 40 = ABI 143). 204 + 9. **Never put backend-specific code in `app/`** - see Critical Rules above 205 + 10. **Run `yarn install` after Electron upgrades** - Native modules (better-sqlite3) must be recompiled for the new Electron ABI. The postinstall script handles this automatically, but you must run `yarn install` to trigger it. 186 206 187 207 ## Code Style 188 208 ··· 357 377 358 378 ### Server Deployment (Railway) 359 379 360 - > **For the comprehensive deployment guide**, see the "Railway Deployment (Peek Server)" section in `CLAUDE.md`. This includes step-by-step workflow, user management, production testing, and troubleshooting. 380 + > **For the comprehensive deployment guide**, see the "Railway Deployment (Peek Server)" section in `AGENTS.md`. This includes step-by-step workflow, user management, production testing, and troubleshooting. 361 381 362 382 The server is configured for Railway deployment. 363 383
-474
MULTI_AGENT_WORKFLOW.md
··· 1 - # Multi-Agent Development Workflow for Peek 2 - 3 - ## Overview 4 - 5 - Parallel AI agent development using jj workspaces + tmux for the Peek project. Work on desktop, mobile, server, and other components simultaneously in isolated worktrees. 6 - 7 - **Remotes:** 8 - - Tangled: `git@tangled.sh:burrito.space/peek` 9 - - GitHub: `git@github.com:autonome/peek` 10 - 11 - --- 12 - 13 - ## Setup 14 - 15 - ### 1. Add shell functions to ~/.zshrc 16 - 17 - Copy the following to your `~/.zshrc`: 18 - 19 - ```bash 20 - # ============================================ 21 - # Multi-agent workflow - jj workspaces + tmux 22 - # ============================================ 23 - 24 - # --- Workspace listing --- 25 - alias mls="jj workspace list" # list all workspaces 26 - alias mwin="tmux list-windows" # list tmux windows 27 - 28 - # --- Quick status --- 29 - alias mlog="jj log --all" # log across all workspaces 30 - 31 - # --- Functions --- 32 - 33 - # Create agent worktree with tmux window 34 - # Usage: magent desktop-auth "Implement OAuth2 login" 35 - function magent { 36 - local name="$1" 37 - local prompt="$2" 38 - local base_dir=$(dirname "$PWD") 39 - local repo_name=$(basename "$PWD") 40 - local worktree_path="${base_dir}/${repo_name}-${name}" 41 - 42 - # Create jj workspace 43 - jj workspace add "$worktree_path" --name "$name" 44 - 45 - # Create tmux window 46 - tmux new-window -n "$name" -c "$worktree_path" 47 - 48 - # Start claude with prompt if provided 49 - if [[ -n "$prompt" ]]; then 50 - tmux send-keys -t "$name" "claude \"$prompt\"" Enter 51 - else 52 - tmux send-keys -t "$name" "claude" Enter 53 - fi 54 - 55 - echo "Created workspace: $name" 56 - echo "Worktree: $worktree_path" 57 - } 58 - 59 - # Clean up agent workspace 60 - # Usage: mclean desktop-auth 61 - function mclean { 62 - local name="$1" 63 - local base_dir=$(dirname "$PWD") 64 - local repo_name=$(basename "$PWD") 65 - local worktree_path="${base_dir}/${repo_name}-${name}" 66 - 67 - # Forget workspace 68 - jj workspace forget "$name" 2>/dev/null 69 - 70 - # Remove directory 71 - if [[ -d "$worktree_path" ]]; then 72 - rm -rf "$worktree_path" 73 - echo "Removed: $worktree_path" 74 - fi 75 - 76 - # Kill tmux window 77 - tmux kill-window -t "$name" 2>/dev/null && echo "Killed window: $name" 78 - } 79 - 80 - # List all agents with status 81 - # Usage: magents 82 - function magents { 83 - echo "=== Workspaces ===" 84 - jj workspace list 2>/dev/null || echo "(not in a jj repo)" 85 - echo "" 86 - echo "=== Tmux Windows ===" 87 - tmux list-windows -F " #{window_index}: #{window_name}" 2>/dev/null || echo "(no tmux session)" 88 - } 89 - 90 - # Switch to agent window 91 - # Usage: mgo desktop-auth 92 - function mgo { 93 - tmux select-window -t "$1" 94 - } 95 - 96 - # Squash and describe current workspace changes 97 - # Usage: mdone "feat(desktop): add OAuth2 login" 98 - function mdone { 99 - jj squash -m "$1" 100 - echo "Squashed with: $1" 101 - } 102 - 103 - # Review diff in agent workspace 104 - # Usage: mreview desktop-auth 105 - function mreview { 106 - local name="$1" 107 - local base_dir=$(dirname "$PWD") 108 - local repo_name=$(basename "$PWD") 109 - local worktree_path="${base_dir}/${repo_name}-${name}" 110 - 111 - if [[ -d "$worktree_path" ]]; then 112 - jj diff --from main --to "$name" 113 - else 114 - echo "Workspace not found: $name" 115 - fi 116 - } 117 - 118 - # Merge agent work to main and cleanup 119 - # Usage: mmerge desktop-auth "feat(desktop): add OAuth2" 120 - function mmerge { 121 - local name="$1" 122 - local message="$2" 123 - local base_dir=$(dirname "$PWD") 124 - local repo_name=$(basename "$PWD") 125 - local worktree_path="${base_dir}/${repo_name}-${name}" 126 - 127 - # Squash in the workspace 128 - (cd "$worktree_path" && jj squash -m "$message") 129 - 130 - # Cleanup 131 - mclean "$name" 132 - 133 - echo "Merged and cleaned: $name" 134 - } 135 - 136 - # Push to all remotes (tangled + github) 137 - # Usage: mpush 138 - function mpush { 139 - jj bookmark set main -r @- 2>/dev/null 140 - jj git push --all-remotes 141 - } 142 - 143 - # Spawn multiple agents from a task file 144 - # Usage: mspawn tasks.txt 145 - # Format: each line is "name: prompt" 146 - function mspawn { 147 - local taskfile="$1" 148 - if [[ ! -f "$taskfile" ]]; then 149 - echo "Task file not found: $taskfile" 150 - return 1 151 - fi 152 - 153 - while IFS=': ' read -r name prompt; do 154 - [[ -z "$name" || "$name" == \#* ]] && continue 155 - echo "Spawning: $name" 156 - magent "$name" "$prompt" 157 - sleep 1 # Brief pause between spawns 158 - done < "$taskfile" 159 - } 160 - ``` 161 - 162 - Then reload: `source ~/.zshrc` 163 - 164 - ### 2. Initialize the repo 165 - 166 - ```bash 167 - cd ~/projects 168 - git clone git@github.com:autonome/peek peek 169 - cd peek 170 - jj git init --colocate 171 - 172 - # Add remotes 173 - jj git remote add tangled git@tangled.sh:burrito.space/peek 174 - jj git remote add github git@github.com:autonome/peek 175 - ``` 176 - 177 - ### 3. Start a tmux session 178 - 179 - ```bash 180 - tmux new-session -s peek -n coord 181 - ``` 182 - 183 - --- 184 - 185 - ## Shell Commands Reference 186 - 187 - | Command | Description | 188 - |---------|-------------| 189 - | `magent <name> "prompt"` | Create workspace + tmux window, start claude | 190 - | `mclean <name>` | Remove workspace, directory, and tmux window | 191 - | `magents` | List all workspaces and tmux windows | 192 - | `mgo <name>` | Switch to agent's tmux window | 193 - | `mdone "message"` | Squash current workspace changes | 194 - | `mreview <name>` | Show diff for agent workspace vs main | 195 - | `mmerge <name> "msg"` | Squash, merge to main, cleanup | 196 - | `mpush` | Push to all remotes | 197 - | `mspawn tasks.txt` | Spawn multiple agents from file | 198 - | `mstatus` | Show all agent statuses | 199 - | `mwatch` | Watch agent status (live updates) | 200 - | `mnotify "title" "msg"` | Send desktop notification | 201 - | `mls` | List jj workspaces | 202 - | `mwin` | List tmux windows | 203 - | `mlog` | Log across all workspaces | 204 - 205 - --- 206 - 207 - ## Workflow 208 - 209 - ### Directory Structure 210 - 211 - ``` 212 - ~/projects/ 213 - ├── peek/ # Main working copy 214 - ├── peek-desktop-auth/ # Agent worktree 215 - ├── peek-mobile-offline/ # Agent worktree 216 - └── peek-server-api/ # Agent worktree 217 - ``` 218 - 219 - ### Typical Session 220 - 221 - **1. Start coordinator** 222 - ```bash 223 - cd ~/projects/peek 224 - tmux new-session -s peek -n coord 225 - claude 226 - ``` 227 - 228 - Ask claude to explore and create a task list: 229 - > "Explore the codebase and create TODO.md with improvements by component" 230 - 231 - **2. Spawn agents** 232 - ```bash 233 - magent desktop-auth "Implement OAuth2 login. See TODO.md" 234 - magent mobile-offline "Add offline caching. See TODO.md" 235 - magent server-api "Refactor REST API to OpenAPI. See TODO.md" 236 - ``` 237 - 238 - **3. Monitor** 239 - ```bash 240 - magents # See all workspaces/windows 241 - Ctrl-b 1 # Switch to window 1 242 - Ctrl-b 2 # Switch to window 2 243 - mreview desktop-auth # Review agent's diff 244 - ``` 245 - 246 - **4. Merge completed work** 247 - ```bash 248 - mmerge desktop-auth "feat(desktop): add OAuth2 login" 249 - mpush # Push to tangled + github 250 - ``` 251 - 252 - ### Batch Spawning 253 - 254 - Create a task file: 255 - ``` 256 - # tasks.txt 257 - desktop-auth: Implement OAuth2 login for desktop app 258 - mobile-offline: Add offline caching for mobile 259 - server-api: Refactor REST API to use OpenAPI spec 260 - ``` 261 - 262 - Spawn all at once: 263 - ```bash 264 - mspawn tasks.txt 265 - ``` 266 - 267 - --- 268 - 269 - ## Tmux Tips 270 - 271 - ```bash 272 - Ctrl-b c # New window 273 - Ctrl-b <number> # Switch to window 274 - Ctrl-b , # Rename window 275 - Ctrl-b w # Window picker 276 - Ctrl-b d # Detach session 277 - tmux attach -t peek # Reattach 278 - ``` 279 - 280 - --- 281 - 282 - ## jj Tips 283 - 284 - ```bash 285 - jj workspace list # See all workspaces 286 - jj log -r 'all()' # Log all changes 287 - jj diff --from main # Diff against main 288 - jj squash -m "message" # Squash with message 289 - jj git push --all-remotes # Push to all remotes 290 - ``` 291 - 292 - ### Handling Conflicts 293 - 294 - If two agents modify the same files: 295 - ```bash 296 - jj git fetch 297 - jj rebase -s <change-id> -d <other-change-id> 298 - jj resolve 299 - ``` 300 - 301 - --- 302 - 303 - ## Why Not Workmux? 304 - 305 - We get 90% of workmux with shell functions: 306 - 307 - | Need | Our Solution | 308 - |------|--------------| 309 - | Create worktree + window | `magent` | 310 - | Cleanup | `mclean` | 311 - | Status dashboard | `magents` + tmux window names | 312 - | Merge workflow | `mmerge` | 313 - 314 - **Advantages of rolling our own:** 315 - - jj-native (workspaces, not git worktrees) 316 - - No extra dependency 317 - - Easy to customize 318 - - Fewer moving parts 319 - 320 - --- 321 - 322 - ## Agent Status Protocol 323 - 324 - Agents report their status so the coordinator can monitor progress without watching each window. 325 - 326 - ### Status States 327 - 328 - | State | When to use | Triggers notification? | 329 - |-------|-------------|----------------------| 330 - | `working` | Actively working on task | No | 331 - | `blocked` | Need input or clarification | Yes | 332 - | `review` | Ready for human review | Yes | 333 - | `done` | Task completed | Yes | 334 - | `error` | Something went wrong | Yes | 335 - 336 - ### How Agents Report Status 337 - 338 - Agents run this bash command to update status: 339 - ```bash 340 - agent-status "state" "brief message" 341 - ``` 342 - 343 - Examples: 344 - ```bash 345 - agent-status "working" "implementing sync API" 346 - agent-status "blocked" "need clarification on auth approach" 347 - agent-status "review" "sync working, ready for testing" 348 - agent-status "done" "pushed to tangled" 349 - agent-status "error" "build failing, see logs" 350 - ``` 351 - 352 - ### When to Update Status 353 - 354 - 1. **On start**: `agent-status "working" "starting task"` 355 - 2. **On progress**: Update when switching subtasks 356 - 3. **When blocked**: Immediately, so coordinator knows 357 - 4. **When done**: Before finishing, so work can be reviewed 358 - 359 - ### Coordinator Commands 360 - 361 - ```bash 362 - mstatus # Show all agent statuses (one-shot) 363 - mwatch # Live-updating status display 364 - ``` 365 - 366 - Status files are stored in `~/.agent-status/` and include timestamps. 367 - 368 - --- 369 - 370 - ## Agent Behavior Rules 371 - 372 - These rules apply to all Claude agents working in this system. 373 - 374 - ### Git Commit Policy 375 - 376 - - User (dietrich ayala) is sole author of all commits 377 - - Do not add co-author lines or "Generated with Claude" to commit messages 378 - - Do not commit changes unless explicitly asked - leave commits to the user 379 - 380 - ### Process Management 381 - 382 - **CRITICAL**: Only use `yarn kill` to kill dev processes. NEVER use generic pkill commands like `pkill -f "Peek"` or `pkill -f "electron"` - these will kill the user's production app. 383 - 384 - ```bash 385 - yarn kill # ONLY way to kill dev Peek 386 - ``` 387 - 388 - **Testing without UI**: When testing startup, logs, or non-interactive behavior, use headless mode: 389 - ```bash 390 - ./scripts/test-headless.sh 8 # Run headless for 8 seconds, auto-kills 391 - yarn test:electron # Run automated tests 392 - ``` 393 - 394 - Only run `yarn dev` or `yarn start` (foreground with UI) when you need the user to interact with the app. 395 - 396 - ### Protected Directories 397 - 398 - **NEVER modify files in `./app` without explicit user approval.** The `app/` directory is backend-agnostic - it must work unchanged with both Electron and Tauri backends. All backend-specific code belongs in `backend/{electron,tauri}/`. If you think `app/` needs changes, ASK FIRST. 399 - 400 - ### TODO Management 401 - 402 - - Project todos go in `TODO.md`, not in CLAUDE.md 403 - - Use checkbox syntax: `- [ ] task` for pending, `- [x] task` for done 404 - - Keep entries brief and actionable 405 - 406 - ### Status Reporting 407 - 408 - **Agents MUST report status** so the coordinator can track progress: 409 - 410 - ```bash 411 - # On start 412 - agent-status "working" "starting: brief description" 413 - 414 - # When blocked or need input 415 - agent-status "blocked" "need clarification on X" 416 - 417 - # When ready for review 418 - agent-status "review" "task complete, ready for review" 419 - 420 - # When completely done 421 - agent-status "done" "summary of what was accomplished" 422 - ``` 423 - 424 - See "Agent Status Protocol" section above for full details. 425 - 426 - ### Agent Setup 427 - 428 - When starting in a new workspace, get pre-configured permissions: 429 - ```bash 430 - mkdir -p .claude 431 - cp ~/sync/Dev/agent-workflow/claude-permissions.json .claude/settings.local.json 432 - ``` 433 - 434 - ### Development Resources 435 - 436 - - See `DEVELOPMENT.md` for architecture, commands, and common pitfalls 437 - - See `docs/api.md` for the Peek API reference 438 - - See `docs/extensions.md` for extension development 439 - 440 - --- 441 - 442 - ## Appendix: Workmux Reference 443 - 444 - If you later want workmux for its config file features: 445 - 446 - ```bash 447 - brew install raine/workmux/workmux 448 - ``` 449 - 450 - `.workmux.yaml`: 451 - ```yaml 452 - panes: 453 - - command: claude 454 - focus: true 455 - - command: npm install && npm run dev 456 - split: horizontal 457 - 458 - files: 459 - symlink: 460 - - node_modules 461 - - .jj 462 - copy: 463 - - .env 464 - ``` 465 - 466 - Main commands: 467 - - `workmux add <name>` - Create worktree + window 468 - - `workmux merge` - Merge to main, cleanup 469 - - `workmux merge --rebase` - Rebase first, then merge 470 - 471 - The config file is useful when you need: 472 - - Auto-run setup commands per worktree 473 - - Symlinked `node_modules` to avoid reinstalling 474 - - Multiple panes per agent (code + dev server)
+145 -118
README.md
··· 1 1 # Peek 2 2 3 - Peek is a web user agent application designed for using the web where, when and how you want. 3 + A web user agent for using the web where, when, and how you want. 4 4 5 - Today's browsers are one-size-fits-all applications, cramming the vast universe of user needs across an unimaginably large web into in an unmodifiable tabbed-window design. 5 + Peek is not a browser. It's a workbench for experimenting with task-aligned interfaces for the web - making it easy to create new UI shapes for the web that fit your needs in the moment. 6 6 7 - Peek is a web user agent that is a workbench for experimenting with task alignment - making it easy to create new user interface shapes for the web which fit our need in the moment. 8 - 9 - We often use the web with a specific goal in mind - that goal should drive the design of the interface of the web user agent. 7 + **Status:** Concept preview. Not safe for daily use. No security audit. 10 8 11 9 <img width="969" alt="settings screenshot" src="settings-screenshot.png"> 12 10 13 - ## Important notes 11 + ## Features 14 12 15 - ___PEEK IS NOT A WEB BROWSER___ 13 + - **Peeks** - Keyboard-activated modal web pages (`Opt+0-9`) 14 + - **Slides** - Gesture-activated pages that slide in from screen edges (`Opt+arrows`) 15 + - **Scripts** - Background page monitors that extract and track data 16 + - **Commands** - Command palette for opening pages and executing actions 17 + - **Groups** - Tag-based page organization (like Firefox Panorama) 18 + - **Sync** - Cross-device sync between desktop, mobile, and server 16 19 17 - Peek is not a web browser, and will never be a browser in the way you are probably familiar with: There are no tabs, and no windows in the tabbed-browser-like sense of them. Peek likely does not have many other of many details we are used to in web browsers, but do not notice until they are missing. Peek may be most useful to you if you view it as an entirely different type of application than a traditional web browser. 20 + ## Quick Start 18 21 19 - ___PEEK IS A CONCEPT PREVIEW___ 22 + ```bash 23 + # Requirements: Node.js 24+ 24 + nvm use 24 20 25 21 - Peek is not safe for daily use yet! It is a proof of concept. Do not use it for anything critical. Peek does not have the same security approach as traditional web browsers, and its security model and security user interface have not yet been determined. Peek has not had a security audit. 26 + # Install and run 27 + yarn install 28 + yarn debug # Development mode with devtools 29 + yarn start # Normal mode 30 + ``` 22 31 23 - ## Features 32 + See [DEVELOPMENT.md](DEVELOPMENT.md) for full development guide. 24 33 25 - You can use Peek in a few ways, with more coming: 34 + ## Architecture 26 35 27 - - Peeks - Keyboard-activated modal chromeless web pages for quickly glancing at or interacting with pages 28 - - Slides - Keyboard- or gesture-activated modal chromeless web pages which slide in from any screen edges 29 - - Scripts - Scripts periodically executed against a web page in the background which extract data for you to route to other pages or applications, or to aggregate, store and process later 36 + Peek supports multiple backends sharing the same storage/api approach: 30 37 31 - In progress, or thinking about: 38 + ``` 39 + peek/ 40 + ├── app/ # Renderer (backend-agnostic) 41 + ├── extensions/ # Built-in extensions 42 + ├── backend/ 43 + │ ├── electron/ # Desktop (primary) 44 + │ ├── tauri/ # Desktop (Rust alternative) 45 + │ ├── tauri-mobile/ # iOS/Android 46 + │ └── server/ # Sync server (Node.js/Hono) 47 + └── docs/ # Documentation 48 + ``` 32 49 33 - - Commands - a graphical command entry palette (GCLI) for opening pages or executing commands against them 34 - - Groups - a way to categorize, recall and interact with groups of pages 35 - - "native" web apps - using Peek as a way to "install" web pages on the local device, as separate applications instead of just separate processes 36 - - "Peeklets" HUD - select parts of pages to add to collection which are rendered as an overlay, toggled by shortcut 50 + ## Documentation 37 51 38 - ### Usage 52 + | Doc | Description | 53 + |-----|-------------| 54 + | [DEVELOPMENT.md](DEVELOPMENT.md) | Development setup, commands, architecture | 55 + | [docs/api.md](docs/api.md) | Peek API reference (`window.app`) | 56 + | [docs/extensions.md](docs/extensions.md) | Extension development | 57 + | [docs/cmd.md](docs/cmd.md) | Command palette extension | 58 + | [docs/themes.md](docs/themes.md) | Theme system | 59 + | [docs/datastore.md](docs/datastore.md) | Data storage and schema | 60 + | [docs/sync.md](docs/sync.md) | Cross-device sync | 61 + | [docs/mobile.md](docs/mobile.md) | Mobile development (iOS/Android) | 39 62 40 - - Settings 41 - * In app, `Cmd/Ctrl+r,` or launch app to open settings, or click tray icon 42 - * Configure Peeks/Slides/Scripts in settings 43 - - Peeks 44 - * `Opt+0-9` to open Peeks 45 - - Slides 46 - * `Opt+←→↑↓` to open Slides 63 + ## Notes 47 64 48 - ### Peeks 65 + Design thinking, research, and implementation notes in `notes/`: 49 66 50 - Peeks are keyboard activated modal chromeless web pages mapped to `Opt+0-9` and closed on blur, the `Escape` key or `cmd/ctrl+w`. 67 + | Note | Topic | 68 + |------|-------| 69 + | [design-vision](notes/design-vision.md) | Original vision, use cases, project history | 70 + | [extensibility](notes/extensibility.md) | Extension model design | 71 + | [escape-navigation](notes/escape-navigation.md) | IZUI navigation model | 72 + | [commands](notes/commands.md) | Command palette design | 73 + | [cmd-backend-notes](notes/cmd-backend-notes.md) | Cmd chaining backend implementation | 74 + | [core-api](notes/core-api.md) | API design notes | 75 + | [shortcuts-api](notes/shortcuts-api.md) | Keyboard shortcuts | 76 + | [groups-model](notes/groups-model.md) | Tab groups / Panorama | 77 + | [page-model](notes/page-model.md) | Page abstraction | 78 + | [ux-rules](notes/ux-rules.md) | UX guidelines | 79 + | [backup](notes/backup.md) | Backup strategy | 80 + | [datastore-*](notes/) | Datastore research (5 files) | 81 + | [sync-*](notes/) | Sync research (2 files) | 51 82 52 - ### Slides 83 + ## Contributing 53 84 54 - Slides are gesture activated modal chromeless web pages which slide in from left/right/bottom/top, and closed on blur, the `Escape` key or `cmd/ctrl+w`. 85 + Concept stage - contributions welcome but expect dragons. 55 86 56 - ### Scripts 87 + ## License 57 88 58 - Scripts periodically load a web page in the background and extract data matching a CSS selector, stores it, and notify the user when the resulting data changes. 89 + MIT 59 90 60 - Ok, so not really "scripts" yet. But safe and effective enough for now. 91 + --- 61 92 62 - ## Design 93 + # Design Notes (to review/archive) 94 + 95 + ## Core Design Philosophy 63 96 64 97 Many user tasks on the web are either transient, chained or persistent, data oriented, or some mix of those. Neither the document-oriented nor application-centric web meets those needs. Traditional browser makers can't meet those needs well, for many reasons. 65 98 ··· 77 110 - Windows and tabs should have died a long time ago, a mixed metaphor constraining the ability of the web to grow/thrive/change and meet user needs 78 111 - Security user interface must be a clear articulation of risks and trade-offs, and users should own the decisions 79 112 80 - ### Escape IZUI 113 + ## Escape IZUI 81 114 82 115 TODO: articulate the escape-to-leave aspect, eg you can peek from *other* applications and ESC to go back to exactly where you were without breaking the task flow. 83 116 84 117 Escape is an inverted zooming user interface (IZUI) design for a flexible window manager that makes possible a web user agent application than can have multiple entry points and a heterogeneous windowing ecosystem. 85 118 86 - IZUI vs ZUI 119 + ### IZUI vs ZUI 87 120 88 121 * ZUIs navigate by starting from a known root and user navigates by zooming ever further in, and then back out 89 122 * Escape can enter a window stack at any point, and via a variety of methods, often from outside the application ··· 91 124 * This design allows unbounded and diverse entry points, but with predictable behavior 92 125 * Regardless of the entry point, the user always has a consistent path to familiar ground 93 126 94 - Escape navigation model 127 + ### Escape navigation model 95 128 * navigation base can start at any level in stack 96 129 * forward navigations are added on top of stack 97 130 * backwards navigations walk the stack in reverse up the tree to the root 98 131 99 - ## Architecture / Implementation 132 + ## Architecture Principles 100 133 101 134 About this space: 102 135 ··· 105 138 - Decouple html+js+css from http+dns+ssl - not entirely, but that trust+security model is not a required starting point 106 139 - Javascript is ok here 107 140 108 - Peek is designed to be modular and configurable around the idea that parts of it 109 - can run in different environments. 141 + Peek is designed to be modular and configurable around the idea that parts of it can run in different environments. 110 142 111 143 For example: 112 144 - Planning on a mobile app which syncs and runs your peeks/slides/scripts 113 145 - I'd like to have a decentralized compute option for running your scripts outside of your clients and syncing the data 114 146 - Want cloud storage for all config and data, esp infinite history, so can do fun things with it 115 147 116 - ### Feature extensibility 148 + ## Feature Extensibility 117 149 118 150 An extensibility model for achieving "personal web workbench" requires a few things: 119 151 - UI extensibility requires OS-level window features beyond what the web allows today (also a baby step towards a minimal OS user interface) 120 152 - Data harvest/transform/process/publish requires a method of moving data between features (web apps) *locally*, cf Web Actions/Intents/Applets, MCP, pubsub, MQTT etc 121 - - Portable ways of accessing network, storage and compute, which address 153 + - Portable ways of accessing network, storage and compute, which address 122 154 123 155 The current implementation has only a few sketches of that world implemented, and has gone through a few iterations: 124 156 - first proof of concept was all Electron - so, privileged JS ··· 133 165 134 166 It would be nice, but not required, to have some alignment with the WebExtension spec - blur your eyes and they're in a similar direction. 135 167 136 - ### Peek API 168 + ## Peek API Design 137 169 138 - Initially the prototype was all Electron. But that's not interesting, and doesn't 139 - really tell us anything about constraints of the web itself. 170 + Initially the prototype was all Electron. But that's not interesting, and doesn't really tell us anything about constraints of the web itself. 140 171 141 - So instead I asked this question: What's the minimum capability set that a web app would 142 - need to build the features I need? 172 + So instead I asked this question: What's the minimum capability set that a web app would need to build the features I need? 143 173 144 174 The answer, so far, is giving `peek` apps the following APIs: 145 175 ··· 149 179 150 180 Custom window api might be able to away entirely, by passing window.open features, working on that. 151 181 152 - ### Desktop App 182 + ## Desktop App Notes 153 183 154 184 Proof of concept is Electron. By far the best option today for cross-platform desktop apps which need a web rendering engine. There's really nothing else remotely suited (yet). 155 185 ··· 157 187 - the built-in features are all modal chromeless web pages at this point 158 188 - settings UI uses custom sidebar navigation with dark mode support 159 189 160 - TODO 190 + TODO: 161 191 - Need to look at whether could library-ize some of what Agregore implemented for non-HTTP protocol support. 162 192 - Min browser might be interesting as a forkable base to work from and contribute to, if they're open to it. At least, should look more at the architecture. 163 193 164 - ## Contribution 165 - 166 - - in proto stage 167 - - all dragons, no promises 168 - 169 - ## Development 170 - 171 - ``` 172 - yarn install 173 - yarn debug 174 - ``` 175 - 176 - ### Mobile 194 + ## Mobile Vision 177 195 178 196 - Quick access to Script output and manual runs, as widgets (or output from cloud runners?) 179 197 - Peeks still totes useful here - on mobile is more like "quick dial" features 198 + - some of the features don't make sense as-is on mobile 199 + - but maybe quick access on mobile to slides/peeks would be nice 200 + - and seeing output of content scripts, or ability to re-run locally on demand 201 + - needs some sync facility (inevitable anyway) 180 202 181 - ### Cloud 203 + ## Cloud Vision 182 204 183 205 - Going full crypto payments for distributed compute on this one. 184 206 185 - ## Papercut / use-case log 207 + --- 186 208 187 - Core high level actions 209 + ## Use Cases & Papercuts 210 + 211 + ### Core High Level Actions 188 212 - open a web page on top/bottom/left/right 189 213 - keep web pages persistent in the background 190 214 - quickly open a web page modally, and close it 191 215 192 - Misc specific 216 + ### Specific Use Cases 217 + 193 218 - open bandcamp in a window, move over to 2nd display, accidently close it while moving around between other windows 194 219 - recent books or recipes from newsletters i subscribe to (but probably didn't read) 195 220 - extract a table from a page periodically, send it somewhere as csv or whatever (chained actions) ··· 199 224 - save a tweet, with URL / image / relevant text, but not whole page webrecorder style 200 225 - "watch local event listings, rate against my music listening patterns and send me shows i might be interested in going to" 201 226 202 - Content scripts 227 + ### Content Scripts 203 228 - extract+log shazams 204 229 - extract+log spotify playlist 205 230 206 - Calculators (variant of script + cmd?) 231 + ### Calculators (variant of script + cmd?) 207 232 - page -> table 208 233 - page -> summary 209 234 - page -> microsummaries 210 235 - page -> dates 211 236 - page -> events 212 237 213 - Workflow deconstructing a "why" task flavour of bookmarking 238 + ### Workflow: Deconstructing a "why" Task Flavor of Bookmarking 214 239 - save https://www.criterionchannel.com/hong-kong-in-new-york 215 240 - extract the movies 216 241 - get reference metadata for each (?!) 217 242 - add to "to watch list", with pointer back to source url 218 243 219 - ## Groups 244 + --- 245 + 246 + ## Groups Design 220 247 221 248 - panorama/tabcandy-ish 222 249 - all browser history ··· 224 251 - autoclustering on topic/date 225 252 - escape from a new page enters default group? 226 253 227 - Groups + Cmds 254 + ### Groups + Cmds 228 255 - top/bottom inputs for filtering/grouping/etc 229 256 - implemented is a cmd input? 230 257 - cmds for opening/searching/finding/viewing/filtering/piping 231 258 - cmds for moving pages into groups 232 259 - groups -> {x} (eg export/pipe) could depend on the chaining/piping bit (see below) 233 260 234 - Architecture 261 + ### Architecture 235 262 - internally is tags? 236 263 - static vs dynamic groups tho? 237 264 238 - Publishing 265 + ### Publishing 239 266 - publishing groups as internal/public feeds? 240 267 - to pinboard? 241 268 242 - ## History view/search 269 + --- 270 + 271 + ## History View/Search 243 272 244 273 A lot of groups work depends on history being in place, and being accessable and annotate-able. 245 274 246 275 ideally use chromium history 247 276 248 - storage+access 277 + ### Storage+Access 249 278 - check out Agregore history viewing approach 250 279 - check out state of electron+webext 251 280 - other way of accessing underlying chromium history? 252 281 253 - features 282 + ### Features 254 283 - awesomebar algo scoring 255 284 - adaptive matching 256 285 257 - ## Chaining / piping 286 + --- 287 + 288 + ## Chaining / Piping 258 289 259 290 investigate: vague thought re chaining: 260 291 - dynamic interstitial representations ··· 263 294 - or a table of data 264 295 - previews of cmds? 265 296 266 - interfaces 297 + ### Interfaces 267 298 - horizontal vs vertical chains 268 299 - back/forward navigation? 269 300 - each step is a cmd+preview? 270 301 - dynamic cmd+previews? 271 302 272 - import/export/undo/redo 303 + ### Import/Export/Undo/Redo 273 304 - record/replay? 274 305 - save a chain as a compound action (cmd)? 275 306 276 - architecture 307 + ### Architecture 277 308 - look at web actions/intents/applets 278 309 - xml pipeline language 279 310 280 - ## Mobile 311 + --- 281 312 282 - - some of the features don't make sense as-is on mobile 283 - - but maybe quick access on mobile to slides/peeks would be nice 284 - - and seeing output of content scripts, or ability to re-run locally on demand 285 - - needs some sync facility (inevitable anyway) 313 + ## Feature Use Cases 286 314 287 - ## Use-cases 288 - 289 - Peeks 315 + ### Peeks 290 316 - translate 291 317 - calendar 292 318 - ai chat ··· 294 320 - everytimezone 295 321 - tldraw 296 322 297 - Slides 323 + ### Slides 298 324 - music: Soundcloud, Hypem 299 325 - stock prices 300 326 - notepad 301 327 - todo list 302 328 303 - Scripts 329 + ### Scripts 304 330 - weather change, eg upcoming weather 305 331 - crypto prices 306 332 307 - Cmd - web 333 + ### Cmd - Web 308 334 - open url 309 335 - web search 310 336 - image search 311 337 - conversions? 312 338 - ddg !actions 313 339 314 - Cmd - system 340 + ### Cmd - System 315 341 - search browser history 316 342 - set peeks/slides 317 343 - open settings 318 344 - restart app 319 345 - llm prompts 320 346 321 - Future 347 + ### Future 322 348 - address something to switch between 323 349 - pipe from/to? 324 350 325 - Publishing high level 351 + --- 352 + 353 + ## Publishing 354 + 355 + ### High Level 326 356 - author web content 327 357 - pull in bits from the web 328 358 - share preview for feedback 329 359 - publish (or at least get output) 330 360 331 - Publishing examples 332 - - writing an event recap 333 - 334 - Publishing: event recap post 361 + ### Example: Event Recap Post 335 362 - make a new markdown doc 336 363 - sections titled for each video title 337 364 - each video's embed code in each section ··· 340 367 - share preview link 341 368 - publish (somewhere?) 342 369 343 - Music 370 + ### Music 344 371 - commands 345 372 - views 346 373 - last.fm of my own, to POSSE out 347 374 348 - ## Unfiled 375 + --- 376 + 377 + ## Unfiled Ideas 349 378 350 - markdown hot reload previewer w/ toc 379 + ### Markdown Hot Reload Previewer w/ TOC 351 380 - markdown support, with sidebar nav 352 381 - reader mode 353 382 - hot reload for file:// (other?) ··· 355 384 - once md and side-by-side, add side-by-side so the md is the nav, content is the preview 356 385 - what's the cmd chain for this? 357 386 358 - content types + chaining 387 + ### Content Types + Chaining 359 388 - cmd: view as… table, feed, markdown, data points, named entities 360 389 - chain: static archive, publish, save, share (os), mailto 361 390 - cmd params, eg {url}, which can themselves autocomplete (eg history) 362 391 363 - multiprotocol 392 + ### Multiprotocol 364 393 - at 365 394 - ipfs/ipns 366 395 - pragmatic addressing+rendering for data (r/d/masl + mime handlers) 367 396 368 - broader patterns (chatting w/ luke) 397 + ### Broader Patterns (chatting w/ luke) 369 398 - why do we have to copy/paste? 370 399 - devtools and ide are divorced 371 400 372 - chainframe/framechain 401 + ### Chainframe/Framechain 373 402 - (web intents/applets/actions) + (webxdc/miniapps/tiles/farcasterframes) 374 403 375 - Small examples of agency 376 - 404 + ### Small Examples of Agency 377 405 - users can move, resize, change things to their requirements 378 406 - eg, browsers restrict min-height of a window, but i should be able make as short as i like 379 407 380 - ## History 408 + --- 409 + 410 + ## Project History 381 411 382 412 In working on Firefox and related things at Mozilla from 2006 - 2019, there were a few specific initiatives which best aligned with my needs as a user on the web: 383 413 384 - - The Awesomebar: infinite history + personalized local search index 385 - - Ubiquity: Natural language commands + chaining 386 - - Jetpack: The Mozilla Labs version - web-platfrom-centric extensibility 387 - - Panorama: née TabCandy, web pages as groups instead of tabs in windows 414 + - **The Awesomebar**: infinite history + personalized local search index 415 + - **Ubiquity**: Natural language commands + chaining 416 + - **Jetpack**: The Mozilla Labs version - web-platfrom-centric extensibility 417 + - **Panorama**: née TabCandy, web pages as groups instead of tabs in windows 388 418 389 419 A few others which were in the right direction but didn't achieve their optimal form: 390 420 ··· 394 424 395 425 The first version of the Peek application has some bits of each of these, and the original Peek browser extension. 396 426 397 - ### Peek browser extension 427 + ### Peek Browser Extension 398 428 399 429 Peek was a browser extension that let you quickly peek at your favorite web pages without breaking your flow - loading pages mapped to keyboard shortcuts into a modal window with no controls, closable via the `Escape` key. 400 430 401 431 However, as browser extension APIs became increasingly limited, it was not possible to create a decent user experience and I abandoned it. You can access the extension in this repo [in the extension directory](/autonome/peek/extension/). 402 432 403 433 The only way to create the ideal user experience for a web user agent that *Does What I Want* is to make it a browser-ish application, and that's what Peek is now. 404 - 405 - 406 -
+1
TODO.md
··· 11 11 Be able to use the app on mobile and desktop with the safety of knowing there's also at least one remote copy. 12 12 13 13 Today 14 + - [ ][desktop] test packaged builds: create command to build/run packaged in-place with temp profile, investigate why out/mac-arm64/Peek.app is 5GB 14 15 - [ ] update main README 15 16 - [ ][desktop] update release build and drive it 16 17
+10 -2
backend/server/README.md
··· 90 90 91 91 Configured for Railway (`railway.json`) using Nixpacks builder with automatic restart on failure. 92 92 93 - > **For detailed Railway deployment guide** including step-by-step workflow, user/API key management, production testing, and troubleshooting, see `CLAUDE.md` in the project root. 93 + > **For detailed Railway deployment guide** including step-by-step workflow, user/API key management, production testing, and troubleshooting, see `AGENTS.md` in the project root. 94 94 95 95 **Quick setup:** 96 96 1. Connect your Railway project to this subdirectory (`backend/server/`) 97 97 2. Attach a volume and set `DATA_DIR` to the mount path for persistent storage 98 - 3. Create users and their API keys (see CLAUDE.md for commands) 98 + 3. Create users and their API keys (see AGENTS.md for commands) 99 + 100 + ## Testing 101 + 102 + ```bash 103 + npm test # Run unit tests 104 + npm run test:api:local # Test against local server (needs PEEK_LOCAL_KEY env var) 105 + npm run test:api:prod # Test against production (needs PEEK_PROD_KEY, PEEK_PROD_URL) 106 + ``` 99 107 100 108 ## Environment Variables 101 109
+1 -388
backend/tauri-mobile/README.md
··· 1 1 # Peek Mobile 2 2 3 - A Tauri-based iOS app for saving and organizing URLs, notes, and tag-sets using frecency (frequency + recency) scoring. 4 - 5 - ## Overview 6 - 7 - Peek is a mobile bookmarking and note-taking app that allows you to: 8 - - Save **Pages** (URLs) directly from the iOS share sheet 9 - - Save **Notes** (text with inline #hashtags) from the share sheet or main app 10 - - Create **Tag-sets** (collections of tags) for quick categorization 11 - - Tag items with multiple tags using frecency-scored suggestions 12 - - Automatically merge tags when saving duplicate items 13 - - Browse saved items in tabbed interface (Pages | Notes | Tags) 14 - - Use domain-affinity tag boost for smarter suggestions 15 - - Sync all item types to an external webhook endpoint 16 - - Edit and delete any saved item 17 - 18 - ## Tech Stack 19 - 20 - - **Frontend**: React + TypeScript + Vite 21 - - **Backend**: Rust with Tauri v2 22 - - **Platform**: iOS (simulator and device) 23 - - **Storage**: SQLite database in iOS App Groups container (shared between app and extension) 24 - - **Database Access**: 25 - - Main app: Rust with `rusqlite` crate via Objective-C FFI bridge 26 - - Share Extension: Swift with GRDB.swift library 27 - - **Native Bridge**: Objective-C bridge for accessing App Group container path from Rust 28 - 29 - ## Architecture 30 - 31 - ### Item Types 32 - 33 - Peek supports three item types, accessible via tab navigation: 34 - 35 - | Type | Description | Source | 36 - |------|-------------|--------| 37 - | **Pages** | URLs/bookmarks | Share Extension (Safari, etc.) | 38 - | **Notes** | Text with inline #hashtags | Share Extension (text) or main app | 39 - | **Tag-sets** | Tag collections only | Main app only | 40 - 41 - **Notes** support two ways to add tags: 42 - - Inline hashtags in the text (e.g., `#idea #todo`) are auto-parsed 43 - - Tag buttons below the textarea for adding additional tags 44 - 45 - **Tag-sets** are useful for creating reusable tag combinations. 46 - 47 - ### iOS Share Extension 48 - 49 - The app uses an iOS Share Extension with a full UI that allows: 50 - - Immediate tagging without opening the main app 51 - - Tag selection from frecency-sorted list 52 - - Creating new tags on the fly 53 - - Automatic detection and merging of duplicate items 54 - - Status display showing existing tags for already-saved items 55 - - Support for both URLs and plain text content 56 - 57 - ### Frecency Algorithm 58 - 59 - Tags are scored using frecency (frequency + recency): 60 - 61 - ``` 62 - frecency_score = frequency × 10 × decay_factor 63 - decay_factor = 1 / (1 + days_since_use / 7) 64 - ``` 65 - 66 - This ensures frequently used tags appear first, but decay over time if not used. 67 - 68 - ### Domain-Affinity Tag Boost 69 - 70 - When displaying unused tags in the save/edit interfaces, tags that have been used on URLs from the same domain get a **2x frecency score multiplier**. This makes relevant tags appear higher in suggestions. 71 - 72 - **Example**: When saving a URL from `github.com/foo/bar`, any tags previously used on other GitHub URLs (e.g., `github.com/bar/baz`) will appear higher in the tag suggestions. 73 - 74 - **Implementation:** 75 - - Domain extraction removes `www.` prefix for matching 76 - - Applied in both Share Extension (Swift) and main app edit mode (Rust/React) 77 - 78 - ### URL Deduplication 79 - 80 - When saving a URL that already exists: 81 - 1. The share extension detects the duplicate 82 - 2. Shows status: "Already saved with tags: existing, tags" 83 - 3. Pre-selects existing tags 84 - 4. Button changes to "Update Tags" 85 - 5. On save, merges new tags with existing tags (set union) 86 - 6. Preserves original ID and timestamp 87 - 88 - ### Webhook Sync 89 - 90 - The app supports syncing all item types to an external webhook endpoint: 91 - 92 - - **Configure webhook URL and API key** in the Settings screen 93 - - **Manual sync** via "Sync All" button 94 - - **Auto-sync on save** from both main app and share extension 95 - - **Daily auto-sync** checks `last_sync` timestamp, syncs if >24 hours 96 - - **Offline detection** skips webhook POST if device is offline (uses `NWPathMonitor`) 97 - 98 - **Payload format:** 99 - ```json 100 - { 101 - "urls": [ 102 - { "id": "uuid", "url": "https://...", "tags": ["tag1"], "saved_at": "..." } 103 - ], 104 - "texts": [ 105 - { "id": "uuid", "content": "Note with #hashtag", "tags": ["hashtag"], "saved_at": "..." } 106 - ], 107 - "tagsets": [ 108 - { "id": "uuid", "tags": ["tag1", "tag2"], "saved_at": "..." } 109 - ] 110 - } 111 - ``` 112 - 113 - ### Data Storage 114 - 115 - Data is stored in a SQLite database (`peek.db`) within the iOS App Groups container (`group.com.dietrich.peek-mobile`). This enables sharing between the main app and share extension with proper concurrent access via WAL mode. 116 - 117 - **Database Location:** 118 - ``` 119 - ~/Library/Developer/CoreSimulator/Devices/<DEVICE_ID>/data/Containers/Shared/AppGroup/<GROUP_UUID>/peek.db 120 - ``` 121 - 122 - **Database Schema:** 123 - 124 - ```sql 125 - -- Unified items table (pages, texts, tagsets) 126 - CREATE TABLE items ( 127 - id TEXT PRIMARY KEY, -- UUID 128 - type TEXT NOT NULL DEFAULT 'page', -- 'page', 'text', or 'tagset' 129 - url TEXT, -- URL (required for 'page' type) 130 - content TEXT, -- Text content (required for 'text' type) 131 - created_at TEXT NOT NULL, -- ISO8601 timestamp 132 - updated_at TEXT NOT NULL, -- ISO8601 timestamp 133 - deleted_at TEXT -- Soft delete timestamp (NULL = active) 134 - ); 135 - 136 - -- Tags table 137 - CREATE TABLE tags ( 138 - id INTEGER PRIMARY KEY AUTOINCREMENT, 139 - name TEXT NOT NULL UNIQUE, -- Tag name (lowercase) 140 - frequency INTEGER NOT NULL DEFAULT 0, 141 - last_used TEXT NOT NULL, -- ISO8601 timestamp 142 - frecency_score REAL NOT NULL DEFAULT 0.0, 143 - created_at TEXT NOT NULL, 144 - updated_at TEXT NOT NULL 145 - ); 146 - 147 - -- Item-Tag junction table (many-to-many) 148 - CREATE TABLE item_tags ( 149 - item_id TEXT NOT NULL, 150 - tag_id INTEGER NOT NULL, 151 - created_at TEXT NOT NULL, 152 - PRIMARY KEY (item_id, tag_id), 153 - FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE, 154 - FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE 155 - ); 156 - 157 - -- Settings table (key-value store) 158 - CREATE TABLE settings ( 159 - key TEXT PRIMARY KEY, -- e.g., 'webhook_url', 'webhook_api_key', 'last_sync' 160 - value TEXT NOT NULL 161 - ); 162 - ``` 163 - 164 - **Key Features:** 165 - - WAL mode for concurrent access from main app and share extension 166 - - Unified items table supports all three item types 167 - - Automatic migration from legacy `urls`/`url_tags` tables 168 - - Normalized schema with junction table for item-tag relationships 169 - - Indexes on frequently queried columns (type, url, content, frecency_score) 170 - 171 - ## Key Files 172 - 173 - ### Share Extension 174 - - `src-tauri/gen/apple/Peek/ShareViewController.swift` - Full UI share extension 175 - - `ShareViewController-full-ui.swift.example` - Reference implementation 176 - 177 - ### App Group Bridge 178 - - `src-tauri/AppGroupBridge.m` - Objective-C bridge for App Groups access 179 - - Provides C functions for Rust FFI: 180 - - `get_app_group_container_path()` - Returns path to App Group container for SQLite database 181 - - `get_system_is_dark_mode()` - Returns current system appearance (dark/light mode) 182 - 183 - ### Rust Backend 184 - - `src-tauri/src/lib.rs` - Tauri commands and business logic 185 - - **Page commands**: `save_url`, `get_saved_urls`, `update_url`, `delete_url` 186 - - **Text commands**: `save_text`, `get_saved_texts`, `update_text` 187 - - **Tagset commands**: `save_tagset`, `get_saved_tagsets`, `update_tagset` 188 - - **Tag commands**: `get_tags_by_frecency`, `get_tags_by_frecency_for_url` 189 - - **Webhook commands**: `sync_to_webhook`, `auto_sync_if_needed` 190 - 191 - ### Frontend 192 - - `src/App.tsx` - React UI with tabbed navigation (Pages | Notes | Tags) 193 - - `src/App.css` - Mobile-optimized styling with dark mode 194 - 195 - ### Tests 196 - - `tests/integration.test.js` - Integration tests against backend/server 197 - 198 - ### Configuration 199 - - `src-tauri/tauri.conf.json` - Bundle ID: `com.dietrich.peek-mobile` 200 - - `src-tauri/gen/apple/tauri-app_iOS/tauri-app_iOS.entitlements` - App Groups entitlement 201 - - `src-tauri/gen/apple/Peek/Peek.entitlements` - Share extension entitlements 202 - 203 - ## Development 204 - 205 - ### Prerequisites 206 - 207 - - Node.js and npm 208 - - Rust and Cargo 209 - - Xcode (for iOS development) 210 - - Apple Developer certificate 211 - 212 - ### Setup 213 - 214 - ```bash 215 - npm install 216 - ``` 217 - 218 - ### Running 219 - 220 - **iOS Simulator:** 221 - ```bash 222 - npm run tauri ios dev -- "iPhone 17 Pro" 223 - ``` 224 - 225 - **Desktop (for quick UI testing):** 226 - ```bash 227 - npm run tauri dev 228 - ``` 229 - 230 - ### Build Workflow 231 - 232 - Frontend assets (CSS, JS, HTML) are **embedded in the Rust binary** at compile time. This means: 233 - - Changing CSS/JS requires rebuilding Rust with `cargo tauri build` (NOT just `cargo build`) 234 - - Simply rebuilding in Xcode won't pick up frontend changes 235 - - The library file to copy is in the `deps/` subdirectory 236 - 237 - **Debug Build (Simulator):** 238 - ```bash 239 - # 1. Start Vite dev server (for hot reload during development) 240 - npx vite --host 241 - 242 - # 2. Build and run from Xcode with Debug scheme on simulator 243 - # OR use the full embedded build: 244 - npm run build 245 - cd src-tauri 246 - cargo tauri build --target aarch64-apple-ios-sim --debug 247 - cp target/aarch64-apple-ios-sim/debug/deps/libpeek_save_lib.a gen/apple/Externals/arm64/Debug/libapp.a 248 - # Then build in Xcode with Debug scheme, simulator target 249 - ``` 250 - 251 - **Release Build (Device):** 252 - ```bash 253 - # Use the build script: 254 - npm run build:release 255 - 256 - # Or manually: 257 - npm run build 258 - cd src-tauri 259 - cargo tauri build --target aarch64-apple-ios 260 - cp target/aarch64-apple-ios/release/deps/libpeek_save_lib.a gen/apple/Externals/arm64/Release/libapp.a 261 - # Then build in Xcode with Release scheme, device target 262 - ``` 263 - 264 - **Important Notes:** 265 - - Debug uses `Externals/arm64/Debug/libapp.a` and target `aarch64-apple-ios-sim` 266 - - Release uses `Externals/arm64/Release/libapp.a` and target `aarch64-apple-ios` 267 - - Always copy from the `deps/` subfolder (has embedded assets), not the root folder 268 - - Use `cargo tauri build`, NOT `cargo build` (the latter doesn't embed frontend assets) 269 - 270 - The Xcode preBuildScript checks if `libapp.a` exists and skips the Rust build if so. To force a Rust rebuild from Xcode, delete the corresponding `libapp.a` file. 271 - 272 - ### App Icon 273 - 274 - The app uses `Peek.icon` bundle (Xcode 15+ unified icon format): 275 - - Source: `src-tauri/gen/apple/Peek.icon/Assets/Peek clouds src.png` (1232x1232) 276 - - Xcode generates all required icon sizes during build 277 - - Do NOT recreate `Assets.xcassets/AppIcon.appiconset/` - that's Tauri's default icons 278 - 279 - ### Building 280 - 281 - **Build Rust library for iOS:** 282 - ```bash 283 - cd src-tauri 284 - ./build-ios.sh 285 - ``` 286 - 287 - **Build iOS app:** 288 - ```bash 289 - cd src-tauri/gen/apple 290 - xcodebuild -scheme tauri-app_iOS -configuration Debug -sdk iphonesimulator -derivedDataPath build 291 - ``` 292 - 293 - **Install on simulator:** 294 - ```bash 295 - xcrun simctl install <DEVICE_ID> "src-tauri/gen/apple/build/Build/Products/debug-iphonesimulator/Peek.app" 296 - ``` 297 - 298 - ### Bundle Identifiers 299 - 300 - - Main app: `com.dietrich.peek-mobile` 301 - - Share extension: `com.dietrich.peek-mobile.share` (must be prefixed with main app ID) 302 - - App Group: `group.com.dietrich.peek-mobile` 303 - 304 - **Important**: All three must match the `-mobile` suffix for the App Groups sharing to work. 305 - 306 - ### Build Script 307 - 308 - The `build-ios.sh` script: 309 - 1. Builds Rust library for both `aarch64-apple-ios-sim` and `x86_64-apple-ios` 310 - 2. Creates universal library with `lipo` 311 - 3. Copies to `gen/apple/Externals/arm64/debug/libapp.a` 312 - 4. Compiles Objective-C bridge code 313 - 314 - ### Testing 315 - 316 - Integration tests verify the webhook sync with the backend server (`backend/server/`): 317 - 318 - ```bash 319 - # Run tests (starts server with temp data, runs tests, cleans up) 320 - npm test 321 - 322 - # Verbose mode (shows server logs) 323 - npm run test:verbose 324 - ``` 325 - 326 - Tests cover: 327 - - Webhook sync for pages (URLs) 328 - - Texts API (create, read, update, delete) 329 - - Tagsets API (create, read, update, delete) 330 - - Unified `/items` API with type filtering 331 - - Tags frecency tracking 332 - - Update and delete operations 333 - 334 - **Requirements:** Server dependencies installed (`cd ../server && npm install`) 335 - 336 - ### Cleaning Data 337 - 338 - To clear all saved items and tags from simulator: 339 - 340 - ```bash 341 - # Find the SQLite database 342 - find ~/Library/Developer/CoreSimulator/Devices/<DEVICE_ID>/data/Containers/Shared/AppGroup -name "peek.db" 343 - 344 - # Delete it (also removes WAL files) 345 - rm "<path_to_peek.db>"* 346 - 347 - # Or to just clear data without deleting the database: 348 - sqlite3 "<path_to_peek.db>" "DELETE FROM item_tags; DELETE FROM items; DELETE FROM tags;" 349 - ``` 350 - 351 - ### Share Extension in Xcode 352 - 353 - The share extension must be configured in Xcode: 354 - 1. Target: "Peek" (Share Extension) 355 - 2. Bundle ID: `com.dietrich.peek-mobile.share` 356 - 3. Principal Class: `ShareViewController` 357 - 4. Copy `ShareViewController-full-ui.swift.example` to `src-tauri/gen/apple/Peek/ShareViewController.swift` 358 - 5. Ensure entitlements include App Groups 359 - 360 - ## Troubleshooting 361 - 362 - ### Stale Build Cache 363 - 364 - If changes to `AppGroupBridge.m` aren't reflected: 365 - 366 - ```bash 367 - cd src-tauri 368 - cargo clean 369 - ./build-ios.sh 370 - # Rebuild in Xcode 371 - ``` 372 - 373 - ### Share Extension Not Appearing 374 - 375 - Check: 376 - 1. Bundle ID has correct prefix: `com.dietrich.peek-mobile.share` 377 - 2. Share extension Info.plist has correct NSExtension configuration 378 - 3. App Groups entitlements match between app and extension 379 - 380 - ### No Saved URLs Showing 381 - 382 - Verify: 383 - 1. App Groups identifier matches in all three places 384 - 2. Both Rust (rusqlite) and Swift (GRDB) are accessing the same database path 385 - 3. Database is being created in the App Group container, not the app sandbox 386 - 4. Check database contents: `sqlite3 <path_to_peek.db> "SELECT * FROM urls;"` 387 - 388 - ## License 389 - 390 - See LICENSE file. 3 + See [docs/mobile.md](../../docs/mobile.md) for full mobile development documentation.
-117
docs/MOBILE.md
··· 1 - # Peek Mobile (Tauri iOS/Android) 2 - 3 - This document covers mobile development for Peek using the `peek-save` app in `backend/tauri-mobile/`. 4 - 5 - ## Architecture 6 - 7 - Mobile uses a **separate Tauri project** rather than sharing code with the desktop Tauri backend. This approach was chosen because: 8 - 9 - 1. **iOS requires extensive native configuration** - Share Extension, App Groups, entitlements, icons, provisioning 10 - 2. **Different UI paradigms** - Mobile has a focused "save URL with tags" workflow vs desktop's multi-window browser 11 - 3. **Build complexity** - iOS builds require Xcode project customization that gets overwritten by `xcodegen` 12 - 13 - ### Project Structure 14 - 15 - ``` 16 - backend/ 17 - ├── electron/ # Desktop Electron backend 18 - ├── tauri/ # Desktop Tauri backend (macOS/Windows/Linux) 19 - └── tauri-mobile/ # Mobile Tauri app (iOS/Android) 20 - ├── src/ # React + TypeScript frontend 21 - ├── src-tauri/ # Rust backend 22 - │ ├── src/ # Rust code 23 - │ └── gen/ 24 - │ └── apple/ # iOS Xcode project 25 - │ ├── peek-save.xcodeproj 26 - │ ├── Peek/ # Share Extension 27 - │ └── peek-save_iOS/ # Main app 28 - └── CLAUDE.md # Full mobile documentation 29 - ``` 30 - 31 - ## Quick Start 32 - 33 - ### Commands 34 - 35 - ```bash 36 - # Build frontend 37 - yarn mobile:build 38 - 39 - # Build iOS debug (simulator) 40 - yarn mobile:ios:build 41 - 42 - # Build iOS release (device) 43 - yarn mobile:ios:build:release 44 - 45 - # Open Xcode project (then build with Xcode GUI) 46 - yarn mobile:ios:xcode 47 - 48 - # Android 49 - yarn mobile:android:init 50 - yarn mobile:android:dev 51 - yarn mobile:android:build 52 - ``` 53 - 54 - ### iOS Build Workflow 55 - 56 - 1. Build the Rust library: 57 - ```bash 58 - yarn mobile:ios:build 59 - ``` 60 - 61 - 2. Open Xcode: 62 - ```bash 63 - yarn mobile:ios:xcode 64 - ``` 65 - 66 - 3. In Xcode GUI: Product → Build (Cmd+B) 67 - 68 - 4. Run on simulator or device from Xcode 69 - 70 - ## iOS Features 71 - 72 - ### Share Extension 73 - 74 - The iOS app includes a Share Extension with full native UI for capturing URLs: 75 - 76 - - Native Swift UIKit interface 77 - - Frecency-sorted tag suggestions with domain-affinity boost 78 - - Create new tags on the fly 79 - - Detects duplicate URLs and shows existing tags 80 - - Merges tags when saving duplicates 81 - 82 - ### Bundle Identifiers 83 - 84 - - Main app: `com.dietrich.peek-mobile` 85 - - Share extension: `com.dietrich.peek-mobile.share` 86 - - App Group: `group.com.dietrich.peek-mobile` 87 - 88 - ### Data Storage 89 - 90 - SQLite database in iOS App Groups container (`peek.db`), enabling sharing between main app and share extension. Uses WAL mode for concurrent access. 91 - 92 - ## Critical Guidelines 93 - 94 - **CRITICAL - Build with Xcode GUI:** 95 - - NEVER run `xcodebuild` commands from terminal for final builds 96 - - Use Xcode GUI (Product → Build, Product → Run) for reliable builds 97 - - CLI xcodebuild often fails silently with code signing issues 98 - 99 - **CRITICAL - Do NOT run `xcodegen generate`:** 100 - - The Xcode project has custom settings (entitlements, Info.plist, share extension) 101 - - Running xcodegen overwrites these configurations 102 - - Edit files directly and rebuild with Xcode GUI 103 - 104 - **CRITICAL - Frontend Embedding:** 105 - - Use `cargo tauri build`, NOT `cargo build` 106 - - `cargo build` doesn't embed frontend assets - app shows localhost error 107 - - Always copy from `deps/` folder, not root target folder 108 - 109 - ## Full Documentation 110 - 111 - For complete mobile development documentation, see: 112 - - `backend/tauri-mobile/CLAUDE.md` - Comprehensive guide including: 113 - - Build workflow details 114 - - Share Extension implementation 115 - - Database schema 116 - - Webhook sync 117 - - Troubleshooting
+390
docs/mobile.md
··· 1 + # Peek Mobile 2 + 3 + A Tauri-based iOS app for saving and organizing URLs, notes, and tag-sets using frecency (frequency + recency) scoring. 4 + 5 + ## Overview 6 + 7 + Peek is a mobile bookmarking and note-taking app that allows you to: 8 + - Save **Pages** (URLs) directly from the iOS share sheet 9 + - Save **Notes** (text with inline #hashtags) from the share sheet or main app 10 + - Create **Tag-sets** (collections of tags) for quick categorization 11 + - Tag items with multiple tags using frecency-scored suggestions 12 + - Automatically merge tags when saving duplicate items 13 + - Browse saved items in tabbed interface (Pages | Notes | Tags) 14 + - Use domain-affinity tag boost for smarter suggestions 15 + - Sync all item types to an external webhook endpoint 16 + - Edit and delete any saved item 17 + 18 + ## Tech Stack 19 + 20 + - **Frontend**: React + TypeScript + Vite 21 + - **Backend**: Rust with Tauri v2 22 + - **Platform**: iOS (simulator and device) 23 + - **Storage**: SQLite database in iOS App Groups container (shared between app and extension) 24 + - **Database Access**: 25 + - Main app: Rust with `rusqlite` crate via Objective-C FFI bridge 26 + - Share Extension: Swift with GRDB.swift library 27 + - **Native Bridge**: Objective-C bridge for accessing App Group container path from Rust 28 + 29 + ## Architecture 30 + 31 + ### Item Types 32 + 33 + Peek supports three item types, accessible via tab navigation: 34 + 35 + | Type | Description | Source | 36 + |------|-------------|--------| 37 + | **Pages** | URLs/bookmarks | Share Extension (Safari, etc.) | 38 + | **Notes** | Text with inline #hashtags | Share Extension (text) or main app | 39 + | **Tag-sets** | Tag collections only | Main app only | 40 + 41 + **Notes** support two ways to add tags: 42 + - Inline hashtags in the text (e.g., `#idea #todo`) are auto-parsed 43 + - Tag buttons below the textarea for adding additional tags 44 + 45 + **Tag-sets** are useful for creating reusable tag combinations. 46 + 47 + ### iOS Share Extension 48 + 49 + The app uses an iOS Share Extension with a full UI that allows: 50 + - Immediate tagging without opening the main app 51 + - Tag selection from frecency-sorted list 52 + - Creating new tags on the fly 53 + - Automatic detection and merging of duplicate items 54 + - Status display showing existing tags for already-saved items 55 + - Support for both URLs and plain text content 56 + 57 + ### Frecency Algorithm 58 + 59 + Tags are scored using frecency (frequency + recency): 60 + 61 + ``` 62 + frecency_score = frequency × 10 × decay_factor 63 + decay_factor = 1 / (1 + days_since_use / 7) 64 + ``` 65 + 66 + This ensures frequently used tags appear first, but decay over time if not used. 67 + 68 + ### Domain-Affinity Tag Boost 69 + 70 + When displaying unused tags in the save/edit interfaces, tags that have been used on URLs from the same domain get a **2x frecency score multiplier**. This makes relevant tags appear higher in suggestions. 71 + 72 + **Example**: When saving a URL from `github.com/foo/bar`, any tags previously used on other GitHub URLs (e.g., `github.com/bar/baz`) will appear higher in the tag suggestions. 73 + 74 + **Implementation:** 75 + - Domain extraction removes `www.` prefix for matching 76 + - Applied in both Share Extension (Swift) and main app edit mode (Rust/React) 77 + 78 + ### URL Deduplication 79 + 80 + When saving a URL that already exists: 81 + 1. The share extension detects the duplicate 82 + 2. Shows status: "Already saved with tags: existing, tags" 83 + 3. Pre-selects existing tags 84 + 4. Button changes to "Update Tags" 85 + 5. On save, merges new tags with existing tags (set union) 86 + 6. Preserves original ID and timestamp 87 + 88 + ### Webhook Sync 89 + 90 + The app supports syncing all item types to an external webhook endpoint: 91 + 92 + - **Configure webhook URL and API key** in the Settings screen 93 + - **Manual sync** via "Sync All" button 94 + - **Auto-sync on save** from both main app and share extension 95 + - **Daily auto-sync** checks `last_sync` timestamp, syncs if >24 hours 96 + - **Offline detection** skips webhook POST if device is offline (uses `NWPathMonitor`) 97 + 98 + **Payload format:** 99 + ```json 100 + { 101 + "urls": [ 102 + { "id": "uuid", "url": "https://...", "tags": ["tag1"], "saved_at": "..." } 103 + ], 104 + "texts": [ 105 + { "id": "uuid", "content": "Note with #hashtag", "tags": ["hashtag"], "saved_at": "..." } 106 + ], 107 + "tagsets": [ 108 + { "id": "uuid", "tags": ["tag1", "tag2"], "saved_at": "..." } 109 + ] 110 + } 111 + ``` 112 + 113 + ### Data Storage 114 + 115 + Data is stored in a SQLite database (`peek.db`) within the iOS App Groups container (`group.com.dietrich.peek-mobile`). This enables sharing between the main app and share extension with proper concurrent access via WAL mode. 116 + 117 + **Database Location:** 118 + ``` 119 + ~/Library/Developer/CoreSimulator/Devices/<DEVICE_ID>/data/Containers/Shared/AppGroup/<GROUP_UUID>/peek.db 120 + ``` 121 + 122 + **Database Schema:** 123 + 124 + ```sql 125 + -- Unified items table (pages, texts, tagsets) 126 + CREATE TABLE items ( 127 + id TEXT PRIMARY KEY, -- UUID 128 + type TEXT NOT NULL DEFAULT 'page', -- 'page', 'text', or 'tagset' 129 + url TEXT, -- URL (required for 'page' type) 130 + content TEXT, -- Text content (required for 'text' type) 131 + created_at TEXT NOT NULL, -- ISO8601 timestamp 132 + updated_at TEXT NOT NULL, -- ISO8601 timestamp 133 + deleted_at TEXT -- Soft delete timestamp (NULL = active) 134 + ); 135 + 136 + -- Tags table 137 + CREATE TABLE tags ( 138 + id INTEGER PRIMARY KEY AUTOINCREMENT, 139 + name TEXT NOT NULL UNIQUE, -- Tag name (lowercase) 140 + frequency INTEGER NOT NULL DEFAULT 0, 141 + last_used TEXT NOT NULL, -- ISO8601 timestamp 142 + frecency_score REAL NOT NULL DEFAULT 0.0, 143 + created_at TEXT NOT NULL, 144 + updated_at TEXT NOT NULL 145 + ); 146 + 147 + -- Item-Tag junction table (many-to-many) 148 + CREATE TABLE item_tags ( 149 + item_id TEXT NOT NULL, 150 + tag_id INTEGER NOT NULL, 151 + created_at TEXT NOT NULL, 152 + PRIMARY KEY (item_id, tag_id), 153 + FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE, 154 + FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE 155 + ); 156 + 157 + -- Settings table (key-value store) 158 + CREATE TABLE settings ( 159 + key TEXT PRIMARY KEY, -- e.g., 'webhook_url', 'webhook_api_key', 'last_sync' 160 + value TEXT NOT NULL 161 + ); 162 + ``` 163 + 164 + **Key Features:** 165 + - WAL mode for concurrent access from main app and share extension 166 + - Unified items table supports all three item types 167 + - Automatic migration from legacy `urls`/`url_tags` tables 168 + - Normalized schema with junction table for item-tag relationships 169 + - Indexes on frequently queried columns (type, url, content, frecency_score) 170 + 171 + ## Key Files 172 + 173 + ### Share Extension 174 + - `src-tauri/gen/apple/Peek/ShareViewController.swift` - Full UI share extension 175 + - `ShareViewController-full-ui.swift.example` - Reference implementation 176 + 177 + ### App Group Bridge 178 + - `src-tauri/AppGroupBridge.m` - Objective-C bridge for App Groups access 179 + - Provides C functions for Rust FFI: 180 + - `get_app_group_container_path()` - Returns path to App Group container for SQLite database 181 + - `get_system_is_dark_mode()` - Returns current system appearance (dark/light mode) 182 + 183 + ### Rust Backend 184 + - `src-tauri/src/lib.rs` - Tauri commands and business logic 185 + - **Page commands**: `save_url`, `get_saved_urls`, `update_url`, `delete_url` 186 + - **Text commands**: `save_text`, `get_saved_texts`, `update_text` 187 + - **Tagset commands**: `save_tagset`, `get_saved_tagsets`, `update_tagset` 188 + - **Tag commands**: `get_tags_by_frecency`, `get_tags_by_frecency_for_url` 189 + - **Webhook commands**: `sync_to_webhook`, `auto_sync_if_needed` 190 + 191 + ### Frontend 192 + - `src/App.tsx` - React UI with tabbed navigation (Pages | Notes | Tags) 193 + - `src/App.css` - Mobile-optimized styling with dark mode 194 + 195 + ### Tests 196 + - `tests/integration.test.js` - Integration tests against backend/server 197 + 198 + ### Configuration 199 + - `src-tauri/tauri.conf.json` - Bundle ID: `com.dietrich.peek-mobile` 200 + - `src-tauri/gen/apple/tauri-app_iOS/tauri-app_iOS.entitlements` - App Groups entitlement 201 + - `src-tauri/gen/apple/Peek/Peek.entitlements` - Share extension entitlements 202 + 203 + ## Development 204 + 205 + ### Prerequisites 206 + 207 + - Node.js and npm 208 + - Rust and Cargo 209 + - Xcode (for iOS development) 210 + - Apple Developer certificate 211 + 212 + ### Setup 213 + 214 + ```bash 215 + npm install 216 + ``` 217 + 218 + ### Running 219 + 220 + **iOS Simulator:** 221 + ```bash 222 + npm run tauri ios dev -- "iPhone 17 Pro" 223 + ``` 224 + 225 + **Desktop (for quick UI testing):** 226 + ```bash 227 + npm run tauri dev 228 + ``` 229 + 230 + ### Build Workflow 231 + 232 + Frontend assets (CSS, JS, HTML) are **embedded in the Rust binary** at compile time. This means: 233 + - Changing CSS/JS requires rebuilding Rust with `cargo tauri build` (NOT just `cargo build`) 234 + - Simply rebuilding in Xcode won't pick up frontend changes 235 + - The library file to copy is in the `deps/` subdirectory 236 + 237 + **Debug Build (Simulator):** 238 + ```bash 239 + # 1. Start Vite dev server (for hot reload during development) 240 + npx vite --host 241 + 242 + # 2. Build and run from Xcode with Debug scheme on simulator 243 + # OR use the full embedded build: 244 + npm run build 245 + cd src-tauri 246 + cargo tauri build --target aarch64-apple-ios-sim --debug 247 + cp target/aarch64-apple-ios-sim/debug/deps/libpeek_save_lib.a gen/apple/Externals/arm64/Debug/libapp.a 248 + # Then build in Xcode with Debug scheme, simulator target 249 + ``` 250 + 251 + **Release Build (Device):** 252 + ```bash 253 + # Use the build script: 254 + npm run build:release 255 + 256 + # Or manually: 257 + npm run build 258 + cd src-tauri 259 + cargo tauri build --target aarch64-apple-ios 260 + cp target/aarch64-apple-ios/release/deps/libpeek_save_lib.a gen/apple/Externals/arm64/Release/libapp.a 261 + # Then build in Xcode with Release scheme, device target 262 + ``` 263 + 264 + **Important Notes:** 265 + - Debug uses `Externals/arm64/Debug/libapp.a` and target `aarch64-apple-ios-sim` 266 + - Release uses `Externals/arm64/Release/libapp.a` and target `aarch64-apple-ios` 267 + - Always copy from the `deps/` subfolder (has embedded assets), not the root folder 268 + - Use `cargo tauri build`, NOT `cargo build` (the latter doesn't embed frontend assets) 269 + 270 + The Xcode preBuildScript checks if `libapp.a` exists and skips the Rust build if so. To force a Rust rebuild from Xcode, delete the corresponding `libapp.a` file. 271 + 272 + ### App Icon 273 + 274 + The app uses `Peek.icon` bundle (Xcode 15+ unified icon format): 275 + - Source: `src-tauri/gen/apple/Peek.icon/Assets/Peek clouds src.png` (1232x1232) 276 + - Xcode generates all required icon sizes during build 277 + - Do NOT recreate `Assets.xcassets/AppIcon.appiconset/` - that's Tauri's default icons 278 + 279 + ### Building 280 + 281 + **Build Rust library for iOS:** 282 + ```bash 283 + cd src-tauri 284 + ./build-ios.sh 285 + ``` 286 + 287 + **Build iOS app:** 288 + ```bash 289 + cd src-tauri/gen/apple 290 + xcodebuild -scheme tauri-app_iOS -configuration Debug -sdk iphonesimulator -derivedDataPath build 291 + ``` 292 + 293 + **Install on simulator:** 294 + ```bash 295 + xcrun simctl install <DEVICE_ID> "src-tauri/gen/apple/build/Build/Products/debug-iphonesimulator/Peek.app" 296 + ``` 297 + 298 + ### Bundle Identifiers 299 + 300 + - Main app: `com.dietrich.peek-mobile` 301 + - Share extension: `com.dietrich.peek-mobile.share` (must be prefixed with main app ID) 302 + - App Group: `group.com.dietrich.peek-mobile` 303 + 304 + **Important**: All three must match the `-mobile` suffix for the App Groups sharing to work. 305 + 306 + ### Build Script 307 + 308 + The `build-ios.sh` script: 309 + 1. Builds Rust library for both `aarch64-apple-ios-sim` and `x86_64-apple-ios` 310 + 2. Creates universal library with `lipo` 311 + 3. Copies to `gen/apple/Externals/arm64/debug/libapp.a` 312 + 4. Compiles Objective-C bridge code 313 + 314 + ### Testing 315 + 316 + Integration tests verify the webhook sync with the backend server (`backend/server/`): 317 + 318 + ```bash 319 + # Run tests (starts server with temp data, runs tests, cleans up) 320 + npm test 321 + 322 + # Verbose mode (shows server logs) 323 + npm run test:verbose 324 + ``` 325 + 326 + Tests cover: 327 + - Webhook sync for pages (URLs) 328 + - Texts API (create, read, update, delete) 329 + - Tagsets API (create, read, update, delete) 330 + - Unified `/items` API with type filtering 331 + - Tags frecency tracking 332 + - Update and delete operations 333 + 334 + **Requirements:** Server dependencies installed (`cd ../server && npm install`) 335 + 336 + ### Cleaning Data 337 + 338 + To clear all saved items and tags from simulator: 339 + 340 + ```bash 341 + # Find the SQLite database 342 + find ~/Library/Developer/CoreSimulator/Devices/<DEVICE_ID>/data/Containers/Shared/AppGroup -name "peek.db" 343 + 344 + # Delete it (also removes WAL files) 345 + rm "<path_to_peek.db>"* 346 + 347 + # Or to just clear data without deleting the database: 348 + sqlite3 "<path_to_peek.db>" "DELETE FROM item_tags; DELETE FROM items; DELETE FROM tags;" 349 + ``` 350 + 351 + ### Share Extension in Xcode 352 + 353 + The share extension must be configured in Xcode: 354 + 1. Target: "Peek" (Share Extension) 355 + 2. Bundle ID: `com.dietrich.peek-mobile.share` 356 + 3. Principal Class: `ShareViewController` 357 + 4. Copy `ShareViewController-full-ui.swift.example` to `src-tauri/gen/apple/Peek/ShareViewController.swift` 358 + 5. Ensure entitlements include App Groups 359 + 360 + ## Troubleshooting 361 + 362 + ### Stale Build Cache 363 + 364 + If changes to `AppGroupBridge.m` aren't reflected: 365 + 366 + ```bash 367 + cd src-tauri 368 + cargo clean 369 + ./build-ios.sh 370 + # Rebuild in Xcode 371 + ``` 372 + 373 + ### Share Extension Not Appearing 374 + 375 + Check: 376 + 1. Bundle ID has correct prefix: `com.dietrich.peek-mobile.share` 377 + 2. Share extension Info.plist has correct NSExtension configuration 378 + 3. App Groups entitlements match between app and extension 379 + 380 + ### No Saved URLs Showing 381 + 382 + Verify: 383 + 1. App Groups identifier matches in all three places 384 + 2. Both Rust (rusqlite) and Swift (GRDB) are accessing the same database path 385 + 3. Database is being created in the App Group container, not the app sandbox 386 + 4. Check database contents: `sqlite3 <path_to_peek.db> "SELECT * FROM urls;"` 387 + 388 + ## License 389 + 390 + See LICENSE file.
+234
docs/themes.md
··· 1 + # Peek Themes 2 + 3 + Themes control the visual appearance of Peek, including colors and typography. They use a Base16 color scheme that supports light and dark variants. 4 + 5 + ## Theme Structure 6 + 7 + Each theme lives in its own directory under `themes/`: 8 + 9 + ``` 10 + themes/ 11 + basic/ 12 + manifest.json # Theme metadata 13 + variables.css # CSS custom properties 14 + peek/ 15 + manifest.json 16 + variables.css 17 + fonts/ # Optional custom fonts 18 + ServerMono-Regular.woff2 19 + ``` 20 + 21 + ### manifest.json 22 + 23 + Required fields: 24 + ```json 25 + { 26 + "id": "basic", 27 + "name": "Basic", 28 + "version": "1.0.0" 29 + } 30 + ``` 31 + 32 + Optional fields: 33 + ```json 34 + { 35 + "description": "Clean minimal theme", 36 + "author": "Peek Team", 37 + "colorSchemes": ["light", "dark"] 38 + } 39 + ``` 40 + 41 + ### variables.css 42 + 43 + Defines CSS custom properties using the Base16 color scheme. The file must define: 44 + 45 + 1. **Base colors (base00-base0F)** - 16 colors following Base16 conventions 46 + 2. **Semantic aliases** - Mapped to base colors for easier use 47 + 3. **Typography** - Font families for sans and mono text 48 + 49 + ```css 50 + /* Light mode (default) */ 51 + :root { 52 + /* Base16 Grayscale */ 53 + --base00: #ffffff; /* Default Background */ 54 + --base01: #f5f5f5; /* Lighter Background */ 55 + --base02: #e0e0e0; /* Selection Background */ 56 + --base03: #b0b0b0; /* Comments, Muted */ 57 + --base04: #666666; /* Dark Foreground */ 58 + --base05: #333333; /* Default Foreground */ 59 + --base06: #1a1a1a; /* Light Foreground */ 60 + --base07: #0f0f0f; /* Lightest */ 61 + 62 + /* Base16 Accent Colors */ 63 + --base08: #d73a49; /* Red - Errors */ 64 + --base09: #e36209; /* Orange */ 65 + --base0A: #c08b00; /* Yellow */ 66 + --base0B: #22863a; /* Green - Success */ 67 + --base0C: #1b7c83; /* Cyan */ 68 + --base0D: #0066cc; /* Blue - Primary Accent */ 69 + --base0E: #6f42c1; /* Purple */ 70 + --base0F: #8b4513; /* Brown */ 71 + 72 + /* Semantic Aliases */ 73 + --theme-bg: var(--base00); 74 + --theme-text: var(--base05); 75 + --theme-accent: var(--base0D); 76 + 77 + /* Typography */ 78 + --theme-font-sans: -apple-system, BlinkMacSystemFont, sans-serif; 79 + --theme-font-mono: "SF Mono", monospace; 80 + } 81 + 82 + /* Dark mode (system preference) */ 83 + @media (prefers-color-scheme: dark) { 84 + :root:not([data-theme="light"]) { 85 + --base00: #1c1c1e; 86 + /* ... dark variants ... */ 87 + } 88 + } 89 + 90 + /* Forced light mode */ 91 + [data-theme="light"] { 92 + --base00: #ffffff; 93 + /* ... light variants ... */ 94 + } 95 + 96 + /* Forced dark mode */ 97 + [data-theme="dark"] { 98 + --base00: #1c1c1e; 99 + /* ... dark variants ... */ 100 + } 101 + ``` 102 + 103 + ### Custom Fonts 104 + 105 + Themes can include custom fonts. Reference them using `peek://theme/{themeId}/` URLs: 106 + 107 + ```css 108 + @font-face { 109 + font-family: "ServerMono"; 110 + src: url("peek://theme/peek/fonts/ServerMono-Regular.woff2") format("woff2"); 111 + font-weight: normal; 112 + font-style: normal; 113 + } 114 + ``` 115 + 116 + ## Using Theme Variables 117 + 118 + CSS files should import the active theme and use its variables: 119 + 120 + ```css 121 + /* Import active theme */ 122 + @import url('peek://theme/variables.css'); 123 + 124 + body { 125 + background: var(--base00); 126 + color: var(--base05); 127 + font-family: var(--theme-font-sans); 128 + } 129 + 130 + .button { 131 + background: var(--base0D); 132 + color: var(--base00); 133 + } 134 + 135 + .error { 136 + color: var(--base08); 137 + } 138 + ``` 139 + 140 + ## Theme Protocol 141 + 142 + The `peek://theme/` protocol serves theme files: 143 + 144 + - `peek://theme/variables.css` - Active theme's variables.css 145 + - `peek://theme/manifest.json` - Active theme's manifest 146 + - `peek://theme/{themeId}/variables.css` - Specific theme's variables 147 + - `peek://theme/{themeId}/fonts/file.woff2` - Theme font files 148 + 149 + Theme CSS and font files are served with `Cache-Control: no-store` headers to ensure theme changes take effect immediately. 150 + 151 + ## Theme API 152 + 153 + The Peek API exposes theme functionality via `window.app.theme`: 154 + 155 + ```javascript 156 + // Get current theme state 157 + const state = await api.theme.get(); 158 + // Returns: { themeId, colorScheme, isDark, effectiveScheme } 159 + 160 + // Set active theme 161 + await api.theme.setTheme('peek'); 162 + 163 + // Set color scheme preference 164 + await api.theme.setColorScheme('dark'); // 'system', 'light', or 'dark' 165 + 166 + // List available themes 167 + const result = await api.theme.getAll(); 168 + // Returns: { success: true, data: [{ id, name, version, ... }] } 169 + 170 + // Listen for theme changes (CSS will auto-reload) 171 + // This is handled automatically by the Peek API 172 + ``` 173 + 174 + ## Color Scheme Modes 175 + 176 + Themes support three color scheme modes: 177 + 178 + 1. **System** (default) - Follows OS light/dark preference 179 + 2. **Light** - Forces light mode 180 + 3. **Dark** - Forces dark mode 181 + 182 + The mode is controlled via the `data-theme` attribute on `<html>`: 183 + - No attribute: follows `prefers-color-scheme` media query 184 + - `data-theme="light"`: forces light mode 185 + - `data-theme="dark"`: forces dark mode 186 + 187 + ## Built-in Themes 188 + 189 + ### Basic 190 + Clean, minimal theme using system fonts and neutral colors. 191 + 192 + ### Peek 193 + Monospace aesthetic using ServerMono font with iOS-inspired accent colors. 194 + 195 + ## Theme Settings Storage 196 + 197 + Theme settings are stored in the `extension_settings` datastore table: 198 + 199 + - `extensionId`: `'core'` 200 + - `key`: `'theme.id'` or `'theme.colorScheme'` 201 + - `value`: JSON-encoded string (e.g., `"peek"`, `"system"`) 202 + 203 + ## Creating a New Theme 204 + 205 + 1. Create a new directory under `themes/`: 206 + ``` 207 + themes/mytheme/ 208 + manifest.json 209 + variables.css 210 + ``` 211 + 212 + 2. Define the manifest: 213 + ```json 214 + { 215 + "id": "mytheme", 216 + "name": "My Theme", 217 + "version": "1.0.0", 218 + "description": "My custom theme" 219 + } 220 + ``` 221 + 222 + 3. Define CSS variables following the Base16 scheme 223 + 224 + 4. Restart Peek - the theme will be auto-discovered 225 + 226 + ## Debugging 227 + 228 + Check theme loading in console: 229 + ``` 230 + Registered theme path: basic /path/to/themes/basic 231 + Discovered theme: basic 232 + ``` 233 + 234 + Run with `DEBUG=1 yarn start` for verbose logging.
-347
extensions/README.md
··· 1 - # Peek Extensions 2 - 3 - Extensions are isolated modules that communicate with the core app via IPC and pubsub messaging. 4 - 5 - ## Hybrid Extension Architecture 6 - 7 - Peek uses a **hybrid extension loading model** that balances memory efficiency with crash isolation: 8 - 9 - ### Built-in Extensions (Consolidated) 10 - Built-in extensions (`cmd`, `groups`, `peeks`, `slides`) run as **iframes in a single extension host window**: 11 - - Share a single Electron BrowserWindow process 12 - - Memory efficient (~80-120MB vs ~200-400MB for separate windows) 13 - - Origin isolation via unique URL hosts (`peek://cmd/`, `peek://groups/`, etc.) 14 - - If one crashes, others in the same host are affected 15 - 16 - ### External Extensions (Separate Windows) 17 - External extensions (including `example` and user-installed) run in **separate BrowserWindows**: 18 - - Each has its own Electron process 19 - - Crash isolation - one extension crashing doesn't affect others 20 - - Uses `peek://ext/{id}/` URL scheme 21 - - Better for untrusted or experimental extensions 22 - 23 - ``` 24 - ┌─────────────────────────────────────────────────────────────┐ 25 - │ Extension Host Window (peek://app/extension-host.html) │ 26 - │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────┐ │ 27 - │ │ <iframe> │ │ <iframe> │ │ <iframe> │ │<iframe> │ │ 28 - │ │ peek://cmd/ │ │peek://groups│ │peek://peeks/│ │peek:// │ │ 29 - │ │ │ │ │ │ │ │slides/ │ │ 30 - │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────┘ │ 31 - └─────────────────────────────────────────────────────────────┘ 32 - 33 - ┌─────────────────┐ ┌─────────────────┐ 34 - │ BrowserWindow │ │ BrowserWindow │ 35 - │ peek://ext/ │ │ peek://ext/ │ 36 - │ example/ │ │ user-ext/ │ 37 - │ (separate proc) │ │ (separate proc) │ 38 - └─────────────────┘ └─────────────────┘ 39 - ``` 40 - 41 - ### Origin Isolation 42 - 43 - Each extension gets a unique origin regardless of loading mode: 44 - - Built-in: `peek://cmd/background.html` → origin `peek://cmd` 45 - - External: `peek://ext/example/background.html` → origin `peek://ext` 46 - 47 - This prevents cross-extension access to localStorage, DOM, and globals. 48 - 49 - ## Extension Structure 50 - 51 - Each extension lives in its own directory under `extensions/`: 52 - 53 - ``` 54 - extensions/ 55 - example/ 56 - manifest.json # Extension metadata 57 - settings-schema.json # Settings UI schema (optional) 58 - background.html # Entry point (loads background.js) 59 - background.js # Main extension logic 60 - *.html, *.js, *.css # Additional UI files 61 - ``` 62 - 63 - ### manifest.json 64 - 65 - Required fields: 66 - ```json 67 - { 68 - "id": "example", 69 - "shortname": "example", 70 - "name": "Example Extension", 71 - "description": "What this extension does", 72 - "version": "1.0.0", 73 - "background": "background.html" 74 - } 75 - ``` 76 - 77 - Optional fields: 78 - ```json 79 - { 80 - "builtin": true, 81 - "settingsSchema": "./settings-schema.json" 82 - } 83 - ``` 84 - 85 - ### settings-schema.json 86 - 87 - Defines the settings UI for the extension. Used by Settings to render configuration forms. 88 - 89 - ```json 90 - { 91 - "prefs": { 92 - "type": "object", 93 - "properties": { 94 - "greeting": { 95 - "type": "string", 96 - "description": "Custom greeting message", 97 - "default": "Hello World" 98 - } 99 - } 100 - }, 101 - "storageKeys": { 102 - "PREFS": "prefs" 103 - }, 104 - "defaults": { 105 - "prefs": { 106 - "greeting": "Hello World" 107 - } 108 - } 109 - } 110 - ``` 111 - 112 - For extensions with list-based settings (like peeks/slides), add an `item` schema: 113 - ```json 114 - { 115 - "prefs": { ... }, 116 - "item": { 117 - "type": "object", 118 - "properties": { 119 - "title": { "type": "string", "title": "Title" }, 120 - "enabled": { "type": "boolean", "title": "Enabled" } 121 - } 122 - }, 123 - "storageKeys": { 124 - "PREFS": "prefs", 125 - "ITEMS": "items" 126 - }, 127 - "defaults": { 128 - "prefs": { ... }, 129 - "items": [] 130 - } 131 - } 132 - ``` 133 - 134 - ### background.html 135 - 136 - Entry point that loads the extension as an ES module: 137 - 138 - ```html 139 - <!DOCTYPE html> 140 - <html> 141 - <head> 142 - <meta charset="UTF-8"> 143 - <title>My Extension</title> 144 - </head> 145 - <body> 146 - <script type="module"> 147 - import extension from './background.js'; 148 - 149 - const api = window.app; 150 - const extId = extension.id; 151 - 152 - console.log(`[ext:${extId}] background.html loaded`); 153 - 154 - // Signal ready to main process 155 - api.publish('ext:ready', { 156 - id: extId, 157 - manifest: { 158 - id: extension.id, 159 - labels: extension.labels, 160 - version: '1.0.0' 161 - } 162 - }, api.scopes.SYSTEM); 163 - 164 - // Initialize extension 165 - if (extension.init) { 166 - console.log(`[ext:${extId}] calling init()`); 167 - extension.init(); 168 - } 169 - 170 - // Handle shutdown 171 - api.subscribe('app:shutdown', () => { 172 - if (extension.uninit) extension.uninit(); 173 - }, api.scopes.SYSTEM); 174 - 175 - api.subscribe(`ext:${extId}:shutdown`, () => { 176 - if (extension.uninit) extension.uninit(); 177 - }, api.scopes.SYSTEM); 178 - </script> 179 - </body> 180 - </html> 181 - ``` 182 - 183 - ### background.js 184 - 185 - Main extension logic as an ES module: 186 - 187 - ```javascript 188 - const api = window.app; 189 - 190 - const extension = { 191 - id: 'example', 192 - labels: { 193 - name: 'Example' 194 - }, 195 - 196 - init() { 197 - console.log('[example] init'); 198 - 199 - // Register commands 200 - api.commands.register({ 201 - name: 'my-command', 202 - description: 'Does something', 203 - execute: () => { 204 - console.log('Command executed!'); 205 - } 206 - }); 207 - 208 - // Register shortcuts 209 - api.shortcuts.register('Option+x', () => { 210 - console.log('Shortcut triggered!'); 211 - }); 212 - 213 - // Subscribe to events 214 - api.subscribe('some:event', (msg) => { 215 - console.log('Event received:', msg); 216 - }, api.scopes.GLOBAL); 217 - }, 218 - 219 - uninit() { 220 - console.log('[example] uninit'); 221 - api.commands.unregister('my-command'); 222 - api.shortcuts.unregister('Option+x'); 223 - } 224 - }; 225 - 226 - export default extension; 227 - ``` 228 - 229 - ## Extension API 230 - 231 - Extensions access the Peek API via `window.app`. See `docs/api.md` for the complete reference. 232 - 233 - Common APIs used by extensions: 234 - 235 - ### Commands 236 - ```javascript 237 - api.commands.register({ name, description, execute }) 238 - api.commands.unregister(name) 239 - ``` 240 - 241 - ### Shortcuts 242 - ```javascript 243 - api.shortcuts.register(shortcut, callback) // e.g., 'Option+1' 244 - api.shortcuts.unregister(shortcut) 245 - ``` 246 - 247 - ### Pubsub Messaging 248 - ```javascript 249 - api.publish(topic, data, scope) 250 - api.subscribe(topic, callback, scope) 251 - 252 - // Scopes 253 - api.scopes.SELF // Only this window 254 - api.scopes.SYSTEM // System-level events 255 - api.scopes.GLOBAL // All windows 256 - ``` 257 - 258 - ### Windows 259 - ```javascript 260 - api.window.open(url, options) 261 - // Options: modal, keepLive, transparent, height, width, key 262 - ``` 263 - 264 - ### Datastore 265 - ```javascript 266 - await api.datastore.getRow(table, id) 267 - await api.datastore.setRow(table, id, data) 268 - await api.datastore.deleteRow(table, id) 269 - await api.datastore.getTable(table) 270 - ``` 271 - 272 - ### Extension Settings 273 - ```javascript 274 - await api.extensions.getSettings(extId) 275 - await api.extensions.setSettings(extId, key, value) 276 - ``` 277 - 278 - ## Extension Loading 279 - 280 - ### Load Order and the cmd Extension 281 - 282 - The `cmd` extension is the command registry - all other extensions register their commands with it via `api.commands.register()`. Because of this dependency: 283 - 284 - 1. **cmd loads first** (sequential) - must be ready before other extensions register commands 285 - 2. **Other extensions load in parallel** - for faster startup 286 - 3. **cmd cannot be disabled** - it's required infrastructure, not optional functionality 287 - 288 - This is enforced in `isBuiltinExtensionEnabled()` which always returns `true` for cmd. 289 - 290 - ### Hybrid Loading Process 291 - 292 - Extensions are loaded in hybrid mode by `loadExtensions()` in `backend/electron/main.ts`: 293 - 294 - 1. **Create extension host window** - Single BrowserWindow at `peek://app/extension-host.html` 295 - 2. **Load built-in extensions as iframes** - `cmd`, `groups`, `peeks`, `slides` loaded via IPC into the host 296 - 3. **Load external extensions as separate windows** - Each gets its own BrowserWindow 297 - 298 - ```typescript 299 - // Which extensions use consolidated mode (defined in main.ts) 300 - const CONSOLIDATED_EXTENSION_IDS = ['cmd', 'groups', 'peeks', 'slides']; 301 - ``` 302 - 303 - ### Built-in Extensions 304 - 305 - Built-in extensions are registered in `index.js`: 306 - ```javascript 307 - registerExtensionPath('example', path.join(__dirname, 'extensions', 'example')); 308 - ``` 309 - 310 - Built-in extensions that are NOT in `CONSOLIDATED_EXTENSION_IDS` (like `example`) are treated as external and get separate windows. This is intentional - it exercises external extension code paths during development. 311 - 312 - ### External Extensions 313 - 314 - External extensions are: 315 - 1. Added via Settings UI (stored in datastore `extensions` table) 316 - 2. Loaded on startup if `enabled === 1` and have a valid `path` 317 - 3. Always run in separate BrowserWindows for crash isolation 318 - 319 - ## Settings Integration 320 - 321 - Extensions with `settingsSchema` in their manifest automatically get a settings section in the Settings UI. The schema is loaded at runtime when the extension window is created. 322 - 323 - Settings are stored in the `extension_settings` datastore table with: 324 - - `extensionId`: The extension's ID 325 - - `key`: Setting key (e.g., 'prefs', 'items') 326 - - `value`: JSON-encoded setting value 327 - 328 - Extensions can listen for settings changes: 329 - ```javascript 330 - api.subscribe(`${extId}:settings-changed`, (msg) => { 331 - // Reload configuration 332 - }, api.scopes.GLOBAL); 333 - ``` 334 - 335 - ## Lifecycle Events 336 - 337 - - `ext:ready` - Published when extension is initialized 338 - - `ext:all-loaded` - Published when all extensions finish loading 339 - - `app:shutdown` - Sent before app closes 340 - - `ext:{id}:shutdown` - Sent when specific extension is being unloaded 341 - - `{extId}:settings-changed` - Sent when extension settings are modified 342 - 343 - ## Debugging 344 - 345 - Console logs from extensions are forwarded to stdout with prefix `[ext:{id}]`. 346 - 347 - Run with `DEBUG=1 yarn start` for verbose logging.
extensions/cmd/BACKEND_NOTES.md notes/cmd-backend-notes.md
extensions/cmd/README.md docs/cmd.md
notes/research/datastore-architecture.md notes/datastore-architecture.md
notes/research/datastore-integration.md notes/datastore-integration.md
notes/research/datastore-research.md notes/datastore-research.md
notes/research/datastore-schema.md notes/datastore-schema.md
notes/research/datastore.md notes/datastore.md
notes/research/design-vision.md notes/design-vision.md
notes/research/sync-architecture.md notes/sync-architecture.md
notes/research/sync-edge-cases.md notes/sync-edge-cases.md
+1 -232
themes/README.md
··· 1 1 # Peek Themes 2 2 3 - Themes control the visual appearance of Peek, including colors and typography. They use a Base16 color scheme that supports light and dark variants. 4 - 5 - ## Theme Structure 6 - 7 - Each theme lives in its own directory under `themes/`: 8 - 9 - ``` 10 - themes/ 11 - basic/ 12 - manifest.json # Theme metadata 13 - variables.css # CSS custom properties 14 - peek/ 15 - manifest.json 16 - variables.css 17 - fonts/ # Optional custom fonts 18 - ServerMono-Regular.woff2 19 - ``` 20 - 21 - ### manifest.json 22 - 23 - Required fields: 24 - ```json 25 - { 26 - "id": "basic", 27 - "name": "Basic", 28 - "version": "1.0.0" 29 - } 30 - ``` 31 - 32 - Optional fields: 33 - ```json 34 - { 35 - "description": "Clean minimal theme", 36 - "author": "Peek Team", 37 - "colorSchemes": ["light", "dark"] 38 - } 39 - ``` 40 - 41 - ### variables.css 42 - 43 - Defines CSS custom properties using the Base16 color scheme. The file must define: 44 - 45 - 1. **Base colors (base00-base0F)** - 16 colors following Base16 conventions 46 - 2. **Semantic aliases** - Mapped to base colors for easier use 47 - 3. **Typography** - Font families for sans and mono text 48 - 49 - ```css 50 - /* Light mode (default) */ 51 - :root { 52 - /* Base16 Grayscale */ 53 - --base00: #ffffff; /* Default Background */ 54 - --base01: #f5f5f5; /* Lighter Background */ 55 - --base02: #e0e0e0; /* Selection Background */ 56 - --base03: #b0b0b0; /* Comments, Muted */ 57 - --base04: #666666; /* Dark Foreground */ 58 - --base05: #333333; /* Default Foreground */ 59 - --base06: #1a1a1a; /* Light Foreground */ 60 - --base07: #0f0f0f; /* Lightest */ 61 - 62 - /* Base16 Accent Colors */ 63 - --base08: #d73a49; /* Red - Errors */ 64 - --base09: #e36209; /* Orange */ 65 - --base0A: #c08b00; /* Yellow */ 66 - --base0B: #22863a; /* Green - Success */ 67 - --base0C: #1b7c83; /* Cyan */ 68 - --base0D: #0066cc; /* Blue - Primary Accent */ 69 - --base0E: #6f42c1; /* Purple */ 70 - --base0F: #8b4513; /* Brown */ 71 - 72 - /* Semantic Aliases */ 73 - --theme-bg: var(--base00); 74 - --theme-text: var(--base05); 75 - --theme-accent: var(--base0D); 76 - 77 - /* Typography */ 78 - --theme-font-sans: -apple-system, BlinkMacSystemFont, sans-serif; 79 - --theme-font-mono: "SF Mono", monospace; 80 - } 81 - 82 - /* Dark mode (system preference) */ 83 - @media (prefers-color-scheme: dark) { 84 - :root:not([data-theme="light"]) { 85 - --base00: #1c1c1e; 86 - /* ... dark variants ... */ 87 - } 88 - } 89 - 90 - /* Forced light mode */ 91 - [data-theme="light"] { 92 - --base00: #ffffff; 93 - /* ... light variants ... */ 94 - } 95 - 96 - /* Forced dark mode */ 97 - [data-theme="dark"] { 98 - --base00: #1c1c1e; 99 - /* ... dark variants ... */ 100 - } 101 - ``` 102 - 103 - ### Custom Fonts 104 - 105 - Themes can include custom fonts. Reference them using `peek://theme/{themeId}/` URLs: 106 - 107 - ```css 108 - @font-face { 109 - font-family: "ServerMono"; 110 - src: url("peek://theme/peek/fonts/ServerMono-Regular.woff2") format("woff2"); 111 - font-weight: normal; 112 - font-style: normal; 113 - } 114 - ``` 115 - 116 - ## Using Theme Variables 117 - 118 - CSS files should import the active theme and use its variables: 119 - 120 - ```css 121 - /* Import active theme */ 122 - @import url('peek://theme/variables.css'); 123 - 124 - body { 125 - background: var(--base00); 126 - color: var(--base05); 127 - font-family: var(--theme-font-sans); 128 - } 129 - 130 - .button { 131 - background: var(--base0D); 132 - color: var(--base00); 133 - } 134 - 135 - .error { 136 - color: var(--base08); 137 - } 138 - ``` 139 - 140 - ## Theme Protocol 141 - 142 - The `peek://theme/` protocol serves theme files: 143 - 144 - - `peek://theme/variables.css` - Active theme's variables.css 145 - - `peek://theme/manifest.json` - Active theme's manifest 146 - - `peek://theme/{themeId}/variables.css` - Specific theme's variables 147 - - `peek://theme/{themeId}/fonts/file.woff2` - Theme font files 148 - 149 - Theme CSS and font files are served with `Cache-Control: no-store` headers to ensure theme changes take effect immediately. 150 - 151 - ## Theme API 152 - 153 - The Peek API exposes theme functionality via `window.app.theme`: 154 - 155 - ```javascript 156 - // Get current theme state 157 - const state = await api.theme.get(); 158 - // Returns: { themeId, colorScheme, isDark, effectiveScheme } 159 - 160 - // Set active theme 161 - await api.theme.setTheme('peek'); 162 - 163 - // Set color scheme preference 164 - await api.theme.setColorScheme('dark'); // 'system', 'light', or 'dark' 165 - 166 - // List available themes 167 - const result = await api.theme.getAll(); 168 - // Returns: { success: true, data: [{ id, name, version, ... }] } 169 - 170 - // Listen for theme changes (CSS will auto-reload) 171 - // This is handled automatically by the Peek API 172 - ``` 173 - 174 - ## Color Scheme Modes 175 - 176 - Themes support three color scheme modes: 177 - 178 - 1. **System** (default) - Follows OS light/dark preference 179 - 2. **Light** - Forces light mode 180 - 3. **Dark** - Forces dark mode 181 - 182 - The mode is controlled via the `data-theme` attribute on `<html>`: 183 - - No attribute: follows `prefers-color-scheme` media query 184 - - `data-theme="light"`: forces light mode 185 - - `data-theme="dark"`: forces dark mode 186 - 187 - ## Built-in Themes 188 - 189 - ### Basic 190 - Clean, minimal theme using system fonts and neutral colors. 191 - 192 - ### Peek 193 - Monospace aesthetic using ServerMono font with iOS-inspired accent colors. 194 - 195 - ## Theme Settings Storage 196 - 197 - Theme settings are stored in the `extension_settings` datastore table: 198 - 199 - - `extensionId`: `'core'` 200 - - `key`: `'theme.id'` or `'theme.colorScheme'` 201 - - `value`: JSON-encoded string (e.g., `"peek"`, `"system"`) 202 - 203 - ## Creating a New Theme 204 - 205 - 1. Create a new directory under `themes/`: 206 - ``` 207 - themes/mytheme/ 208 - manifest.json 209 - variables.css 210 - ``` 211 - 212 - 2. Define the manifest: 213 - ```json 214 - { 215 - "id": "mytheme", 216 - "name": "My Theme", 217 - "version": "1.0.0", 218 - "description": "My custom theme" 219 - } 220 - ``` 221 - 222 - 3. Define CSS variables following the Base16 scheme 223 - 224 - 4. Restart Peek - the theme will be auto-discovered 225 - 226 - ## Debugging 227 - 228 - Check theme loading in console: 229 - ``` 230 - Registered theme path: basic /path/to/themes/basic 231 - Discovered theme: basic 232 - ``` 233 - 234 - Run with `DEBUG=1 yarn start` for verbose logging. 3 + See [docs/themes.md](../docs/themes.md) for theme documentation.