···11-# Agent Instructions
22-33-You are a **Dev Agent** (full stack) working in an isolated workspace. Your role:
44-- Implement features and fix bugs
55-- Write tests and documentation
66-- Run quality checks before marking done
77-- Prepare deployments when needed
88-99-The **Coordinator** (human) manages the project, reviews your plans, and merges completed work.
1010-1111-## Resources
1212-1313-All these files are in YOUR workspace directory (use `pwd` to confirm):
1414-- `TODO.md` - Project tasks and priorities
1515-- `notes/development.md` - Architecture and dev guide
1616-1717-**Do NOT access files in ~/misc/mpeek/ or any parent directory. Your workspace has everything you need.**
1818-1919-## Status Reporting
2020-2121-Report status so the coordinator knows your progress:
2222-2323-```bash
2424-agent-status "working" "brief description"
2525-agent-status "blocked" "what you need" # Only when truly stuck
2626-agent-status "review" "ready for review"
2727-agent-status "done" "what you accomplished"
2828-```
2929-3030----
3131-3232-## Policies
3333-3434-### Policy: Autonomy
3535-3636-- **Work independently** - Make reasonable decisions without asking. You have full access to the codebase.
3737-- **Don't ask for basic permissions** - Common tools (ls, find, npm, yarn, jj, node, etc.) are pre-approved.
3838-- **Batch your work** - Do multiple related operations before pausing.
3939-- **Only interrupt when truly blocked** - Not for confirmations, only for missing information you can't find.
4040-4141-### Policy: Workspace
4242-4343-- **Work in your current directory** - Your workspace is an isolated jj worktree with a full copy of the repo.
4444-- **CRITICAL: Use YOUR workspace path for all file operations** - When using Read/Edit tools, files must be in YOUR workspace directory (check with `pwd`), NOT the main repo. Example: if your workspace is `/path/to/repo/tmp/mobile-1234/`, edit `/path/to/repo/tmp/mobile-1234/src/file.js`, NOT `/path/to/repo/src/file.js`.
4545-- **Never use hardcoded paths to ~/misc/mpeek or similar** - Always resolve paths relative to your current working directory.
4646-4747-### Policy: Process Safety (CRITICAL)
4848-4949-**NEVER interfere with processes outside your workspace.**
5050-5151-- **Do NOT kill, stop, or signal processes** you didn't start (no `kill`, `pkill`, `killall` on external processes)
5252-- **Do NOT modify system state** outside your workspace (no writing to /tmp shared files, no global npm installs, etc.)
5353-- **Only manage processes YOU started** within YOUR workspace session
5454-- **If external processes block you, REPORT IT** - don't try to fix it yourself
5555-5656-Violations of this policy compromise other agents and the coordinator's work. If you encounter a port conflict or blocking process, use `agent-status "blocked" "description"` and wait for coordinator assistance.
5757-5858-### Policy: Commands (CRITICAL)
5959-6060-**STOP. READ THIS BEFORE RUNNING ANY COMMAND.**
6161-6262-1. **ALWAYS use package.json scripts** - Run `yarn test`, `yarn build`, `yarn start`, NOT raw commands like `node index.js` or `npm run`.
6363-6464-2. **NEVER use && or ; or |** - These require approval EVERY time. Run commands separately.
6565-6666-3. **Check package.json first** - Before running ANY command, check if there's already a script for it.
6767-6868-**BAD (will be rejected):**
6969-```bash
7070-cd backend/server && node index.js
7171-npm install && npm test
7272-NODE_ENV=test node --test
7373-```
7474-7575-**GOOD:**
7676-```bash
7777-yarn test
7878-yarn start
7979-yarn build
8080-```
8181-8282-If a script doesn't exist for what you need, ADD ONE to package.json first, then run it.
8383-8484-### Policy: Version Control
8585-8686-**This repo uses jj (Jujutsu), not git. Never use git commands directly.**
8787-8888-```bash
8989-jj st # status
9090-jj log # log recent changes
9191-jj diff # diff working copy
9292-jj commit -m "message" # commit your work
9393-jj squash -m "message" # squash into parent with message (when finishing)
9494-```
9595-9696-Key differences from git:
9797-- No staging area - all changes tracked automatically
9898-- `jj commit` creates new empty change on top
9999-- **NEVER move the main bookmark** - coordinator handles that via `mmerge`
100100-- Don't push directly - coordinator handles pushing after merge
101101-102102-**CRITICAL: Commit frequently to avoid losing work!**
103103-- If you see "working copy is stale", COMMIT FIRST before running `jj workspace update-stale`
104104-- `jj workspace update-stale` will OVERWRITE uncommitted changes
105105-- Commit after every significant edit, not just at the end
106106-- Use `jj commit -m "WIP: description"` for work-in-progress saves
107107-108108-### Policy: Sync Before Starting Work
109109-110110-After your plan is approved and before starting implementation, **always rebase on main** to get the latest changes:
111111-112112-```bash
113113-jj rebase -d main
114114-```
115115-116116-This ensures you have the latest code and TODO.md state from other agents or the coordinator.
117117-118118-### Policy: Commits
119119-120120-- Ask before committing - don't commit automatically, but do offer to commit when work is done
121121-- User (dietrich ayala) is sole author of all commits
122122-123123-### Policy: Quality
124124-125125-Before marking any task as done:
126126-127127-1. **Tests** - Write tests for new functionality. Run all tests and ensure they pass.
128128-2. **Documentation** - Update relevant docs (README, API docs, etc.) if behavior changes.
129129-3. **Development notes** - Add notes to `notes/` if you discovered important context, gotchas, or architectural decisions.
130130-4. **Verify** - Run the build/lint/tests. Do NOT mark done if tests fail.
131131-132132-### Policy: Cleanup
133133-134134-When your task is complete, you MUST complete this checklist:
135135-136136-**1. Mark task complete in TODO.md**
137137-- Find your task in the Today section, change `- [~]` (in-progress) to `- [x]` (done)
138138-- Move to Done section under current week heading (`### YYYY-WNN`)
139139-140140-**2. Write development notes** (if applicable)
141141-- Add to existing file in `notes/` or create new one
142142-- Focus on what future developers need to know
143143-144144-**3. Record learnings** (if you discovered tips, gotchas, or patterns)
145145-- Create or append to `notes/agent-learnings.md` in your workspace
146146-- The coordinator will consolidate these after merging
147147-148148-**4. Squash your work**
149149-```bash
150150-jj squash -m "description of changes"
151151-```
152152-The coordinator will handle the actual merge with `mmerge`.
153153-154154-**5. Report completion**
155155-```bash
156156-agent-status "done" "summary of what you accomplished"
157157-```
158158-159159-**Do NOT skip these steps.** Never access files outside your workspace.
160160-161161----
162162-163163-## BEFORE REPORTING DONE
164164-165165-You MUST complete the cleanup checklist. Verify:
166166-- [ ] TODO.md updated: task changed from `[~]` to `[x]`, moved to Done section under `### 2026-WNN`
167167-- [ ] `jj squash -m "description"` run to squash your commits
168168-- [ ] `agent-status "done" "summary"` called
169169-170170-**Failure to update TODO.md means the task is not complete.**
171171-172172----
173173-174174-## Your Taskshared iOS build artifacts in ./tmp to avoid full rebuilds per workspace (one mobile task at a time)
+325-156
DEVELOPMENT.md
···11-# Development Guide
11+# Peek Development Guide
2233-## Commands
33+## Project Overview
4455-Bare commands (`dev`, `start`, `debug`, `kill`, `restart`) default to Electron. Use `BACKEND=tauri` to switch:
55+Peek is a web user agent application that provides alternative ways to interact with web pages through keyboard shortcuts, modal windows, and background scripts. It's designed as a concept preview exploring task-aligned interfaces for the web, moving beyond traditional tabbed browsers.
6677-```bash
88-yarn debug # Electron (default)
99-BACKEND=tauri yarn debug # Tauri
1010-yarn debug:electron # Explicit Electron
1111-yarn debug:tauri # Explicit Tauri
1212-```
77+**Multi-Backend Architecture**: Peek supports multiple backends (Electron, Tauri) that can coexist and sync data. The `app/` directory is **backend-agnostic** and must not contain backend-specific code. Backend-specific code lives in `backend/{electron,tauri}/`.
88+99+## Requirements
1010+1111+- **Node.js 24+** - Required for both desktop (Electron 40) and server
1212+- Install via `nvm install 24` or download from nodejs.org
13131414-All Electron commands automatically build TypeScript before running.
1414+## Key Commands
15151616+### Development
1617```bash
1717-# Install dependencies
1818-yarn install
1818+nvm use 24 # Ensure Node 24 is active
1919+yarn install # Install dependencies
2020+yarn debug # Run in development mode (with devtools)
2121+yarn start # Start normally
2222+yarn package # Package (output: out/mac-arm64/)
2323+yarn package:install # Package and install to /Applications (macOS)
2424+yarn make # Build distributable packages
2525+yarn npm audit # Check for security vulnerabilities
19262020-# Run in development mode (with devtools + hot reload)
2121-yarn debug
2727+# Tauri backend
2828+yarn tauri:dev # Run Tauri in development
2929+yarn tauri:build # Build Tauri for production
3030+yarn tauri:check # Check Tauri compiles
3131+yarn tauri:test # Run Tauri smoke tests (Rust)
22322323-# Start the application normally
2424-yarn start
3333+# Tauri Mobile (iOS/Android)
3434+yarn tauri:ios:dev # Run on iOS simulator
3535+yarn tauri:ios:build # Build for iOS
3636+yarn tauri:ios:xcode # Open Xcode project
3737+yarn tauri:android:dev # Run on Android emulator
25382626-# Build TypeScript only (usually not needed - commands auto-build)
2727-yarn build
3939+# Server (webhook API for mobile sync)
4040+yarn server:install # Install server dependencies
4141+yarn server:start # Run production server
4242+yarn server:dev # Run with hot reload
4343+yarn server:test # Run server tests
4444+yarn server:healthcheck # Verify server starts
2845```
29463030-## Hot Reload (Electron only)
3131-3232-In dev mode (`yarn debug` or `DEBUG=1`), hot reload is enabled:
3333-- Watches `app/` and `extensions/` directories
3434-- Auto-reloads all windows when `.html`, `.js`, or `.css` files change
3535-- No restart needed for renderer-side changes
3636-3737-Note: Changes to `backend/` TypeScript files still require rebuilding (`yarn build`) and restarting the app. Hot reload is not yet implemented for the Tauri backend.
3838-4747+### Testing
3948```bash
4040-# Typical dev workflow:
4141-yarn debug # Start app with hot reload
4242-# Edit files in app/ or extensions/
4343-# Windows auto-reload on save
4949+yarn test # Run all tests (headless)
5050+yarn test:electron # Run Electron tests only
5151+yarn test:visible # Run with visible windows
5252+yarn test:grep "pattern" # Run specific test by name
4453```
45544646-```bash
4747-# Package the application (output: out/mac-arm64/)
4848-yarn package
4949-5050-# Package and install to /Applications (macOS)
5151-yarn package:install
5252-5353-# Build distributable packages
5454-yarn make
5555-5656-# Check for security vulnerabilities
5757-yarn npm audit
5858-5959-# Test packaged build with dev profile
6060-PROFILE=dev out/mac-arm64/Peek.app/Contents/MacOS/Peek
6161-```
5555+**Testing Policy**: When fixing bugs or adding features, always add tests to cover the change. Tests live in `tests/desktop/smoke.spec.ts`.
62566357## Architecture Overview
64586559### Core Structure
66606767-The application uses a multi-window Electron architecture:
6868-69611. **Main Process** (`index.js`):
7062 - Manages app lifecycle, windows, shortcuts, IPC communication
7163 - Implements custom `peek://` protocol for internal navigation
···746675672. **Renderer Process** (`app/`):
7668 - Core app logic loads from `peek://app/background.html`
7777- - Feature modules: peeks, slides, scripts, cmd, groups
6969+ - Extension loader at `app/extensions/loader.js`
7070+ - Feature modules: scripts, cmd (registered in `app/features.js`)
7171+ - Built-in extensions: groups, peeks, slides (in `./extensions/`)
7872 - Settings UI at `peek://app/settings/settings.html`
7979- - Datastore viewer at `peek://app/datastore/viewer.html`
807381743. **Peek API** (`window.app`):
8282- - Unified API exposed to all `peek://` pages
8383- - Provides shortcuts, window management, pubsub, datastore, and theme APIs
8484- - See `docs/PEEK-API.md` for complete reference
7575+ - `api.window.*` for window management
7676+ - `api.shortcuts.*` for hotkeys (local by default, `{ global: true }` for OS-level)
7777+ - `api.datastore.*` for data persistence
7878+ - `api.commands.*` for command palette integration
7979+ - `api.theme.*` for theme management
8080+ - `api.publish()`/`api.subscribe()` for cross-window messaging
8181+8282+### Backend Structure
8383+```
8484+backend/
8585+├── electron/ # Electron desktop backend (current primary)
8686+│ ├── datastore.ts # SQLite via better-sqlite3
8787+│ ├── ipc.ts # IPC handlers
8888+│ └── protocol.ts # peek:// protocol handler
8989+├── tauri/ # Tauri desktop backend (Rust)
9090+│ ├── src-tauri/
9191+│ │ ├── src/
9292+│ │ │ ├── lib.rs # App setup
9393+│ │ │ ├── datastore.rs # SQLite via rusqlite
9494+│ │ │ └── commands/ # IPC command handlers
9595+│ │ └── tests/smoke.rs # Rust smoke tests
9696+│ └── preload.js # Peek API implementation (Tauri)
9797+├── tauri-mobile/ # Tauri mobile app (iOS/Android)
9898+│ ├── src/ # React frontend
9999+│ └── src-tauri/ # Rust backend
100100+└── server/ # Webhook API server for mobile sync
101101+ ├── index.js # Hono HTTP server
102102+ ├── db.js # SQLite via better-sqlite3
103103+ └── users.js # Multi-user API key auth
104104+```
105105+106106+**Data Sync**: Both backends use the same SQLite database path (`~/.config/Peek/{profile}/datastore.sqlite`). Only one backend should run at a time (file lock).
8510786108### Custom Protocol
109109+- `peek://app/...` - Core application files (from `app/` directory)
110110+- `peek://ext/{shortname}/...` - Extension files
111111+- `peek://tauri/...` - Tauri backend files (only in Tauri)
871128888-- Uses `peek://` scheme for internal pages
8989-- Cross-origin network access enabled for peek:// pages
9090-- Special APIs available: window control, global hotkeys, pubsub messaging
113113+### Extensions Architecture
911149292-### Profile Management
115115+Extensions run in isolated BrowserWindow processes at `peek://ext/{id}/background.html`.
931169494-Profile is determined automatically:
9595-- Packaged app (`/Applications/Peek.app`) uses `default` profile
117117+```
118118+extensions/
119119+├── example/ # Hello world example
120120+├── groups/
121121+├── peeks/
122122+└── slides/
123123+ ├── manifest.json # Extension metadata
124124+ ├── settings-schema.json # Settings UI schema (optional)
125125+ ├── background.html # Entry point
126126+ └── background.js # Main logic (ES module export)
127127+```
128128+129129+**Background script pattern:**
130130+```javascript
131131+const api = window.app;
132132+133133+const extension = {
134134+ id: 'example',
135135+ labels: { name: 'Example' },
136136+137137+ init() {
138138+ api.shortcuts.register('Option+x', handler, { global: true });
139139+ api.commands.register({ name: 'my-cmd', description: '...', execute: fn });
140140+ },
141141+142142+ uninit() {
143143+ api.shortcuts.unregister('Option+x', { global: true });
144144+ api.commands.unregister('my-cmd');
145145+ }
146146+};
147147+148148+export default extension;
149149+```
150150+151151+### Window Management
152152+- Windows identified by keys for lifecycle management (e.g., `peek:${address}`)
153153+- Modal windows use `type: 'panel'` to return focus to previous app on close
154154+- Parameters: `modal`, `keepLive`, `persistState`, `transparent`, `height`, `width`, `key`
155155+- "Escape IZUI" design - ESC key always returns to previous context
156156+157157+### Data Storage
158158+159159+**Settings Storage (localStorage)**:
160160+- Profile-based data separation in `{userData}/{PROFILE}/` directory
161161+- Features use `openStore(id, defaults, clear)` utility from `app/utils.js`
162162+163163+**Datastore (TinyBase)**:
164164+- In-memory TinyBase store for structured data
165165+- Runs in main process with IPC handlers for renderer access
166166+- Schema defined in `app/datastore/schema.js`
167167+- Tables: `addresses`, `visits`, `content`, `tags`, `blobs`, `scripts_data`, `feeds`
168168+169169+**Profile Management**:
170170+- Packaged app uses `default` profile
96171- Running from source (`yarn start`) uses `dev` profile
9797-- `PROFILE` env var overrides (e.g., `PROFILE=test yarn start`)
172172+- `PROFILE` env var overrides automatic detection
981739999-Profile data stored in `{userData}/{PROFILE}/` directory.
174174+## Common Pitfalls
100175101101-## Window API
176176+1. Don't use relative paths in peek:// URLs - use absolute paths
177177+2. Remember to unregister shortcuts when features unload
178178+3. Windows opened by features should be tracked and closed on unload
179179+4. Modal windows require both `modal: true` and `type: 'panel'`
180180+5. Window keys must be unique - use pattern like `peek:${address}`
181181+6. Check if items are enabled (`item.enabled == true`) before registering shortcuts
182182+7. Datastore API returns `{ success, data }` - always check `result.success`
183183+8. Pubsub subscriptions are keyed by source - same source subscribing twice overwrites
184184+9. **Never put backend-specific code in `app/`**
185185+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).
102186103103-### Opening Windows
187187+## Code Style
104188105105-```javascript
106106-import windows from './windows.js';
189189+- ES6 modules throughout (type: "module" in package.json)
190190+- Async/await preferred over callbacks
191191+- Console logging for debugging (controlled by DEBUG env var)
192192+- No TypeScript in app/, pure JavaScript
193193+- Uses `nodemon` for hot reload during development
107194108108-// Modal window (closes on blur/ESC)
109109-windows.openModalWindow(url, options);
195195+## Security Notes
110196111111-// Regular window
112112-windows.createWindow(url, options);
113113-```
197197+- This is a concept preview, NOT production-ready
198198+- No formal security audit performed
199199+- Different security model than traditional browsers
200200+- Be cautious with cross-origin access and custom APIs
114201115115-### Window Options
202202+## Mobile Development (Tauri iOS/Android)
203203+204204+Mobile development uses the separate `peek-save` app in `backend/tauri-mobile/`.
205205+206206+**CRITICAL - Build with Xcode GUI for iOS:**
207207+- NEVER run `xcodebuild` commands from terminal for final builds
208208+- Use Xcode GUI (Product → Build, Product → Run) for reliable builds
116209117117-| Option | Type | Description |
118118-|--------|------|-------------|
119119-| `width`, `height` | number | Window dimensions |
120120-| `x`, `y` | number | Window position |
121121-| `modal` | boolean | Frameless, closes on blur |
122122-| `key` | string | Unique ID for window reuse |
123123-| `keepLive` | boolean | Hide instead of close |
124124-| `escapeMode` | string | ESC behavior: `'close'`, `'navigate'`, `'auto'` |
125125-| `trackingSource` | string | Source for visit tracking |
210210+**CRITICAL - Do NOT run `xcodegen generate`:**
211211+- The Xcode project has custom settings that xcodegen overwrites
126212127127-### Escape Handling
213213+### Quick Start (One Command)
128214129129-Windows can control how ESC behaves using `escapeMode`:
215215+The easiest way to set up mobile development with sync testing:
130216131131-- **`'close'`** (default): ESC immediately closes/hides the window
132132-- **`'navigate'`**: Renderer handles ESC first; closes only when renderer signals root state
133133-- **`'auto'`**: Transient windows (opened via hotkey from another app) close immediately; active windows use navigate behavior
217217+```bash
218218+cd backend/tauri-mobile
134219135135-#### Using escapeMode: 'navigate'
220220+# First time: build both debug and release Rust libraries
221221+npm run build # Build frontend
222222+cd src-tauri
223223+cargo build --target aarch64-apple-ios-sim # Debug (simulator)
224224+cargo build --target aarch64-apple-ios --release # Release (device)
225225+cd ..
136226137137-1. Open window with `escapeMode: 'navigate'`:
138138-```javascript
139139-windows.createWindow(url, { escapeMode: 'navigate' });
227227+# Start everything (servers, configure iOS, seed test data, open Xcode)
228228+npm run dev:ios
140229```
141230142142-2. Register escape handler in renderer:
143143-```javascript
144144-api.escape.onEscape(() => {
145145- if (canNavigateBack) {
146146- navigateBack();
147147- return { handled: true }; // ESC was handled internally
148148- }
149149- return { handled: false }; // At root, let window close
150150-});
231231+`npm run dev:ios` does all of this automatically:
232232+1. Resets server data for a clean slate
233233+2. Starts backend server on a random port (10000-19999)
234234+3. Starts frontend dev server on port 1420
235235+4. Configures iOS simulator with server URL and API key
236236+5. Copies both debug and release Rust libraries
237237+6. Opens Xcode
238238+7. Seeds test data (3 items on server, 3 on iOS)
239239+240240+### Available npm Scripts
241241+242242+```bash
243243+# Development
244244+npm run dev:ios # Full dev setup (servers + config + seed + Xcode)
245245+npm run xcode # Just copy libraries and open Xcode
246246+npm run seed # Seed test data (needs SERVER_URL and API_KEY env vars)
247247+npm run reset:server # Delete server data directory
248248+249249+# Building
250250+npm run build # Build frontend (tsc + vite)
251251+npm run build:ios # Build frontend + debug Rust library
252252+npm run build:ios:release # Build frontend + release Rust library
253253+254254+# Testing
255255+npm run test # Run integration tests
256256+npm run test:verbose # Run tests with verbose output
151257```
152258153153-See `notes/escape-navigation.md` for full design details.
259259+### iOS Build Process (Manual)
154260155155-## Dock / App Switcher Visibility (macOS)
261261+If you need to build manually instead of using `npm run dev:ios`:
156262157157-The dock icon visibility is dynamic based on window state and user preference:
263263+```bash
264264+# 1. Build frontend
265265+cd backend/tauri-mobile && npm install && npm run build
158266159159-- **Windows visible**: Dock icon shown (regardless of preference)
160160-- **No windows visible**: Dock icon hidden (unless preference enabled)
267267+# 2. Build debug library (for simulator)
268268+cd src-tauri
269269+cargo tauri build --target aarch64-apple-ios-sim --debug
270270+mkdir -p gen/apple/Externals/arm64/Debug
271271+cp target/aarch64-apple-ios-sim/debug/deps/libpeek_save_lib.a gen/apple/Externals/arm64/Debug/libapp.a
161272162162-The `showInDockAndSwitcher` preference controls whether to *always* show the dock icon, even when no windows are open. When disabled (default), the dock icon only appears while Peek windows are visible.
273273+# 3. Build release library (for device)
274274+cargo tauri build --target aarch64-apple-ios
275275+mkdir -p gen/apple/Externals/arm64/Release
276276+cp target/aarch64-apple-ios/release/deps/libpeek_save_lib.a gen/apple/Externals/arm64/Release/libapp.a
163277164164-This is implemented via:
165165-- `getVisibleWindowCount()` - counts non-background visible windows
166166-- `updateDockVisibility()` - shows/hides dock based on window count + pref
167167-- Called from: window-open, window-show, maybeHideApp, prefs change
278278+# 4. Create assets symlink (if missing)
279279+ln -s ../../../dist gen/apple/assets
168280169169-## App Icon Generation
281281+# 5. Open Xcode and build from GUI
282282+open gen/apple/peek-save.xcodeproj
283283+```
170284171171-The macOS app icon is generated from a source PNG using ImageMagick. The process applies rounded corners and adds padding to match macOS icon guidelines.
285285+### Testing Bidirectional Sync
172286173173-### Icon files in `assets/`
287287+After running `npm run dev:ios`, test data is automatically seeded:
174288175175-- `appicon-source.png` - Original source image (1232x1232, no rounding)
176176-- `appicon-rounded.png` - Processed version with rounded corners and padding (1024x1024)
177177-- `appicon.icns` - Final macOS icon file used by electron-builder
289289+**Server has 3 items:**
290290+- URL: https://github.com/from-server (tags: server, github)
291291+- URL: https://example.com/from-server-1 (tags: server, test)
292292+- Text: "This is a text note from the server" (tags: server, note)
178293179179-### Regenerating the icon
294294+**iOS Simulator has 3 items:**
295295+- URL: https://mobile-only-1.example.com (tags: mobile, local)
296296+- URL: https://mobile-news.example.com (tags: local)
297297+- Text: "This text was created on mobile only" (tags: mobile)
180298181181-Requires ImageMagick (`brew install imagemagick`).
299299+**To test:**
300300+1. Build and run in Xcode (Cmd+R)
301301+2. Go to Settings in the app
302302+3. Settings should already be configured (server URL and API key)
303303+4. Tap **Sync All** to pull and push
304304+5. After sync, both server and iOS should have 6 items
182305306306+**Gotchas:**
307307+- The `gen/apple/assets` symlink must exist or Xcode fails with "No such file or directory"
308308+- Debug scheme = simulator, Release scheme = device
309309+- If Rust code changes, rebuild the library and copy again
310310+- The "Build Rust Code" pre-build script in Xcode can hang indefinitely - pre-building avoids this
311311+- iOS simulator can't reach `localhost` - use Mac's IP address (dev:ios handles this automatically)
312312+313313+## Server Backend (Webhook API)
314314+315315+The server backend (`backend/server/`) is a remote HTTP API for syncing data from the mobile app. It's separate from the desktop backends - it doesn't implement the Peek API, it's a standalone Node.js server.
316316+317317+### Server Commands
183318```bash
184184-SRC="assets/appicon-source.png"
185185-ROUNDED="assets/appicon-rounded.png"
319319+# From project root
320320+yarn server:install # Install dependencies (first time)
321321+yarn server:start # Run production server (port 3000)
322322+yarn server:dev # Run with hot reload
323323+yarn server:test # Run unit tests (92 tests)
324324+yarn server:healthcheck # Verify server starts and responds
186325187187-SIZE=1232
188188-RADIUS=222 # ~18% of size for rounded corners
326326+# From backend/server/
327327+npm install
328328+npm start
329329+npm run dev
330330+npm test
331331+npm run test:api:local # Test against local server (needs PEEK_LOCAL_KEY)
332332+npm run test:api:prod # Test against production (needs PEEK_PROD_KEY, PEEK_PROD_URL)
333333+```
189334190190-# Step 1: Apply rounded corners to source
191191-magick "$SRC" \
192192- \( +clone -alpha extract \
193193- -draw 'fill black polygon 0,0 0,'"$RADIUS $RADIUS"',0 fill white circle '"$RADIUS,$RADIUS $RADIUS"',0' \
194194- \( +clone -flip \) -compose Multiply -composite \
195195- \( +clone -flop \) -compose Multiply -composite \
196196- \) -alpha off -compose CopyOpacity -composite \
197197- /tmp/rounded-temp.png
335335+### Server Architecture
336336+- **Hono** - Lightweight HTTP framework (like Express but faster)
337337+- **better-sqlite3** - SQLite database (same as Electron backend)
338338+- **Multi-user** - Each user gets isolated database in `./data/{userId}/`
339339+- **API key auth** - Bearer token authentication, keys hashed with SHA-256
198340199199-# Step 2: Add padding (scale to 80% and center on 1024x1024 canvas)
200200-magick /tmp/rounded-temp.png \
201201- -resize 824x824 \
202202- -gravity center \
203203- -background transparent \
204204- -extent 1024x1024 \
205205- "$ROUNDED"
341341+### Server API Endpoints
342342+All endpoints except `/` require `Authorization: Bearer <api_key>` header.
206343207207-# Step 3: Generate iconset and convert to icns
208208-mkdir -p assets/AppIcon.iconset
209209-for size in 16 32 64 128 256 512 1024; do
210210- magick "$ROUNDED" -resize ${size}x${size} PNG32:"assets/AppIcon.iconset/icon_${size}x${size}.png"
211211-done
344344+| Endpoint | Method | Description |
345345+|----------|--------|-------------|
346346+| `/` | GET | Health check (public) |
347347+| `/webhook` | POST | Receive items from mobile (`{ urls, texts, tagsets }`) |
348348+| `/urls` | GET | List saved URLs |
349349+| `/urls/:id` | DELETE | Delete URL |
350350+| `/urls/:id/tags` | PATCH | Update URL tags |
351351+| `/texts` | GET/POST | List or create texts |
352352+| `/tagsets` | GET/POST | List or create tagsets |
353353+| `/images` | GET/POST | List or upload images |
354354+| `/images/:id` | GET/DELETE | Get or delete image |
355355+| `/items` | GET/POST | Unified endpoint (filter with `?type=`) |
356356+| `/tags` | GET | List tags by frecency |
212357213213-# Create @2x variants
214214-cp assets/AppIcon.iconset/icon_32x32.png assets/AppIcon.iconset/icon_16x16@2x.png
215215-cp assets/AppIcon.iconset/icon_64x64.png assets/AppIcon.iconset/icon_32x32@2x.png
216216-cp assets/AppIcon.iconset/icon_256x256.png assets/AppIcon.iconset/icon_128x128@2x.png
217217-cp assets/AppIcon.iconset/icon_512x512.png assets/AppIcon.iconset/icon_256x256@2x.png
218218-cp assets/AppIcon.iconset/icon_1024x1024.png assets/AppIcon.iconset/icon_512x512@2x.png
219219-rm assets/AppIcon.iconset/icon_64x64.png assets/AppIcon.iconset/icon_1024x1024.png
358358+### Server Deployment (Railway)
220359221221-iconutil -c icns assets/AppIcon.iconset -o assets/appicon.icns
222222-rm -rf assets/AppIcon.iconset /tmp/rounded-temp.png
360360+> **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.
361361+362362+The server is configured for Railway deployment.
363363+364364+**Initial Setup:**
365365+1. Connect Railway to `backend/server/` subdirectory
366366+2. Attach a persistent volume, set `DATA_DIR` env var to mount path
367367+3. Create users via the `users.js` module
368368+369369+**Deploying Updates:**
370370+```bash
371371+# Link to project (one-time, from backend/server/)
372372+railway link -p <project-name> -s <service-name> -e production
373373+374374+# Always run tests first
375375+npm test
376376+377377+# Deploy
378378+railway up -d
379379+380380+# Check logs
381381+railway logs -n 50
382382+383383+# Health check
384384+curl https://peek-node.up.railway.app/
223385```
224386225225-### Icon design notes
387387+**Deployment Order (Server + Mobile):**
388388+1. **Server first** - stateless, has auto-migrations that run on first request
389389+2. **Mobile second** - works offline, adapts to server changes
390390+3. One-way sync only: mobile → server (no pull/download sync yet)
226391227227-- Rounded corners at ~18% radius matches common macOS app icon style
228228-- 80% content size with 10% padding on each side matches Apple HIG
229229-- Source image should be square, ideally 1024x1024 or larger
392392+**Migration Gotcha:**
393393+When adding database columns via migration, ensure indexes on those columns are created AFTER the column migration runs, not in the initial CREATE TABLE statement.
230394231231-## Known Issues
395395+### Server Database Schema
396396+Different from desktop datastore - optimized for mobile sync:
397397+- `items` - Unified table (type: url/text/tagset/image)
398398+- `tags` - Tag names with frecency scoring
399399+- `item_tags` - Many-to-many junction
400400+- `settings` - Key-value config
232401233233-- **Tray icon in packaged builds**: Tray icon displays correctly in debug mode but not in packaged app builds. Works fine in dev.
402402+Images stored on disk in `./data/{userId}/images/` with content-hash deduplication.
+3-3
MULTI_AGENT_WORKFLOW.md
···433433434434### Development Resources
435435436436-- See `notes/development.md` for architecture, commands, and common pitfalls
437437-- See `docs/PEEK-API.md` for the Peek API reference
438438-- See `extensions/README.md` for extension development
436436+- See `DEVELOPMENT.md` for architecture, commands, and common pitfalls
437437+- See `docs/api.md` for the Peek API reference
438438+- See `docs/extensions.md` for extension development
439439440440---
441441
+49-379
README.md
···11# Peek
2233-Peek is a web user agent application designed for using the web where, when and how you want.
33+A web user agent for using the web where, when, and how you want.
4455-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.
55+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.
6677-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.
88-99-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.
77+**Status:** Concept preview. Not safe for daily use. No security audit.
108119<img width="969" alt="settings screenshot" src="settings-screenshot.png">
12101313-## Important notes
1414-1515-___PEEK IS NOT A WEB BROWSER___
1616-1717-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.
1818-1919-___PEEK IS A CONCEPT PREVIEW___
2020-2121-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.
2222-2311## Features
24122525-You can use Peek in a few ways, with more coming:
2626-2727-- Peeks - Keyboard-activated modal chromeless web pages for quickly glancing at or interacting with pages
2828-- Slides - Keyboard- or gesture-activated modal chromeless web pages which slide in from any screen edges
2929-- 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
3030-3131-In progress, or thinking about:
3232-3333-- Commands - a graphical command entry palette (GCLI) for opening pages or executing commands against them
3434-- Groups - a way to categorize, recall and interact with groups of pages
3535-- "native" web apps - using Peek as a way to "install" web pages on the local device, as separate applications instead of just separate processes
3636-- "Peeklets" HUD - select parts of pages to add to collection which are rendered as an overlay, toggled by shortcut
3737-3838-### Usage
3939-4040-- Settings
4141- * In app, `Cmd/Ctrl+r,` or launch app to open settings, or click tray icon
4242- * Configure Peeks/Slides/Scripts in settings
4343-- Peeks
4444- * `Opt+0-9` to open Peeks
4545-- Slides
4646- * `Opt+←→↑↓` to open Slides
4747-4848-### Peeks
4949-5050-Peeks are keyboard activated modal chromeless web pages mapped to `Opt+0-9` and closed on blur, the `Escape` key or `cmd/ctrl+w`.
5151-5252-### Slides
5353-5454-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`.
5555-5656-### Scripts
5757-5858-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.
5959-6060-Ok, so not really "scripts" yet. But safe and effective enough for now.
6161-6262-## Design
6363-6464-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.
6565-6666-Characteristics of how we use the web, that are not addressed in contemporary web browsers:
6767-6868-- transient
6969-- chained
7070-- persistent
7171-- data-centric
7272-- archival / evidential
7373-7474-Some thoughts driving the design of Peek:
7575-7676-- Web user agents should be bounded by the user, not browser vendor business models
7777-- 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
7878-- Security user interface must be a clear articulation of risks and trade-offs, and users should own the decisions
7979-8080-### Escape IZUI
8181-8282-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.
8383-8484-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.
8585-8686-IZUI vs ZUI
8787-8888-* ZUIs navigate by starting from a known root and user navigates by zooming ever further in, and then back out
8989-* Escape can enter a window stack at any point, and via a variety of methods, often from outside the application
9090-* Instead of navigating by zooming in, all interfaces can zoom out to go back, using the Escape key
9191-* This design allows unbounded and diverse entry points, but with predictable behavior
9292-* Regardless of the entry point, the user always has a consistent path to familiar ground
9393-9494-Escape navigation model
9595-* navigation base can start at any level in stack
9696-* forward navigations are added on top of stack
9797-* backwards navigations walk the stack in reverse up the tree to the root
9898-9999-## Architecture / Implementation
100100-101101-About this space:
102102-103103-- Web pages can themselves be navigators of the web
104104-- Embrace the app-ness of the web platform, as a way to efficiently access the document-ness
105105-- Decouple html+js+css from http+dns+ssl - not entirely, but that trust+security model is not a required starting point
106106-- Javascript is ok here
107107-108108-Peek is designed to be modular and configurable around the idea that parts of it
109109-can run in different environments.
110110-111111-For example:
112112-- Planning on a mobile app which syncs and runs your peeks/slides/scripts
113113-- I'd like to have a decentralized compute option for running your scripts outside of your clients and syncing the data
114114-- Want cloud storage for all config and data, esp infinite history, so can do fun things with it
115115-116116-### Feature extensibility
117117-118118-An extensibility model for achieving "personal web workbench" requires a few things:
119119-- UI extensibility requires OS-level window features beyond what the web allows today (also a baby step towards a minimal OS user interface)
120120-- 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
121121-- Portable ways of accessing network, storage and compute, which address
122122-123123-The current implementation has only a few sketches of that world implemented, and has gone through a few iterations:
124124-- first proof of concept was all Electron - so, privileged JS
125125-- second experiment moved each feature to a separate web app running in own window scope, with access to smallest possible custom API, with one main web app loading and orchestrating the others, using pubsub for cross-app communication
126126-- third and current implementation bundles all features into one web app, with access to smallest possible custom API for platform-level capabilities
127127-128128-The web app is loaded into custom scheme of `peek`, which provides access to a few special apis noted in the next section, allows cross-origin network access and other things.
129129-130130-This is not ideal, as the extensibility vector is contributions to core, which too tightly bounds experimentation and innovation.
131131-132132-However it's pretty portable given the small custom API surface area.
133133-134134-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.
135135-136136-### Peek API
137137-138138-Initially the prototype was all Electron. But that's not interesting, and doesn't
139139-really tell us anything about constraints of the web itself.
140140-141141-So instead I asked this question: What's the minimum capability set that a web app would
142142-need to build the features I need?
1313+- **Peeks** - Keyboard-activated modal web pages (`Opt+0-9`)
1414+- **Slides** - Gesture-activated pages that slide in from screen edges (`Opt+arrows`)
1515+- **Scripts** - Background page monitors that extract and track data
1616+- **Commands** - Command palette for opening pages and executing actions
1717+- **Groups** - Tag-based page organization (like Firefox Panorama)
1818+- **Sync** - Cross-device sync between desktop, mobile, and server
14319144144-The answer, so far, is giving `peek` apps the following APIs:
2020+## Quick Start
14521146146-- window open/close
147147-- global hotkey registration
148148-- pubsub messaging
2222+```bash
2323+# Requirements: Node.js 24+
2424+nvm use 24
14925150150-Custom window api might be able to away entirely, by passing window.open features, working on that.
151151-152152-### Desktop App
153153-154154-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).
155155-156156-User interface:
157157-- the built-in features are all modal chromeless web pages at this point
158158-- settings UI uses custom sidebar navigation with dark mode support
159159-160160-TODO
161161-- Need to look at whether could library-ize some of what Agregore implemented for non-HTTP protocol support.
162162-- 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.
163163-164164-## Contribution
165165-166166-- in proto stage
167167-- all dragons, no promises
168168-169169-## Development
170170-171171-```
2626+# Install and run
17227yarn install
173173-yarn debug
2828+yarn debug # Development mode with devtools
2929+yarn start # Normal mode
17430```
17531176176-### Mobile
177177-178178-- Quick access to Script output and manual runs, as widgets (or output from cloud runners?)
179179-- Peeks still totes useful here - on mobile is more like "quick dial" features
180180-181181-### Cloud
182182-183183-- Going full crypto payments for distributed compute on this one.
184184-185185-## Papercut / use-case log
186186-187187-Core high level actions
188188-- open a web page on top/bottom/left/right
189189-- keep web pages persistent in the background
190190-- quickly open a web page modally, and close it
191191-192192-Misc specific
193193-- open bandcamp in a window, move over to 2nd display, accidently close it while moving around between other windows
194194-- recent books or recipes from newsletters i subscribe to (but probably didn't read)
195195-- extract a table from a page periodically, send it somewhere as csv or whatever (chained actions)
196196-- collect microformats, metadata, events
197197-- web page w/ some locations as an input to a map (creates overlay) "map this page"
198198-- be able to see where a book/etc recommendation came from
199199-- save a tweet, with URL / image / relevant text, but not whole page webrecorder style
200200-- "watch local event listings, rate against my music listening patterns and send me shows i might be interested in going to"
201201-202202-Content scripts
203203-- extract+log shazams
204204-- extract+log spotify playlist
205205-206206-Calculators (variant of script + cmd?)
207207-- page -> table
208208-- page -> summary
209209-- page -> microsummaries
210210-- page -> dates
211211-- page -> events
212212-213213-Workflow deconstructing a "why" task flavour of bookmarking
214214-- save https://www.criterionchannel.com/hong-kong-in-new-york
215215-- extract the movies
216216-- get reference metadata for each (?!)
217217-- add to "to watch list", with pointer back to source url
218218-219219-## Groups
220220-221221-- panorama/tabcandy-ish
222222-- all browser history
223223-- smart groups vs curated groups
224224-- autoclustering on topic/date
225225-- escape from a new page enters default group?
226226-227227-Groups + Cmds
228228-- top/bottom inputs for filtering/grouping/etc
229229-- implemented is a cmd input?
230230-- cmds for opening/searching/finding/viewing/filtering/piping
231231-- cmds for moving pages into groups
232232-- groups -> {x} (eg export/pipe) could depend on the chaining/piping bit (see below)
233233-234234-Architecture
235235-- internally is tags?
236236-- static vs dynamic groups tho?
237237-238238-Publishing
239239-- publishing groups as internal/public feeds?
240240-- to pinboard?
241241-242242-## History view/search
243243-244244-A lot of groups work depends on history being in place, and being accessable and annotate-able.
245245-246246-ideally use chromium history
247247-248248-storage+access
249249-- check out Agregore history viewing approach
250250-- check out state of electron+webext
251251-- other way of accessing underlying chromium history?
252252-253253-features
254254-- awesomebar algo scoring
255255-- adaptive matching
256256-257257-## Chaining / piping
258258-259259-investigate: vague thought re chaining:
260260-- dynamic interstitial representations
261261-- mime type detection?
262262-- eg image previews
263263-- or a table of data
264264-- previews of cmds?
265265-266266-interfaces
267267-- horizontal vs vertical chains
268268-- back/forward navigation?
269269-- each step is a cmd+preview?
270270-- dynamic cmd+previews?
271271-272272-import/export/undo/redo
273273-- record/replay?
274274-- save a chain as a compound action (cmd)?
275275-276276-architecture
277277-- look at web actions/intents/applets
278278-- xml pipeline language
279279-280280-## Mobile
281281-282282-- some of the features don't make sense as-is on mobile
283283-- but maybe quick access on mobile to slides/peeks would be nice
284284-- and seeing output of content scripts, or ability to re-run locally on demand
285285-- needs some sync facility (inevitable anyway)
286286-287287-## Use-cases
288288-289289-Peeks
290290-- translate
291291-- calendar
292292-- ai chat
293293-- currency conversion
294294-- everytimezone
295295-- tldraw
296296-297297-Slides
298298-- music: Soundcloud, Hypem
299299-- stock prices
300300-- notepad
301301-- todo list
302302-303303-Scripts
304304-- weather change, eg upcoming weather
305305-- crypto prices
306306-307307-Cmd - web
308308-- open url
309309-- web search
310310-- image search
311311-- conversions?
312312-- ddg !actions
313313-314314-Cmd - system
315315-- search browser history
316316-- set peeks/slides
317317-- open settings
318318-- restart app
319319-- llm prompts
320320-321321-Future
322322-- address something to switch between
323323-- pipe from/to?
3232+See [DEVELOPMENT.md](DEVELOPMENT.md) for full development guide.
32433325325-Publishing high level
326326-- author web content
327327-- pull in bits from the web
328328-- share preview for feedback
329329-- publish (or at least get output)
3434+## Architecture
33035331331-Publishing examples
332332-- writing an event recap
3636+Peek supports multiple backends sharing the same renderer code:
33337334334-Publishing: event recap post
335335-- make a new markdown doc
336336-- sections titled for each video title
337337-- each video's embed code in each section
338338-- navigate around the document for review and updates
339339-- need to easily preview rendered content
340340-- share preview link
341341-- publish (somewhere?)
3838+```
3939+peek/
4040+├── app/ # Renderer (backend-agnostic)
4141+├── extensions/ # Built-in extensions
4242+├── backend/
4343+│ ├── electron/ # Desktop (primary)
4444+│ ├── tauri/ # Desktop (Rust alternative)
4545+│ ├── tauri-mobile/ # iOS/Android
4646+│ └── server/ # Sync server (Node.js/Hono)
4747+└── docs/ # Documentation
4848+```
34249343343-Music
344344-- commands
345345-- views
346346-- last.fm of my own, to POSSE out
5050+## Documentation
34751348348-## Unfiled
5252+| Doc | Description |
5353+|-----|-------------|
5454+| [DEVELOPMENT.md](DEVELOPMENT.md) | Development setup, commands, architecture |
5555+| [docs/api.md](docs/api.md) | Peek API reference (`window.app`) |
5656+| [docs/extensions.md](docs/extensions.md) | Extension development |
5757+| [docs/datastore.md](docs/datastore.md) | Data storage and schema |
5858+| [docs/sync.md](docs/sync.md) | Cross-device sync |
5959+| [docs/MOBILE.md](docs/MOBILE.md) | Mobile development |
34960350350-markdown hot reload previewer w/ toc
351351-- markdown support, with sidebar nav
352352-- reader mode
353353-- hot reload for file:// (other?)
354354-- add side-by-side view
355355-- once md and side-by-side, add side-by-side so the md is the nav, content is the preview
356356-- what's the cmd chain for this?
6161+## Design Philosophy
35762358358-content types + chaining
359359-- cmd: view as… table, feed, markdown, data points, named entities
360360-- chain: static archive, publish, save, share (os), mailto
361361-- cmd params, eg {url}, which can themselves autocomplete (eg history)
6363+- Web pages can be navigators of the web
6464+- User tasks on the web are transient, chained, persistent, or data-oriented - none well-served by tabbed browsers
6565+- The "Escape IZUI" pattern: enter at any point, ESC always returns to familiar ground
6666+- Minimum viable API surface for web apps to access platform capabilities
36267363363-multiprotocol
364364-- at
365365-- ipfs/ipns
366366-- pragmatic addressing+rendering for data (r/d/masl + mime handlers)
6868+See [notes/extensibility.md](notes/extensibility.md) for detailed design notes.
36769368368-broader patterns (chatting w/ luke)
369369-- why do we have to copy/paste?
370370-- devtools and ide are divorced
7070+## Contributing
37171372372-chainframe/framechain
373373-- (web intents/applets/actions) + (webxdc/miniapps/tiles/farcasterframes)
7272+Concept stage - contributions welcome but expect dragons.
37473375375-Small examples of agency
7474+## License
37675377377-- users can move, resize, change things to their requirements
378378- - eg, browsers restrict min-height of a window, but i should be able make as short as i like
379379-380380-## History
381381-382382-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:
383383-384384-- The Awesomebar: infinite history + personalized local search index
385385-- Ubiquity: Natural language commands + chaining
386386-- Jetpack: The Mozilla Labs version - web-platfrom-centric extensibility
387387-- Panorama: née TabCandy, web pages as groups instead of tabs in windows
388388-389389-A few others which were in the right direction but didn't achieve their optimal form:
390390-391391-- Greasemonkey
392392-- Microsummaries
393393-- Contacts extension
394394-395395-The first version of the Peek application has some bits of each of these, and the original Peek browser extension.
396396-397397-### Peek browser extension
398398-399399-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.
400400-401401-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/).
402402-403403-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.
404404-405405-406406-7676+MIT
+3-3
backend/README.md
···55## Design Principles
66771. **Backend Abstraction**: The `app/` directory contains all renderer code and must work unchanged with any backend
88-2. **Shared API Contract**: All backends expose the same Peek API (`window.app`) - see `docs/PEEK-API.md`
88+2. **Shared API Contract**: All backends expose the same Peek API (`window.app`) - see `docs/api.md`
993. **Shared Data**: Backends use the same SQLite schema and can share database files
10104. **Profile Isolation**: Data is separated by profile (dev, default, etc.)
1111···7272Database location: `{app_data}/{profile}/datastore.sqlite`
73737474### 4. Peek API Injection
7575-Inject the `window.app` API before any page scripts run. See `docs/PEEK-API.md` for the complete API reference.
7575+Inject the `window.app` API before any page scripts run. See `docs/api.md` for the complete API reference.
76767777## API Contract
7878···1851852. Implement custom protocol handler for `peek://`
1861863. Implement SQLite datastore with shared schema
1871874. Implement window management commands
188188-5. Implement the Peek API (`window.app`) matching `docs/PEEK-API.md`
188188+5. Implement the Peek API (`window.app`) matching `docs/api.md`
1891896. Inject the API before page scripts run
190190191191The renderer code (`app/`) should work without modification.
docs/Peek-API.md
docs/api.md
+180
docs/datastore.md
···11+# Peek Datastore
22+33+The Peek Personal Datastore stores addresses, navigation history, tags, notes, and other user data. It uses SQLite with a unified schema across all backends (Electron, Tauri, Server).
44+55+## Architecture
66+77+All data operations are handled in the main/backend process, with renderer processes accessing via IPC through `window.app.datastore`.
88+99+```
1010+Renderer Process Main/Backend Process
1111+┌─────────────────┐ ┌─────────────────────┐
1212+│ window.app. │ IPC invoke() │ IPC Handlers │
1313+│ datastore.* │───────────────▶│ ├─ add-item │
1414+│ │ │ ├─ query-items │
1515+│ │ │ ├─ add-visit │
1616+│ │ │ └─ ... │
1717+└─────────────────┘ │ │ │
1818+ │ ▼ │
1919+ │ ┌─────────────┐ │
2020+ │ │ SQLite │ │
2121+ │ │ Database │ │
2222+ │ └─────────────┘ │
2323+ └─────────────────────┘
2424+```
2525+2626+### Why IPC-Based?
2727+2828+1. **Backend Portability** - Same renderer code works with Electron, Tauri, or mobile
2929+2. **Storage Flexibility** - Can swap SQLite for other backends without renderer changes
3030+3. **Security** - Datastore logic in trusted main process
3131+4. **Sync Readiness** - Same API can route to local or remote storage
3232+3333+## Database Schema
3434+3535+Location: `{app_data}/{profile}/datastore.sqlite`
3636+3737+### Core Tables
3838+3939+#### `items` - Unified Item Storage
4040+Stores all user content types: URLs, text notes, tagsets, images.
4141+4242+| Column | Type | Description |
4343+|--------|------|-------------|
4444+| id | TEXT | Primary key (UUID) |
4545+| type | TEXT | `url`, `text`, `tagset`, `image` |
4646+| content | TEXT | The actual content (URL, note text, etc.) |
4747+| title | TEXT | Display title |
4848+| metadata | TEXT | JSON for flexible extra data |
4949+| createdAt | TEXT | ISO timestamp |
5050+| updatedAt | TEXT | ISO timestamp |
5151+| syncedAt | TEXT | Last sync timestamp |
5252+| sync_id | TEXT | Server-assigned ID for sync |
5353+5454+#### `visits` - Navigation History
5555+Tracks page visits with timing and context.
5656+5757+| Column | Type | Description |
5858+|--------|------|-------------|
5959+| id | TEXT | Primary key |
6060+| addressId | TEXT | FK to items |
6161+| timestamp | INTEGER | Unix timestamp (ms) |
6262+| duration | INTEGER | Time spent (ms) |
6363+| source | TEXT | `peek`, `slide`, `direct`, `link` |
6464+| windowType | TEXT | `modal`, `persistent`, `main` |
6565+6666+#### `tags` - Tag Definitions
6767+| Column | Type | Description |
6868+|--------|------|-------------|
6969+| id | TEXT | Primary key |
7070+| name | TEXT | Tag name (unique) |
7171+| frecency | REAL | Usage frequency score |
7272+7373+#### `item_tags` - Tag Associations
7474+| Column | Type | Description |
7575+|--------|------|-------------|
7676+| item_id | TEXT | FK to items |
7777+| tag_id | TEXT | FK to tags |
7878+7979+#### `extension_settings` - Extension Config
8080+Key-value storage for extension and core settings.
8181+8282+| Column | Type | Description |
8383+|--------|------|-------------|
8484+| key | TEXT | Setting key (e.g., `sync.serverUrl`) |
8585+| value | TEXT | JSON-encoded value |
8686+8787+## API Reference
8888+8989+Access via `window.app.datastore` in any `peek://` page.
9090+9191+### Items
9292+9393+```javascript
9494+// Add item
9595+const result = await api.datastore.addItem({
9696+ type: 'url',
9797+ content: 'https://example.com',
9898+ title: 'Example',
9999+ tags: ['bookmark', 'work']
100100+});
101101+102102+// Query items
103103+const urls = await api.datastore.queryItems({ type: 'url' });
104104+const tagged = await api.datastore.queryItems({ tag: 'bookmark' });
105105+106106+// Update item
107107+await api.datastore.updateItem(id, { title: 'New Title' });
108108+109109+// Delete item
110110+await api.datastore.deleteItem(id);
111111+```
112112+113113+### Tags
114114+115115+```javascript
116116+// Get or create tag
117117+const tag = await api.datastore.getOrCreateTag('bookmark');
118118+119119+// Tag an item
120120+await api.datastore.tagItem(itemId, tagId);
121121+122122+// Untag
123123+await api.datastore.untagItem(itemId, tagId);
124124+125125+// Get item's tags
126126+const tags = await api.datastore.getItemTags(itemId);
127127+128128+// Get items by tag
129129+const items = await api.datastore.getItemsByTag(tagId);
130130+```
131131+132132+### Visits
133133+134134+```javascript
135135+// Record visit
136136+await api.datastore.addVisit(addressId, {
137137+ source: 'peek',
138138+ windowType: 'modal'
139139+});
140140+141141+// Query visits
142142+const history = await api.datastore.queryVisits({
143143+ limit: 100,
144144+ offset: 0
145145+});
146146+```
147147+148148+### Settings
149149+150150+```javascript
151151+// Get setting
152152+const value = await api.datastore.getSetting('sync.serverUrl');
153153+154154+// Set setting
155155+await api.datastore.setSetting('sync.serverUrl', 'https://...');
156156+```
157157+158158+### Stats
159159+160160+```javascript
161161+const stats = await api.datastore.getStats();
162162+// Returns: { items: 150, visits: 1200, tags: 25 }
163163+```
164164+165165+## Sync
166166+167167+The datastore syncs between backends via the server API:
168168+169169+1. **Desktop ↔ Server**: Bidirectional sync in `backend/electron/sync.ts`
170170+2. **Mobile → Server**: Push sync from mobile app
171171+3. **Conflict Resolution**: Last-write-wins based on `updatedAt`
172172+173173+See `docs/sync.md` for sync architecture details.
174174+175175+## Files
176176+177177+- `backend/electron/datastore.ts` - Electron SQLite implementation
178178+- `backend/tauri/src-tauri/src/datastore.rs` - Tauri SQLite implementation
179179+- `backend/server/db.js` - Server SQLite implementation
180180+- `app/datastore/` - Shared schema and helpers
+347
docs/extensions.md
···11+# Peek Extensions
22+33+Extensions are isolated modules that communicate with the core app via IPC and pubsub messaging.
44+55+## Hybrid Extension Architecture
66+77+Peek uses a **hybrid extension loading model** that balances memory efficiency with crash isolation:
88+99+### Built-in Extensions (Consolidated)
1010+Built-in extensions (`cmd`, `groups`, `peeks`, `slides`) run as **iframes in a single extension host window**:
1111+- Share a single Electron BrowserWindow process
1212+- Memory efficient (~80-120MB vs ~200-400MB for separate windows)
1313+- Origin isolation via unique URL hosts (`peek://cmd/`, `peek://groups/`, etc.)
1414+- If one crashes, others in the same host are affected
1515+1616+### External Extensions (Separate Windows)
1717+External extensions (including `example` and user-installed) run in **separate BrowserWindows**:
1818+- Each has its own Electron process
1919+- Crash isolation - one extension crashing doesn't affect others
2020+- Uses `peek://ext/{id}/` URL scheme
2121+- Better for untrusted or experimental extensions
2222+2323+```
2424+┌─────────────────────────────────────────────────────────────┐
2525+│ Extension Host Window (peek://app/extension-host.html) │
2626+│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────┐ │
2727+│ │ <iframe> │ │ <iframe> │ │ <iframe> │ │<iframe> │ │
2828+│ │ peek://cmd/ │ │peek://groups│ │peek://peeks/│ │peek:// │ │
2929+│ │ │ │ │ │ │ │slides/ │ │
3030+│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────┘ │
3131+└─────────────────────────────────────────────────────────────┘
3232+3333+┌─────────────────┐ ┌─────────────────┐
3434+│ BrowserWindow │ │ BrowserWindow │
3535+│ peek://ext/ │ │ peek://ext/ │
3636+│ example/ │ │ user-ext/ │
3737+│ (separate proc) │ │ (separate proc) │
3838+└─────────────────┘ └─────────────────┘
3939+```
4040+4141+### Origin Isolation
4242+4343+Each extension gets a unique origin regardless of loading mode:
4444+- Built-in: `peek://cmd/background.html` → origin `peek://cmd`
4545+- External: `peek://ext/example/background.html` → origin `peek://ext`
4646+4747+This prevents cross-extension access to localStorage, DOM, and globals.
4848+4949+## Extension Structure
5050+5151+Each extension lives in its own directory under `extensions/`:
5252+5353+```
5454+extensions/
5555+ example/
5656+ manifest.json # Extension metadata
5757+ settings-schema.json # Settings UI schema (optional)
5858+ background.html # Entry point (loads background.js)
5959+ background.js # Main extension logic
6060+ *.html, *.js, *.css # Additional UI files
6161+```
6262+6363+### manifest.json
6464+6565+Required fields:
6666+```json
6767+{
6868+ "id": "example",
6969+ "shortname": "example",
7070+ "name": "Example Extension",
7171+ "description": "What this extension does",
7272+ "version": "1.0.0",
7373+ "background": "background.html"
7474+}
7575+```
7676+7777+Optional fields:
7878+```json
7979+{
8080+ "builtin": true,
8181+ "settingsSchema": "./settings-schema.json"
8282+}
8383+```
8484+8585+### settings-schema.json
8686+8787+Defines the settings UI for the extension. Used by Settings to render configuration forms.
8888+8989+```json
9090+{
9191+ "prefs": {
9292+ "type": "object",
9393+ "properties": {
9494+ "greeting": {
9595+ "type": "string",
9696+ "description": "Custom greeting message",
9797+ "default": "Hello World"
9898+ }
9999+ }
100100+ },
101101+ "storageKeys": {
102102+ "PREFS": "prefs"
103103+ },
104104+ "defaults": {
105105+ "prefs": {
106106+ "greeting": "Hello World"
107107+ }
108108+ }
109109+}
110110+```
111111+112112+For extensions with list-based settings (like peeks/slides), add an `item` schema:
113113+```json
114114+{
115115+ "prefs": { ... },
116116+ "item": {
117117+ "type": "object",
118118+ "properties": {
119119+ "title": { "type": "string", "title": "Title" },
120120+ "enabled": { "type": "boolean", "title": "Enabled" }
121121+ }
122122+ },
123123+ "storageKeys": {
124124+ "PREFS": "prefs",
125125+ "ITEMS": "items"
126126+ },
127127+ "defaults": {
128128+ "prefs": { ... },
129129+ "items": []
130130+ }
131131+}
132132+```
133133+134134+### background.html
135135+136136+Entry point that loads the extension as an ES module:
137137+138138+```html
139139+<!DOCTYPE html>
140140+<html>
141141+<head>
142142+ <meta charset="UTF-8">
143143+ <title>My Extension</title>
144144+</head>
145145+<body>
146146+<script type="module">
147147+ import extension from './background.js';
148148+149149+ const api = window.app;
150150+ const extId = extension.id;
151151+152152+ console.log(`[ext:${extId}] background.html loaded`);
153153+154154+ // Signal ready to main process
155155+ api.publish('ext:ready', {
156156+ id: extId,
157157+ manifest: {
158158+ id: extension.id,
159159+ labels: extension.labels,
160160+ version: '1.0.0'
161161+ }
162162+ }, api.scopes.SYSTEM);
163163+164164+ // Initialize extension
165165+ if (extension.init) {
166166+ console.log(`[ext:${extId}] calling init()`);
167167+ extension.init();
168168+ }
169169+170170+ // Handle shutdown
171171+ api.subscribe('app:shutdown', () => {
172172+ if (extension.uninit) extension.uninit();
173173+ }, api.scopes.SYSTEM);
174174+175175+ api.subscribe(`ext:${extId}:shutdown`, () => {
176176+ if (extension.uninit) extension.uninit();
177177+ }, api.scopes.SYSTEM);
178178+</script>
179179+</body>
180180+</html>
181181+```
182182+183183+### background.js
184184+185185+Main extension logic as an ES module:
186186+187187+```javascript
188188+const api = window.app;
189189+190190+const extension = {
191191+ id: 'example',
192192+ labels: {
193193+ name: 'Example'
194194+ },
195195+196196+ init() {
197197+ console.log('[example] init');
198198+199199+ // Register commands
200200+ api.commands.register({
201201+ name: 'my-command',
202202+ description: 'Does something',
203203+ execute: () => {
204204+ console.log('Command executed!');
205205+ }
206206+ });
207207+208208+ // Register shortcuts
209209+ api.shortcuts.register('Option+x', () => {
210210+ console.log('Shortcut triggered!');
211211+ });
212212+213213+ // Subscribe to events
214214+ api.subscribe('some:event', (msg) => {
215215+ console.log('Event received:', msg);
216216+ }, api.scopes.GLOBAL);
217217+ },
218218+219219+ uninit() {
220220+ console.log('[example] uninit');
221221+ api.commands.unregister('my-command');
222222+ api.shortcuts.unregister('Option+x');
223223+ }
224224+};
225225+226226+export default extension;
227227+```
228228+229229+## Extension API
230230+231231+Extensions access the Peek API via `window.app`. See `docs/api.md` for the complete reference.
232232+233233+Common APIs used by extensions:
234234+235235+### Commands
236236+```javascript
237237+api.commands.register({ name, description, execute })
238238+api.commands.unregister(name)
239239+```
240240+241241+### Shortcuts
242242+```javascript
243243+api.shortcuts.register(shortcut, callback) // e.g., 'Option+1'
244244+api.shortcuts.unregister(shortcut)
245245+```
246246+247247+### Pubsub Messaging
248248+```javascript
249249+api.publish(topic, data, scope)
250250+api.subscribe(topic, callback, scope)
251251+252252+// Scopes
253253+api.scopes.SELF // Only this window
254254+api.scopes.SYSTEM // System-level events
255255+api.scopes.GLOBAL // All windows
256256+```
257257+258258+### Windows
259259+```javascript
260260+api.window.open(url, options)
261261+// Options: modal, keepLive, transparent, height, width, key
262262+```
263263+264264+### Datastore
265265+```javascript
266266+await api.datastore.getRow(table, id)
267267+await api.datastore.setRow(table, id, data)
268268+await api.datastore.deleteRow(table, id)
269269+await api.datastore.getTable(table)
270270+```
271271+272272+### Extension Settings
273273+```javascript
274274+await api.extensions.getSettings(extId)
275275+await api.extensions.setSettings(extId, key, value)
276276+```
277277+278278+## Extension Loading
279279+280280+### Load Order and the cmd Extension
281281+282282+The `cmd` extension is the command registry - all other extensions register their commands with it via `api.commands.register()`. Because of this dependency:
283283+284284+1. **cmd loads first** (sequential) - must be ready before other extensions register commands
285285+2. **Other extensions load in parallel** - for faster startup
286286+3. **cmd cannot be disabled** - it's required infrastructure, not optional functionality
287287+288288+This is enforced in `isBuiltinExtensionEnabled()` which always returns `true` for cmd.
289289+290290+### Hybrid Loading Process
291291+292292+Extensions are loaded in hybrid mode by `loadExtensions()` in `backend/electron/main.ts`:
293293+294294+1. **Create extension host window** - Single BrowserWindow at `peek://app/extension-host.html`
295295+2. **Load built-in extensions as iframes** - `cmd`, `groups`, `peeks`, `slides` loaded via IPC into the host
296296+3. **Load external extensions as separate windows** - Each gets its own BrowserWindow
297297+298298+```typescript
299299+// Which extensions use consolidated mode (defined in main.ts)
300300+const CONSOLIDATED_EXTENSION_IDS = ['cmd', 'groups', 'peeks', 'slides'];
301301+```
302302+303303+### Built-in Extensions
304304+305305+Built-in extensions are registered in `index.js`:
306306+```javascript
307307+registerExtensionPath('example', path.join(__dirname, 'extensions', 'example'));
308308+```
309309+310310+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.
311311+312312+### External Extensions
313313+314314+External extensions are:
315315+1. Added via Settings UI (stored in datastore `extensions` table)
316316+2. Loaded on startup if `enabled === 1` and have a valid `path`
317317+3. Always run in separate BrowserWindows for crash isolation
318318+319319+## Settings Integration
320320+321321+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.
322322+323323+Settings are stored in the `extension_settings` datastore table with:
324324+- `extensionId`: The extension's ID
325325+- `key`: Setting key (e.g., 'prefs', 'items')
326326+- `value`: JSON-encoded setting value
327327+328328+Extensions can listen for settings changes:
329329+```javascript
330330+api.subscribe(`${extId}:settings-changed`, (msg) => {
331331+ // Reload configuration
332332+}, api.scopes.GLOBAL);
333333+```
334334+335335+## Lifecycle Events
336336+337337+- `ext:ready` - Published when extension is initialized
338338+- `ext:all-loaded` - Published when all extensions finish loading
339339+- `app:shutdown` - Sent before app closes
340340+- `ext:{id}:shutdown` - Sent when specific extension is being unloaded
341341+- `{extId}:settings-changed` - Sent when extension settings are modified
342342+343343+## Debugging
344344+345345+Console logs from extensions are forwarded to stdout with prefix `[ext:{id}]`.
346346+347347+Run with `DEBUG=1 yarn start` for verbose logging.
+115
docs/sync.md
···11+# Peek Sync
22+33+Cross-platform data synchronization between mobile, desktop, and server.
44+55+## Architecture
66+77+```
88+┌─────────────┐ ┌─────────────┐ ┌─────────────┐
99+│ Mobile │────▶│ Server │◀────│ Desktop │
1010+│ (Tauri) │ │ (Hono) │ │ (Electron) │
1111+└─────────────┘ └─────────────┘ └─────────────┘
1212+ │ │ │
1313+ ▼ ▼ ▼
1414+ SQLite SQLite SQLite
1515+```
1616+1717+The server acts as the source of truth. All clients push and pull data via the server's REST API.
1818+1919+## Item Types
2020+2121+All platforms use the same unified types:
2222+2323+| Type | Description | Content Field |
2424+|------|-------------|---------------|
2525+| `url` | Saved URLs/bookmarks | The URL string |
2626+| `text` | Text content/notes | The text content |
2727+| `tagset` | Tag-only items | null |
2828+| `image` | Binary images | Base64 data or filename |
2929+3030+## Sync Protocol
3131+3232+### Pull (Server → Client)
3333+3434+1. Fetch: `GET /items` (full) or `GET /items/since/:timestamp` (incremental)
3535+2. For each server item:
3636+ - Find local item by `syncId` matching server `id`
3737+ - If not found: insert new item
3838+ - If found and server is newer: update local
3939+ - If local is newer: skip (will push later)
4040+4141+### Push (Client → Server)
4242+4343+1. Query items where `syncSource = ''` OR `updatedAt > lastSyncTime`
4444+2. For each item: `POST /items` with type, content, tags
4545+3. On success: update local `syncId` and `syncSource`
4646+4747+### Conflict Resolution
4848+4949+**Last-write-wins** based on `updatedAt` timestamp.
5050+5151+## API Endpoints
5252+5353+### Server
5454+5555+```
5656+GET /items # All items
5757+GET /items/since/:timestamp # Items modified after timestamp
5858+GET /items/:id # Single item
5959+POST /items # Create item
6060+PATCH /items/:id/tags # Update tags
6161+DELETE /items/:id # Delete item
6262+```
6363+6464+### Desktop IPC
6565+6666+```javascript
6767+await window.app.sync.getConfig() // Get server URL, API key
6868+await window.app.sync.setConfig(cfg) // Save config
6969+await window.app.sync.pull() // Pull from server
7070+await window.app.sync.push() // Push to server
7171+await window.app.sync.full() // Full bidirectional sync
7272+```
7373+7474+## Configuration
7575+7676+### Desktop
7777+7878+Sync settings stored in `extension_settings` table:
7979+- `sync.serverUrl` - Server URL
8080+- `sync.apiKey` - API key
8181+- `sync.lastSyncTime` - Last sync timestamp
8282+- `sync.autoSync` - Enable auto-sync
8383+8484+### Mobile
8585+8686+Settings keys in Tauri:
8787+- `webhook_url` - Server URL
8888+- `webhook_api_key` - API key
8989+9090+## Known Limitations
9191+9292+### Deletes Not Synced (HIGH)
9393+9494+Deleted items are local-only. Items may "resurrect" on other devices after sync.
9595+9696+**Current behavior:**
9797+1. Delete on desktop sets `deletedAt` locally
9898+2. Deleted items excluded from push query
9999+3. Server never learns about deletion
100100+4. Other devices still see the item
101101+102102+**Workaround:** Delete on all devices manually.
103103+104104+### Push Failures Not Retried (HIGH)
105105+106106+Failed push operations are logged but not retried. After sync, `lastSyncTime` advances, so failed items won't be picked up on next sync.
107107+108108+**Workaround:** Manually trigger sync if network issues occurred.
109109+110110+## Files
111111+112112+- `backend/electron/sync.ts` - Desktop sync implementation
113113+- `backend/server/db.js` - Server database with sync support
114114+- `backend/tauri-mobile/src-tauri/src/commands/sync.rs` - Mobile sync
115115+- `notes/sync-edge-cases.md` - Detailed edge case documentation
+1-1
extensions/README.md
···228228229229## Extension API
230230231231-Extensions access the Peek API via `window.app`. See `docs/PEEK-API.md` for the complete reference.
231231+Extensions access the Peek API via `window.app`. See `docs/api.md` for the complete reference.
232232233233Common APIs used by extensions:
234234
···11-# Peek Development Guide
22-33-## Project Overview
44-55-Peek is a web user agent application that provides alternative ways to interact with web pages through keyboard shortcuts, modal windows, and background scripts. It's designed as a concept preview exploring task-aligned interfaces for the web, moving beyond traditional tabbed browsers.
66-77-**Multi-Backend Architecture**: Peek supports multiple backends (Electron, Tauri) that can coexist and sync data. The `app/` directory is **backend-agnostic** and must not contain backend-specific code. Backend-specific code lives in `backend/{electron,tauri}/`.
88-99-## Requirements
1010-1111-- **Node.js 24+** - Required for both desktop (Electron 40) and server
1212-- Install via `nvm install 24` or download from nodejs.org
1313-1414-## Key Commands
1515-1616-### Development
1717-```bash
1818-nvm use 24 # Ensure Node 24 is active
1919-yarn install # Install dependencies
2020-yarn debug # Run in development mode (with devtools)
2121-yarn start # Start normally
2222-yarn package # Package (output: out/mac-arm64/)
2323-yarn package:install # Package and install to /Applications (macOS)
2424-yarn make # Build distributable packages
2525-yarn npm audit # Check for security vulnerabilities
2626-2727-# Tauri backend
2828-yarn tauri:dev # Run Tauri in development
2929-yarn tauri:build # Build Tauri for production
3030-yarn tauri:check # Check Tauri compiles
3131-yarn tauri:test # Run Tauri smoke tests (Rust)
3232-3333-# Tauri Mobile (iOS/Android)
3434-yarn tauri:ios:dev # Run on iOS simulator
3535-yarn tauri:ios:build # Build for iOS
3636-yarn tauri:ios:xcode # Open Xcode project
3737-yarn tauri:android:dev # Run on Android emulator
3838-3939-# Server (webhook API for mobile sync)
4040-yarn server:install # Install server dependencies
4141-yarn server:start # Run production server
4242-yarn server:dev # Run with hot reload
4343-yarn server:test # Run server tests
4444-yarn server:healthcheck # Verify server starts
4545-```
4646-4747-### Testing
4848-```bash
4949-yarn test # Run all tests (headless)
5050-yarn test:electron # Run Electron tests only
5151-yarn test:visible # Run with visible windows
5252-yarn test:grep "pattern" # Run specific test by name
5353-```
5454-5555-**Testing Policy**: When fixing bugs or adding features, always add tests to cover the change. Tests live in `tests/desktop/smoke.spec.ts`.
5656-5757-## Architecture Overview
5858-5959-### Core Structure
6060-6161-1. **Main Process** (`index.js`):
6262- - Manages app lifecycle, windows, shortcuts, IPC communication
6363- - Implements custom `peek://` protocol for internal navigation
6464- - Handles profile management and data persistence
6565- - Hosts the TinyBase datastore with IPC handlers for renderer access
6666-6767-2. **Renderer Process** (`app/`):
6868- - Core app logic loads from `peek://app/background.html`
6969- - Extension loader at `app/extensions/loader.js`
7070- - Feature modules: scripts, cmd (registered in `app/features.js`)
7171- - Built-in extensions: groups, peeks, slides (in `./extensions/`)
7272- - Settings UI at `peek://app/settings/settings.html`
7373-7474-3. **Peek API** (`window.app`):
7575- - `api.window.*` for window management
7676- - `api.shortcuts.*` for hotkeys (local by default, `{ global: true }` for OS-level)
7777- - `api.datastore.*` for data persistence
7878- - `api.commands.*` for command palette integration
7979- - `api.theme.*` for theme management
8080- - `api.publish()`/`api.subscribe()` for cross-window messaging
8181-8282-### Backend Structure
8383-```
8484-backend/
8585-├── electron/ # Electron desktop backend (current primary)
8686-│ ├── datastore.ts # SQLite via better-sqlite3
8787-│ ├── ipc.ts # IPC handlers
8888-│ └── protocol.ts # peek:// protocol handler
8989-├── tauri/ # Tauri desktop backend (Rust)
9090-│ ├── src-tauri/
9191-│ │ ├── src/
9292-│ │ │ ├── lib.rs # App setup
9393-│ │ │ ├── datastore.rs # SQLite via rusqlite
9494-│ │ │ └── commands/ # IPC command handlers
9595-│ │ └── tests/smoke.rs # Rust smoke tests
9696-│ └── preload.js # Peek API implementation (Tauri)
9797-├── tauri-mobile/ # Tauri mobile app (iOS/Android)
9898-│ ├── src/ # React frontend
9999-│ └── src-tauri/ # Rust backend
100100-└── server/ # Webhook API server for mobile sync
101101- ├── index.js # Hono HTTP server
102102- ├── db.js # SQLite via better-sqlite3
103103- └── users.js # Multi-user API key auth
104104-```
105105-106106-**Data Sync**: Both backends use the same SQLite database path (`~/.config/Peek/{profile}/datastore.sqlite`). Only one backend should run at a time (file lock).
107107-108108-### Custom Protocol
109109-- `peek://app/...` - Core application files (from `app/` directory)
110110-- `peek://ext/{shortname}/...` - Extension files
111111-- `peek://tauri/...` - Tauri backend files (only in Tauri)
112112-113113-### Extensions Architecture
114114-115115-Extensions run in isolated BrowserWindow processes at `peek://ext/{id}/background.html`.
116116-117117-```
118118-extensions/
119119-├── example/ # Hello world example
120120-├── groups/
121121-├── peeks/
122122-└── slides/
123123- ├── manifest.json # Extension metadata
124124- ├── settings-schema.json # Settings UI schema (optional)
125125- ├── background.html # Entry point
126126- └── background.js # Main logic (ES module export)
127127-```
128128-129129-**Background script pattern:**
130130-```javascript
131131-const api = window.app;
132132-133133-const extension = {
134134- id: 'example',
135135- labels: { name: 'Example' },
136136-137137- init() {
138138- api.shortcuts.register('Option+x', handler, { global: true });
139139- api.commands.register({ name: 'my-cmd', description: '...', execute: fn });
140140- },
141141-142142- uninit() {
143143- api.shortcuts.unregister('Option+x', { global: true });
144144- api.commands.unregister('my-cmd');
145145- }
146146-};
147147-148148-export default extension;
149149-```
150150-151151-### Window Management
152152-- Windows identified by keys for lifecycle management (e.g., `peek:${address}`)
153153-- Modal windows use `type: 'panel'` to return focus to previous app on close
154154-- Parameters: `modal`, `keepLive`, `persistState`, `transparent`, `height`, `width`, `key`
155155-- "Escape IZUI" design - ESC key always returns to previous context
156156-157157-### Data Storage
158158-159159-**Settings Storage (localStorage)**:
160160-- Profile-based data separation in `{userData}/{PROFILE}/` directory
161161-- Features use `openStore(id, defaults, clear)` utility from `app/utils.js`
162162-163163-**Datastore (TinyBase)**:
164164-- In-memory TinyBase store for structured data
165165-- Runs in main process with IPC handlers for renderer access
166166-- Schema defined in `app/datastore/schema.js`
167167-- Tables: `addresses`, `visits`, `content`, `tags`, `blobs`, `scripts_data`, `feeds`
168168-169169-**Profile Management**:
170170-- Packaged app uses `default` profile
171171-- Running from source (`yarn start`) uses `dev` profile
172172-- `PROFILE` env var overrides automatic detection
173173-174174-## Common Pitfalls
175175-176176-1. Don't use relative paths in peek:// URLs - use absolute paths
177177-2. Remember to unregister shortcuts when features unload
178178-3. Windows opened by features should be tracked and closed on unload
179179-4. Modal windows require both `modal: true` and `type: 'panel'`
180180-5. Window keys must be unique - use pattern like `peek:${address}`
181181-6. Check if items are enabled (`item.enabled == true`) before registering shortcuts
182182-7. Datastore API returns `{ success, data }` - always check `result.success`
183183-8. Pubsub subscriptions are keyed by source - same source subscribing twice overwrites
184184-9. **Never put backend-specific code in `app/`**
185185-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).
186186-187187-## Code Style
188188-189189-- ES6 modules throughout (type: "module" in package.json)
190190-- Async/await preferred over callbacks
191191-- Console logging for debugging (controlled by DEBUG env var)
192192-- No TypeScript in app/, pure JavaScript
193193-- Uses `nodemon` for hot reload during development
194194-195195-## Security Notes
196196-197197-- This is a concept preview, NOT production-ready
198198-- No formal security audit performed
199199-- Different security model than traditional browsers
200200-- Be cautious with cross-origin access and custom APIs
201201-202202-## Mobile Development (Tauri iOS/Android)
203203-204204-Mobile development uses the separate `peek-save` app in `backend/tauri-mobile/`.
205205-206206-**CRITICAL - Build with Xcode GUI for iOS:**
207207-- NEVER run `xcodebuild` commands from terminal for final builds
208208-- Use Xcode GUI (Product → Build, Product → Run) for reliable builds
209209-210210-**CRITICAL - Do NOT run `xcodegen generate`:**
211211-- The Xcode project has custom settings that xcodegen overwrites
212212-213213-### Quick Start (One Command)
214214-215215-The easiest way to set up mobile development with sync testing:
216216-217217-```bash
218218-cd backend/tauri-mobile
219219-220220-# First time: build both debug and release Rust libraries
221221-npm run build # Build frontend
222222-cd src-tauri
223223-cargo build --target aarch64-apple-ios-sim # Debug (simulator)
224224-cargo build --target aarch64-apple-ios --release # Release (device)
225225-cd ..
226226-227227-# Start everything (servers, configure iOS, seed test data, open Xcode)
228228-npm run dev:ios
229229-```
230230-231231-`npm run dev:ios` does all of this automatically:
232232-1. Resets server data for a clean slate
233233-2. Starts backend server on a random port (10000-19999)
234234-3. Starts frontend dev server on port 1420
235235-4. Configures iOS simulator with server URL and API key
236236-5. Copies both debug and release Rust libraries
237237-6. Opens Xcode
238238-7. Seeds test data (3 items on server, 3 on iOS)
239239-240240-### Available npm Scripts
241241-242242-```bash
243243-# Development
244244-npm run dev:ios # Full dev setup (servers + config + seed + Xcode)
245245-npm run xcode # Just copy libraries and open Xcode
246246-npm run seed # Seed test data (needs SERVER_URL and API_KEY env vars)
247247-npm run reset:server # Delete server data directory
248248-249249-# Building
250250-npm run build # Build frontend (tsc + vite)
251251-npm run build:ios # Build frontend + debug Rust library
252252-npm run build:ios:release # Build frontend + release Rust library
253253-254254-# Testing
255255-npm run test # Run integration tests
256256-npm run test:verbose # Run tests with verbose output
257257-```
258258-259259-### iOS Build Process (Manual)
260260-261261-If you need to build manually instead of using `npm run dev:ios`:
262262-263263-```bash
264264-# 1. Build frontend
265265-cd backend/tauri-mobile && npm install && npm run build
266266-267267-# 2. Build debug library (for simulator)
268268-cd src-tauri
269269-cargo tauri build --target aarch64-apple-ios-sim --debug
270270-mkdir -p gen/apple/Externals/arm64/Debug
271271-cp target/aarch64-apple-ios-sim/debug/deps/libpeek_save_lib.a gen/apple/Externals/arm64/Debug/libapp.a
272272-273273-# 3. Build release library (for device)
274274-cargo tauri build --target aarch64-apple-ios
275275-mkdir -p gen/apple/Externals/arm64/Release
276276-cp target/aarch64-apple-ios/release/deps/libpeek_save_lib.a gen/apple/Externals/arm64/Release/libapp.a
277277-278278-# 4. Create assets symlink (if missing)
279279-ln -s ../../../dist gen/apple/assets
280280-281281-# 5. Open Xcode and build from GUI
282282-open gen/apple/peek-save.xcodeproj
283283-```
284284-285285-### Testing Bidirectional Sync
286286-287287-After running `npm run dev:ios`, test data is automatically seeded:
288288-289289-**Server has 3 items:**
290290-- URL: https://github.com/from-server (tags: server, github)
291291-- URL: https://example.com/from-server-1 (tags: server, test)
292292-- Text: "This is a text note from the server" (tags: server, note)
293293-294294-**iOS Simulator has 3 items:**
295295-- URL: https://mobile-only-1.example.com (tags: mobile, local)
296296-- URL: https://mobile-news.example.com (tags: local)
297297-- Text: "This text was created on mobile only" (tags: mobile)
298298-299299-**To test:**
300300-1. Build and run in Xcode (Cmd+R)
301301-2. Go to Settings in the app
302302-3. Settings should already be configured (server URL and API key)
303303-4. Tap **Sync All** to pull and push
304304-5. After sync, both server and iOS should have 6 items
305305-306306-**Gotchas:**
307307-- The `gen/apple/assets` symlink must exist or Xcode fails with "No such file or directory"
308308-- Debug scheme = simulator, Release scheme = device
309309-- If Rust code changes, rebuild the library and copy again
310310-- The "Build Rust Code" pre-build script in Xcode can hang indefinitely - pre-building avoids this
311311-- iOS simulator can't reach `localhost` - use Mac's IP address (dev:ios handles this automatically)
312312-313313-## Server Backend (Webhook API)
314314-315315-The server backend (`backend/server/`) is a remote HTTP API for syncing data from the mobile app. It's separate from the desktop backends - it doesn't implement the Peek API, it's a standalone Node.js server.
316316-317317-### Server Commands
318318-```bash
319319-# From project root
320320-yarn server:install # Install dependencies (first time)
321321-yarn server:start # Run production server (port 3000)
322322-yarn server:dev # Run with hot reload
323323-yarn server:test # Run unit tests (92 tests)
324324-yarn server:healthcheck # Verify server starts and responds
325325-326326-# From backend/server/
327327-npm install
328328-npm start
329329-npm run dev
330330-npm test
331331-npm run test:api:local # Test against local server (needs PEEK_LOCAL_KEY)
332332-npm run test:api:prod # Test against production (needs PEEK_PROD_KEY, PEEK_PROD_URL)
333333-```
334334-335335-### Server Architecture
336336-- **Hono** - Lightweight HTTP framework (like Express but faster)
337337-- **better-sqlite3** - SQLite database (same as Electron backend)
338338-- **Multi-user** - Each user gets isolated database in `./data/{userId}/`
339339-- **API key auth** - Bearer token authentication, keys hashed with SHA-256
340340-341341-### Server API Endpoints
342342-All endpoints except `/` require `Authorization: Bearer <api_key>` header.
343343-344344-| Endpoint | Method | Description |
345345-|----------|--------|-------------|
346346-| `/` | GET | Health check (public) |
347347-| `/webhook` | POST | Receive items from mobile (`{ urls, texts, tagsets }`) |
348348-| `/urls` | GET | List saved URLs |
349349-| `/urls/:id` | DELETE | Delete URL |
350350-| `/urls/:id/tags` | PATCH | Update URL tags |
351351-| `/texts` | GET/POST | List or create texts |
352352-| `/tagsets` | GET/POST | List or create tagsets |
353353-| `/images` | GET/POST | List or upload images |
354354-| `/images/:id` | GET/DELETE | Get or delete image |
355355-| `/items` | GET/POST | Unified endpoint (filter with `?type=`) |
356356-| `/tags` | GET | List tags by frecency |
357357-358358-### Server Deployment (Railway)
359359-360360-> **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.
361361-362362-The server is configured for Railway deployment.
363363-364364-**Initial Setup:**
365365-1. Connect Railway to `backend/server/` subdirectory
366366-2. Attach a persistent volume, set `DATA_DIR` env var to mount path
367367-3. Create users via the `users.js` module
368368-369369-**Deploying Updates:**
370370-```bash
371371-# Link to project (one-time, from backend/server/)
372372-railway link -p <project-name> -s <service-name> -e production
373373-374374-# Always run tests first
375375-npm test
376376-377377-# Deploy
378378-railway up -d
379379-380380-# Check logs
381381-railway logs -n 50
382382-383383-# Health check
384384-curl https://peek-node.up.railway.app/
385385-```
386386-387387-**Deployment Order (Server + Mobile):**
388388-1. **Server first** - stateless, has auto-migrations that run on first request
389389-2. **Mobile second** - works offline, adapts to server changes
390390-3. One-way sync only: mobile → server (no pull/download sync yet)
391391-392392-**Migration Gotcha:**
393393-When adding database columns via migration, ensure indexes on those columns are created AFTER the column migration runs, not in the initial CREATE TABLE statement.
394394-395395-### Server Database Schema
396396-Different from desktop datastore - optimized for mobile sync:
397397-- `items` - Unified table (type: url/text/tagset/image)
398398-- `tags` - Tag names with frecency scoring
399399-- `item_tags` - Many-to-many junction
400400-- `settings` - Key-value config
401401-402402-Images stored on disk in `./data/{userId}/images/` with content-hash deduplication.