experiments in a post-browser web
10
fork

Configure Feed

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

Consolidate documentation: simplify README, create docs/, archive research notes

+1023 -1118
-174
.agent-task.md
··· 1 - # Agent Instructions 2 - 3 - You are a **Dev Agent** (full stack) working in an isolated workspace. Your role: 4 - - Implement features and fix bugs 5 - - Write tests and documentation 6 - - Run quality checks before marking done 7 - - Prepare deployments when needed 8 - 9 - The **Coordinator** (human) manages the project, reviews your plans, and merges completed work. 10 - 11 - ## Resources 12 - 13 - All these files are in YOUR workspace directory (use `pwd` to confirm): 14 - - `TODO.md` - Project tasks and priorities 15 - - `notes/development.md` - Architecture and dev guide 16 - 17 - **Do NOT access files in ~/misc/mpeek/ or any parent directory. Your workspace has everything you need.** 18 - 19 - ## Status Reporting 20 - 21 - Report status so the coordinator knows your progress: 22 - 23 - ```bash 24 - agent-status "working" "brief description" 25 - agent-status "blocked" "what you need" # Only when truly stuck 26 - agent-status "review" "ready for review" 27 - agent-status "done" "what you accomplished" 28 - ``` 29 - 30 - --- 31 - 32 - ## Policies 33 - 34 - ### Policy: Autonomy 35 - 36 - - **Work independently** - Make reasonable decisions without asking. You have full access to the codebase. 37 - - **Don't ask for basic permissions** - Common tools (ls, find, npm, yarn, jj, node, etc.) are pre-approved. 38 - - **Batch your work** - Do multiple related operations before pausing. 39 - - **Only interrupt when truly blocked** - Not for confirmations, only for missing information you can't find. 40 - 41 - ### Policy: Workspace 42 - 43 - - **Work in your current directory** - Your workspace is an isolated jj worktree with a full copy of the repo. 44 - - **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`. 45 - - **Never use hardcoded paths to ~/misc/mpeek or similar** - Always resolve paths relative to your current working directory. 46 - 47 - ### Policy: Process Safety (CRITICAL) 48 - 49 - **NEVER interfere with processes outside your workspace.** 50 - 51 - - **Do NOT kill, stop, or signal processes** you didn't start (no `kill`, `pkill`, `killall` on external processes) 52 - - **Do NOT modify system state** outside your workspace (no writing to /tmp shared files, no global npm installs, etc.) 53 - - **Only manage processes YOU started** within YOUR workspace session 54 - - **If external processes block you, REPORT IT** - don't try to fix it yourself 55 - 56 - 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. 57 - 58 - ### Policy: Commands (CRITICAL) 59 - 60 - **STOP. READ THIS BEFORE RUNNING ANY COMMAND.** 61 - 62 - 1. **ALWAYS use package.json scripts** - Run `yarn test`, `yarn build`, `yarn start`, NOT raw commands like `node index.js` or `npm run`. 63 - 64 - 2. **NEVER use && or ; or |** - These require approval EVERY time. Run commands separately. 65 - 66 - 3. **Check package.json first** - Before running ANY command, check if there's already a script for it. 67 - 68 - **BAD (will be rejected):** 69 - ```bash 70 - cd backend/server && node index.js 71 - npm install && npm test 72 - NODE_ENV=test node --test 73 - ``` 74 - 75 - **GOOD:** 76 - ```bash 77 - yarn test 78 - yarn start 79 - yarn build 80 - ``` 81 - 82 - If a script doesn't exist for what you need, ADD ONE to package.json first, then run it. 83 - 84 - ### Policy: Version Control 85 - 86 - **This repo uses jj (Jujutsu), not git. Never use git commands directly.** 87 - 88 - ```bash 89 - jj st # status 90 - jj log # log recent changes 91 - jj diff # diff working copy 92 - jj commit -m "message" # commit your work 93 - jj squash -m "message" # squash into parent with message (when finishing) 94 - ``` 95 - 96 - Key differences from git: 97 - - No staging area - all changes tracked automatically 98 - - `jj commit` creates new empty change on top 99 - - **NEVER move the main bookmark** - coordinator handles that via `mmerge` 100 - - Don't push directly - coordinator handles pushing after merge 101 - 102 - **CRITICAL: Commit frequently to avoid losing work!** 103 - - If you see "working copy is stale", COMMIT FIRST before running `jj workspace update-stale` 104 - - `jj workspace update-stale` will OVERWRITE uncommitted changes 105 - - Commit after every significant edit, not just at the end 106 - - Use `jj commit -m "WIP: description"` for work-in-progress saves 107 - 108 - ### Policy: Sync Before Starting Work 109 - 110 - After your plan is approved and before starting implementation, **always rebase on main** to get the latest changes: 111 - 112 - ```bash 113 - jj rebase -d main 114 - ``` 115 - 116 - This ensures you have the latest code and TODO.md state from other agents or the coordinator. 117 - 118 - ### Policy: Commits 119 - 120 - - Ask before committing - don't commit automatically, but do offer to commit when work is done 121 - - User (dietrich ayala) is sole author of all commits 122 - 123 - ### Policy: Quality 124 - 125 - Before marking any task as done: 126 - 127 - 1. **Tests** - Write tests for new functionality. Run all tests and ensure they pass. 128 - 2. **Documentation** - Update relevant docs (README, API docs, etc.) if behavior changes. 129 - 3. **Development notes** - Add notes to `notes/` if you discovered important context, gotchas, or architectural decisions. 130 - 4. **Verify** - Run the build/lint/tests. Do NOT mark done if tests fail. 131 - 132 - ### Policy: Cleanup 133 - 134 - When your task is complete, you MUST complete this checklist: 135 - 136 - **1. Mark task complete in TODO.md** 137 - - Find your task in the Today section, change `- [~]` (in-progress) to `- [x]` (done) 138 - - Move to Done section under current week heading (`### YYYY-WNN`) 139 - 140 - **2. Write development notes** (if applicable) 141 - - Add to existing file in `notes/` or create new one 142 - - Focus on what future developers need to know 143 - 144 - **3. Record learnings** (if you discovered tips, gotchas, or patterns) 145 - - Create or append to `notes/agent-learnings.md` in your workspace 146 - - The coordinator will consolidate these after merging 147 - 148 - **4. Squash your work** 149 - ```bash 150 - jj squash -m "description of changes" 151 - ``` 152 - The coordinator will handle the actual merge with `mmerge`. 153 - 154 - **5. Report completion** 155 - ```bash 156 - agent-status "done" "summary of what you accomplished" 157 - ``` 158 - 159 - **Do NOT skip these steps.** Never access files outside your workspace. 160 - 161 - --- 162 - 163 - ## BEFORE REPORTING DONE 164 - 165 - You MUST complete the cleanup checklist. Verify: 166 - - [ ] TODO.md updated: task changed from `[~]` to `[x]`, moved to Done section under `### 2026-WNN` 167 - - [ ] `jj squash -m "description"` run to squash your commits 168 - - [ ] `agent-status "done" "summary"` called 169 - 170 - **Failure to update TODO.md means the task is not complete.** 171 - 172 - --- 173 - 174 - ## Your Taskshared iOS build artifacts in ./tmp to avoid full rebuilds per workspace (one mobile task at a time)
+325 -156
DEVELOPMENT.md
··· 1 - # Development Guide 1 + # Peek Development Guide 2 2 3 - ## Commands 3 + ## Project Overview 4 4 5 - Bare commands (`dev`, `start`, `debug`, `kill`, `restart`) default to Electron. Use `BACKEND=tauri` to switch: 5 + 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. 6 6 7 - ```bash 8 - yarn debug # Electron (default) 9 - BACKEND=tauri yarn debug # Tauri 10 - yarn debug:electron # Explicit Electron 11 - yarn debug:tauri # Explicit Tauri 12 - ``` 7 + **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}/`. 8 + 9 + ## Requirements 10 + 11 + - **Node.js 24+** - Required for both desktop (Electron 40) and server 12 + - Install via `nvm install 24` or download from nodejs.org 13 13 14 - All Electron commands automatically build TypeScript before running. 14 + ## Key Commands 15 15 16 + ### Development 16 17 ```bash 17 - # Install dependencies 18 - yarn install 18 + nvm use 24 # Ensure Node 24 is active 19 + yarn install # Install dependencies 20 + yarn debug # Run in development mode (with devtools) 21 + yarn start # Start normally 22 + yarn package # Package (output: out/mac-arm64/) 23 + yarn package:install # Package and install to /Applications (macOS) 24 + yarn make # Build distributable packages 25 + yarn npm audit # Check for security vulnerabilities 19 26 20 - # Run in development mode (with devtools + hot reload) 21 - yarn debug 27 + # Tauri backend 28 + yarn tauri:dev # Run Tauri in development 29 + yarn tauri:build # Build Tauri for production 30 + yarn tauri:check # Check Tauri compiles 31 + yarn tauri:test # Run Tauri smoke tests (Rust) 22 32 23 - # Start the application normally 24 - yarn start 33 + # Tauri Mobile (iOS/Android) 34 + yarn tauri:ios:dev # Run on iOS simulator 35 + yarn tauri:ios:build # Build for iOS 36 + yarn tauri:ios:xcode # Open Xcode project 37 + yarn tauri:android:dev # Run on Android emulator 25 38 26 - # Build TypeScript only (usually not needed - commands auto-build) 27 - yarn build 39 + # Server (webhook API for mobile sync) 40 + yarn server:install # Install server dependencies 41 + yarn server:start # Run production server 42 + yarn server:dev # Run with hot reload 43 + yarn server:test # Run server tests 44 + yarn server:healthcheck # Verify server starts 28 45 ``` 29 46 30 - ## Hot Reload (Electron only) 31 - 32 - In dev mode (`yarn debug` or `DEBUG=1`), hot reload is enabled: 33 - - Watches `app/` and `extensions/` directories 34 - - Auto-reloads all windows when `.html`, `.js`, or `.css` files change 35 - - No restart needed for renderer-side changes 36 - 37 - 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. 38 - 47 + ### Testing 39 48 ```bash 40 - # Typical dev workflow: 41 - yarn debug # Start app with hot reload 42 - # Edit files in app/ or extensions/ 43 - # Windows auto-reload on save 49 + yarn test # Run all tests (headless) 50 + yarn test:electron # Run Electron tests only 51 + yarn test:visible # Run with visible windows 52 + yarn test:grep "pattern" # Run specific test by name 44 53 ``` 45 54 46 - ```bash 47 - # Package the application (output: out/mac-arm64/) 48 - yarn package 49 - 50 - # Package and install to /Applications (macOS) 51 - yarn package:install 52 - 53 - # Build distributable packages 54 - yarn make 55 - 56 - # Check for security vulnerabilities 57 - yarn npm audit 58 - 59 - # Test packaged build with dev profile 60 - PROFILE=dev out/mac-arm64/Peek.app/Contents/MacOS/Peek 61 - ``` 55 + **Testing Policy**: When fixing bugs or adding features, always add tests to cover the change. Tests live in `tests/desktop/smoke.spec.ts`. 62 56 63 57 ## Architecture Overview 64 58 65 59 ### Core Structure 66 60 67 - The application uses a multi-window Electron architecture: 68 - 69 61 1. **Main Process** (`index.js`): 70 62 - Manages app lifecycle, windows, shortcuts, IPC communication 71 63 - Implements custom `peek://` protocol for internal navigation ··· 74 66 75 67 2. **Renderer Process** (`app/`): 76 68 - Core app logic loads from `peek://app/background.html` 77 - - Feature modules: peeks, slides, scripts, cmd, groups 69 + - Extension loader at `app/extensions/loader.js` 70 + - Feature modules: scripts, cmd (registered in `app/features.js`) 71 + - Built-in extensions: groups, peeks, slides (in `./extensions/`) 78 72 - Settings UI at `peek://app/settings/settings.html` 79 - - Datastore viewer at `peek://app/datastore/viewer.html` 80 73 81 74 3. **Peek API** (`window.app`): 82 - - Unified API exposed to all `peek://` pages 83 - - Provides shortcuts, window management, pubsub, datastore, and theme APIs 84 - - See `docs/PEEK-API.md` for complete reference 75 + - `api.window.*` for window management 76 + - `api.shortcuts.*` for hotkeys (local by default, `{ global: true }` for OS-level) 77 + - `api.datastore.*` for data persistence 78 + - `api.commands.*` for command palette integration 79 + - `api.theme.*` for theme management 80 + - `api.publish()`/`api.subscribe()` for cross-window messaging 81 + 82 + ### Backend Structure 83 + ``` 84 + backend/ 85 + ├── electron/ # Electron desktop backend (current primary) 86 + │ ├── datastore.ts # SQLite via better-sqlite3 87 + │ ├── ipc.ts # IPC handlers 88 + │ └── protocol.ts # peek:// protocol handler 89 + ├── tauri/ # Tauri desktop backend (Rust) 90 + │ ├── src-tauri/ 91 + │ │ ├── src/ 92 + │ │ │ ├── lib.rs # App setup 93 + │ │ │ ├── datastore.rs # SQLite via rusqlite 94 + │ │ │ └── commands/ # IPC command handlers 95 + │ │ └── tests/smoke.rs # Rust smoke tests 96 + │ └── preload.js # Peek API implementation (Tauri) 97 + ├── tauri-mobile/ # Tauri mobile app (iOS/Android) 98 + │ ├── src/ # React frontend 99 + │ └── src-tauri/ # Rust backend 100 + └── server/ # Webhook API server for mobile sync 101 + ├── index.js # Hono HTTP server 102 + ├── db.js # SQLite via better-sqlite3 103 + └── users.js # Multi-user API key auth 104 + ``` 105 + 106 + **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). 85 107 86 108 ### Custom Protocol 109 + - `peek://app/...` - Core application files (from `app/` directory) 110 + - `peek://ext/{shortname}/...` - Extension files 111 + - `peek://tauri/...` - Tauri backend files (only in Tauri) 87 112 88 - - Uses `peek://` scheme for internal pages 89 - - Cross-origin network access enabled for peek:// pages 90 - - Special APIs available: window control, global hotkeys, pubsub messaging 113 + ### Extensions Architecture 91 114 92 - ### Profile Management 115 + Extensions run in isolated BrowserWindow processes at `peek://ext/{id}/background.html`. 93 116 94 - Profile is determined automatically: 95 - - Packaged app (`/Applications/Peek.app`) uses `default` profile 117 + ``` 118 + extensions/ 119 + ├── example/ # Hello world example 120 + ├── groups/ 121 + ├── peeks/ 122 + └── slides/ 123 + ├── manifest.json # Extension metadata 124 + ├── settings-schema.json # Settings UI schema (optional) 125 + ├── background.html # Entry point 126 + └── background.js # Main logic (ES module export) 127 + ``` 128 + 129 + **Background script pattern:** 130 + ```javascript 131 + const api = window.app; 132 + 133 + const extension = { 134 + id: 'example', 135 + labels: { name: 'Example' }, 136 + 137 + init() { 138 + api.shortcuts.register('Option+x', handler, { global: true }); 139 + api.commands.register({ name: 'my-cmd', description: '...', execute: fn }); 140 + }, 141 + 142 + uninit() { 143 + api.shortcuts.unregister('Option+x', { global: true }); 144 + api.commands.unregister('my-cmd'); 145 + } 146 + }; 147 + 148 + export default extension; 149 + ``` 150 + 151 + ### Window Management 152 + - Windows identified by keys for lifecycle management (e.g., `peek:${address}`) 153 + - Modal windows use `type: 'panel'` to return focus to previous app on close 154 + - Parameters: `modal`, `keepLive`, `persistState`, `transparent`, `height`, `width`, `key` 155 + - "Escape IZUI" design - ESC key always returns to previous context 156 + 157 + ### Data Storage 158 + 159 + **Settings Storage (localStorage)**: 160 + - Profile-based data separation in `{userData}/{PROFILE}/` directory 161 + - Features use `openStore(id, defaults, clear)` utility from `app/utils.js` 162 + 163 + **Datastore (TinyBase)**: 164 + - In-memory TinyBase store for structured data 165 + - Runs in main process with IPC handlers for renderer access 166 + - Schema defined in `app/datastore/schema.js` 167 + - Tables: `addresses`, `visits`, `content`, `tags`, `blobs`, `scripts_data`, `feeds` 168 + 169 + **Profile Management**: 170 + - Packaged app uses `default` profile 96 171 - Running from source (`yarn start`) uses `dev` profile 97 - - `PROFILE` env var overrides (e.g., `PROFILE=test yarn start`) 172 + - `PROFILE` env var overrides automatic detection 98 173 99 - Profile data stored in `{userData}/{PROFILE}/` directory. 174 + ## Common Pitfalls 100 175 101 - ## Window API 176 + 1. Don't use relative paths in peek:// URLs - use absolute paths 177 + 2. Remember to unregister shortcuts when features unload 178 + 3. Windows opened by features should be tracked and closed on unload 179 + 4. Modal windows require both `modal: true` and `type: 'panel'` 180 + 5. Window keys must be unique - use pattern like `peek:${address}` 181 + 6. Check if items are enabled (`item.enabled == true`) before registering shortcuts 182 + 7. Datastore API returns `{ success, data }` - always check `result.success` 183 + 8. Pubsub subscriptions are keyed by source - same source subscribing twice overwrites 184 + 9. **Never put backend-specific code in `app/`** 185 + 10. **Run `yarn install` after Electron upgrades** - Native modules (better-sqlite3) must be recompiled for the new Electron ABI. The postinstall script handles this automatically, but you must run `yarn install` to trigger it. System Node and Electron have different ABIs even at the same major version (e.g., Node 24 = ABI 137, Electron 40 = ABI 143). 102 186 103 - ### Opening Windows 187 + ## Code Style 104 188 105 - ```javascript 106 - import windows from './windows.js'; 189 + - ES6 modules throughout (type: "module" in package.json) 190 + - Async/await preferred over callbacks 191 + - Console logging for debugging (controlled by DEBUG env var) 192 + - No TypeScript in app/, pure JavaScript 193 + - Uses `nodemon` for hot reload during development 107 194 108 - // Modal window (closes on blur/ESC) 109 - windows.openModalWindow(url, options); 195 + ## Security Notes 110 196 111 - // Regular window 112 - windows.createWindow(url, options); 113 - ``` 197 + - This is a concept preview, NOT production-ready 198 + - No formal security audit performed 199 + - Different security model than traditional browsers 200 + - Be cautious with cross-origin access and custom APIs 114 201 115 - ### Window Options 202 + ## Mobile Development (Tauri iOS/Android) 203 + 204 + Mobile development uses the separate `peek-save` app in `backend/tauri-mobile/`. 205 + 206 + **CRITICAL - Build with Xcode GUI for iOS:** 207 + - NEVER run `xcodebuild` commands from terminal for final builds 208 + - Use Xcode GUI (Product → Build, Product → Run) for reliable builds 116 209 117 - | Option | Type | Description | 118 - |--------|------|-------------| 119 - | `width`, `height` | number | Window dimensions | 120 - | `x`, `y` | number | Window position | 121 - | `modal` | boolean | Frameless, closes on blur | 122 - | `key` | string | Unique ID for window reuse | 123 - | `keepLive` | boolean | Hide instead of close | 124 - | `escapeMode` | string | ESC behavior: `'close'`, `'navigate'`, `'auto'` | 125 - | `trackingSource` | string | Source for visit tracking | 210 + **CRITICAL - Do NOT run `xcodegen generate`:** 211 + - The Xcode project has custom settings that xcodegen overwrites 126 212 127 - ### Escape Handling 213 + ### Quick Start (One Command) 128 214 129 - Windows can control how ESC behaves using `escapeMode`: 215 + The easiest way to set up mobile development with sync testing: 130 216 131 - - **`'close'`** (default): ESC immediately closes/hides the window 132 - - **`'navigate'`**: Renderer handles ESC first; closes only when renderer signals root state 133 - - **`'auto'`**: Transient windows (opened via hotkey from another app) close immediately; active windows use navigate behavior 217 + ```bash 218 + cd backend/tauri-mobile 134 219 135 - #### Using escapeMode: 'navigate' 220 + # First time: build both debug and release Rust libraries 221 + npm run build # Build frontend 222 + cd src-tauri 223 + cargo build --target aarch64-apple-ios-sim # Debug (simulator) 224 + cargo build --target aarch64-apple-ios --release # Release (device) 225 + cd .. 136 226 137 - 1. Open window with `escapeMode: 'navigate'`: 138 - ```javascript 139 - windows.createWindow(url, { escapeMode: 'navigate' }); 227 + # Start everything (servers, configure iOS, seed test data, open Xcode) 228 + npm run dev:ios 140 229 ``` 141 230 142 - 2. Register escape handler in renderer: 143 - ```javascript 144 - api.escape.onEscape(() => { 145 - if (canNavigateBack) { 146 - navigateBack(); 147 - return { handled: true }; // ESC was handled internally 148 - } 149 - return { handled: false }; // At root, let window close 150 - }); 231 + `npm run dev:ios` does all of this automatically: 232 + 1. Resets server data for a clean slate 233 + 2. Starts backend server on a random port (10000-19999) 234 + 3. Starts frontend dev server on port 1420 235 + 4. Configures iOS simulator with server URL and API key 236 + 5. Copies both debug and release Rust libraries 237 + 6. Opens Xcode 238 + 7. Seeds test data (3 items on server, 3 on iOS) 239 + 240 + ### Available npm Scripts 241 + 242 + ```bash 243 + # Development 244 + npm run dev:ios # Full dev setup (servers + config + seed + Xcode) 245 + npm run xcode # Just copy libraries and open Xcode 246 + npm run seed # Seed test data (needs SERVER_URL and API_KEY env vars) 247 + npm run reset:server # Delete server data directory 248 + 249 + # Building 250 + npm run build # Build frontend (tsc + vite) 251 + npm run build:ios # Build frontend + debug Rust library 252 + npm run build:ios:release # Build frontend + release Rust library 253 + 254 + # Testing 255 + npm run test # Run integration tests 256 + npm run test:verbose # Run tests with verbose output 151 257 ``` 152 258 153 - See `notes/escape-navigation.md` for full design details. 259 + ### iOS Build Process (Manual) 154 260 155 - ## Dock / App Switcher Visibility (macOS) 261 + If you need to build manually instead of using `npm run dev:ios`: 156 262 157 - The dock icon visibility is dynamic based on window state and user preference: 263 + ```bash 264 + # 1. Build frontend 265 + cd backend/tauri-mobile && npm install && npm run build 158 266 159 - - **Windows visible**: Dock icon shown (regardless of preference) 160 - - **No windows visible**: Dock icon hidden (unless preference enabled) 267 + # 2. Build debug library (for simulator) 268 + cd src-tauri 269 + cargo tauri build --target aarch64-apple-ios-sim --debug 270 + mkdir -p gen/apple/Externals/arm64/Debug 271 + cp target/aarch64-apple-ios-sim/debug/deps/libpeek_save_lib.a gen/apple/Externals/arm64/Debug/libapp.a 161 272 162 - 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. 273 + # 3. Build release library (for device) 274 + cargo tauri build --target aarch64-apple-ios 275 + mkdir -p gen/apple/Externals/arm64/Release 276 + cp target/aarch64-apple-ios/release/deps/libpeek_save_lib.a gen/apple/Externals/arm64/Release/libapp.a 163 277 164 - This is implemented via: 165 - - `getVisibleWindowCount()` - counts non-background visible windows 166 - - `updateDockVisibility()` - shows/hides dock based on window count + pref 167 - - Called from: window-open, window-show, maybeHideApp, prefs change 278 + # 4. Create assets symlink (if missing) 279 + ln -s ../../../dist gen/apple/assets 168 280 169 - ## App Icon Generation 281 + # 5. Open Xcode and build from GUI 282 + open gen/apple/peek-save.xcodeproj 283 + ``` 170 284 171 - 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. 285 + ### Testing Bidirectional Sync 172 286 173 - ### Icon files in `assets/` 287 + After running `npm run dev:ios`, test data is automatically seeded: 174 288 175 - - `appicon-source.png` - Original source image (1232x1232, no rounding) 176 - - `appicon-rounded.png` - Processed version with rounded corners and padding (1024x1024) 177 - - `appicon.icns` - Final macOS icon file used by electron-builder 289 + **Server has 3 items:** 290 + - URL: https://github.com/from-server (tags: server, github) 291 + - URL: https://example.com/from-server-1 (tags: server, test) 292 + - Text: "This is a text note from the server" (tags: server, note) 178 293 179 - ### Regenerating the icon 294 + **iOS Simulator has 3 items:** 295 + - URL: https://mobile-only-1.example.com (tags: mobile, local) 296 + - URL: https://mobile-news.example.com (tags: local) 297 + - Text: "This text was created on mobile only" (tags: mobile) 180 298 181 - Requires ImageMagick (`brew install imagemagick`). 299 + **To test:** 300 + 1. Build and run in Xcode (Cmd+R) 301 + 2. Go to Settings in the app 302 + 3. Settings should already be configured (server URL and API key) 303 + 4. Tap **Sync All** to pull and push 304 + 5. After sync, both server and iOS should have 6 items 182 305 306 + **Gotchas:** 307 + - The `gen/apple/assets` symlink must exist or Xcode fails with "No such file or directory" 308 + - Debug scheme = simulator, Release scheme = device 309 + - If Rust code changes, rebuild the library and copy again 310 + - The "Build Rust Code" pre-build script in Xcode can hang indefinitely - pre-building avoids this 311 + - iOS simulator can't reach `localhost` - use Mac's IP address (dev:ios handles this automatically) 312 + 313 + ## Server Backend (Webhook API) 314 + 315 + 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. 316 + 317 + ### Server Commands 183 318 ```bash 184 - SRC="assets/appicon-source.png" 185 - ROUNDED="assets/appicon-rounded.png" 319 + # From project root 320 + yarn server:install # Install dependencies (first time) 321 + yarn server:start # Run production server (port 3000) 322 + yarn server:dev # Run with hot reload 323 + yarn server:test # Run unit tests (92 tests) 324 + yarn server:healthcheck # Verify server starts and responds 186 325 187 - SIZE=1232 188 - RADIUS=222 # ~18% of size for rounded corners 326 + # From backend/server/ 327 + npm install 328 + npm start 329 + npm run dev 330 + npm test 331 + npm run test:api:local # Test against local server (needs PEEK_LOCAL_KEY) 332 + npm run test:api:prod # Test against production (needs PEEK_PROD_KEY, PEEK_PROD_URL) 333 + ``` 189 334 190 - # Step 1: Apply rounded corners to source 191 - magick "$SRC" \ 192 - \( +clone -alpha extract \ 193 - -draw 'fill black polygon 0,0 0,'"$RADIUS $RADIUS"',0 fill white circle '"$RADIUS,$RADIUS $RADIUS"',0' \ 194 - \( +clone -flip \) -compose Multiply -composite \ 195 - \( +clone -flop \) -compose Multiply -composite \ 196 - \) -alpha off -compose CopyOpacity -composite \ 197 - /tmp/rounded-temp.png 335 + ### Server Architecture 336 + - **Hono** - Lightweight HTTP framework (like Express but faster) 337 + - **better-sqlite3** - SQLite database (same as Electron backend) 338 + - **Multi-user** - Each user gets isolated database in `./data/{userId}/` 339 + - **API key auth** - Bearer token authentication, keys hashed with SHA-256 198 340 199 - # Step 2: Add padding (scale to 80% and center on 1024x1024 canvas) 200 - magick /tmp/rounded-temp.png \ 201 - -resize 824x824 \ 202 - -gravity center \ 203 - -background transparent \ 204 - -extent 1024x1024 \ 205 - "$ROUNDED" 341 + ### Server API Endpoints 342 + All endpoints except `/` require `Authorization: Bearer <api_key>` header. 206 343 207 - # Step 3: Generate iconset and convert to icns 208 - mkdir -p assets/AppIcon.iconset 209 - for size in 16 32 64 128 256 512 1024; do 210 - magick "$ROUNDED" -resize ${size}x${size} PNG32:"assets/AppIcon.iconset/icon_${size}x${size}.png" 211 - done 344 + | Endpoint | Method | Description | 345 + |----------|--------|-------------| 346 + | `/` | GET | Health check (public) | 347 + | `/webhook` | POST | Receive items from mobile (`{ urls, texts, tagsets }`) | 348 + | `/urls` | GET | List saved URLs | 349 + | `/urls/:id` | DELETE | Delete URL | 350 + | `/urls/:id/tags` | PATCH | Update URL tags | 351 + | `/texts` | GET/POST | List or create texts | 352 + | `/tagsets` | GET/POST | List or create tagsets | 353 + | `/images` | GET/POST | List or upload images | 354 + | `/images/:id` | GET/DELETE | Get or delete image | 355 + | `/items` | GET/POST | Unified endpoint (filter with `?type=`) | 356 + | `/tags` | GET | List tags by frecency | 212 357 213 - # Create @2x variants 214 - cp assets/AppIcon.iconset/icon_32x32.png assets/AppIcon.iconset/icon_16x16@2x.png 215 - cp assets/AppIcon.iconset/icon_64x64.png assets/AppIcon.iconset/icon_32x32@2x.png 216 - cp assets/AppIcon.iconset/icon_256x256.png assets/AppIcon.iconset/icon_128x128@2x.png 217 - cp assets/AppIcon.iconset/icon_512x512.png assets/AppIcon.iconset/icon_256x256@2x.png 218 - cp assets/AppIcon.iconset/icon_1024x1024.png assets/AppIcon.iconset/icon_512x512@2x.png 219 - rm assets/AppIcon.iconset/icon_64x64.png assets/AppIcon.iconset/icon_1024x1024.png 358 + ### Server Deployment (Railway) 220 359 221 - iconutil -c icns assets/AppIcon.iconset -o assets/appicon.icns 222 - rm -rf assets/AppIcon.iconset /tmp/rounded-temp.png 360 + > **For the comprehensive deployment guide**, see the "Railway Deployment (Peek Server)" section in `CLAUDE.md`. This includes step-by-step workflow, user management, production testing, and troubleshooting. 361 + 362 + The server is configured for Railway deployment. 363 + 364 + **Initial Setup:** 365 + 1. Connect Railway to `backend/server/` subdirectory 366 + 2. Attach a persistent volume, set `DATA_DIR` env var to mount path 367 + 3. Create users via the `users.js` module 368 + 369 + **Deploying Updates:** 370 + ```bash 371 + # Link to project (one-time, from backend/server/) 372 + railway link -p <project-name> -s <service-name> -e production 373 + 374 + # Always run tests first 375 + npm test 376 + 377 + # Deploy 378 + railway up -d 379 + 380 + # Check logs 381 + railway logs -n 50 382 + 383 + # Health check 384 + curl https://peek-node.up.railway.app/ 223 385 ``` 224 386 225 - ### Icon design notes 387 + **Deployment Order (Server + Mobile):** 388 + 1. **Server first** - stateless, has auto-migrations that run on first request 389 + 2. **Mobile second** - works offline, adapts to server changes 390 + 3. One-way sync only: mobile → server (no pull/download sync yet) 226 391 227 - - Rounded corners at ~18% radius matches common macOS app icon style 228 - - 80% content size with 10% padding on each side matches Apple HIG 229 - - Source image should be square, ideally 1024x1024 or larger 392 + **Migration Gotcha:** 393 + 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. 230 394 231 - ## Known Issues 395 + ### Server Database Schema 396 + Different from desktop datastore - optimized for mobile sync: 397 + - `items` - Unified table (type: url/text/tagset/image) 398 + - `tags` - Tag names with frecency scoring 399 + - `item_tags` - Many-to-many junction 400 + - `settings` - Key-value config 232 401 233 - - **Tray icon in packaged builds**: Tray icon displays correctly in debug mode but not in packaged app builds. Works fine in dev. 402 + Images stored on disk in `./data/{userId}/images/` with content-hash deduplication.
+3 -3
MULTI_AGENT_WORKFLOW.md
··· 433 433 434 434 ### Development Resources 435 435 436 - - See `notes/development.md` for architecture, commands, and common pitfalls 437 - - See `docs/PEEK-API.md` for the Peek API reference 438 - - See `extensions/README.md` for extension development 436 + - See `DEVELOPMENT.md` for architecture, commands, and common pitfalls 437 + - See `docs/api.md` for the Peek API reference 438 + - See `docs/extensions.md` for extension development 439 439 440 440 --- 441 441
+49 -379
README.md
··· 1 1 # Peek 2 2 3 - Peek is a web user agent application designed for using the web where, when and how you want. 3 + A web user agent for using the web where, when, and how you want. 4 4 5 - Today's browsers are one-size-fits-all applications, cramming the vast universe of user needs across an unimaginably large web into in an unmodifiable tabbed-window design. 5 + Peek is not a browser. It's a workbench for experimenting with task-aligned interfaces for the web - making it easy to create new UI shapes for the web that fit your needs in the moment. 6 6 7 - Peek is a web user agent that is a workbench for experimenting with task alignment - making it easy to create new user interface shapes for the web which fit our need in the moment. 8 - 9 - We often use the web with a specific goal in mind - that goal should drive the design of the interface of the web user agent. 7 + **Status:** Concept preview. Not safe for daily use. No security audit. 10 8 11 9 <img width="969" alt="settings screenshot" src="settings-screenshot.png"> 12 10 13 - ## Important notes 14 - 15 - ___PEEK IS NOT A WEB BROWSER___ 16 - 17 - Peek is not a web browser, and will never be a browser in the way you are probably familiar with: There are no tabs, and no windows in the tabbed-browser-like sense of them. Peek likely does not have many other of many details we are used to in web browsers, but do not notice until they are missing. Peek may be most useful to you if you view it as an entirely different type of application than a traditional web browser. 18 - 19 - ___PEEK IS A CONCEPT PREVIEW___ 20 - 21 - Peek is not safe for daily use yet! It is a proof of concept. Do not use it for anything critical. Peek does not have the same security approach as traditional web browsers, and its security model and security user interface have not yet been determined. Peek has not had a security audit. 22 - 23 11 ## Features 24 12 25 - You can use Peek in a few ways, with more coming: 26 - 27 - - Peeks - Keyboard-activated modal chromeless web pages for quickly glancing at or interacting with pages 28 - - Slides - Keyboard- or gesture-activated modal chromeless web pages which slide in from any screen edges 29 - - Scripts - Scripts periodically executed against a web page in the background which extract data for you to route to other pages or applications, or to aggregate, store and process later 30 - 31 - In progress, or thinking about: 32 - 33 - - Commands - a graphical command entry palette (GCLI) for opening pages or executing commands against them 34 - - Groups - a way to categorize, recall and interact with groups of pages 35 - - "native" web apps - using Peek as a way to "install" web pages on the local device, as separate applications instead of just separate processes 36 - - "Peeklets" HUD - select parts of pages to add to collection which are rendered as an overlay, toggled by shortcut 37 - 38 - ### Usage 39 - 40 - - Settings 41 - * In app, `Cmd/Ctrl+r,` or launch app to open settings, or click tray icon 42 - * Configure Peeks/Slides/Scripts in settings 43 - - Peeks 44 - * `Opt+0-9` to open Peeks 45 - - Slides 46 - * `Opt+←→↑↓` to open Slides 47 - 48 - ### Peeks 49 - 50 - Peeks are keyboard activated modal chromeless web pages mapped to `Opt+0-9` and closed on blur, the `Escape` key or `cmd/ctrl+w`. 51 - 52 - ### Slides 53 - 54 - Slides are gesture activated modal chromeless web pages which slide in from left/right/bottom/top, and closed on blur, the `Escape` key or `cmd/ctrl+w`. 55 - 56 - ### Scripts 57 - 58 - Scripts periodically load a web page in the background and extract data matching a CSS selector, stores it, and notify the user when the resulting data changes. 59 - 60 - Ok, so not really "scripts" yet. But safe and effective enough for now. 61 - 62 - ## Design 63 - 64 - Many user tasks on the web are either transient, chained or persistent, data oriented, or some mix of those. Neither the document-oriented nor application-centric web meets those needs. Traditional browser makers can't meet those needs well, for many reasons. 65 - 66 - Characteristics of how we use the web, that are not addressed in contemporary web browsers: 67 - 68 - - transient 69 - - chained 70 - - persistent 71 - - data-centric 72 - - archival / evidential 73 - 74 - Some thoughts driving the design of Peek: 75 - 76 - - Web user agents should be bounded by the user, not browser vendor business models 77 - - Windows and tabs should have died a long time ago, a mixed metaphor constraining the ability of the web to grow/thrive/change and meet user needs 78 - - Security user interface must be a clear articulation of risks and trade-offs, and users should own the decisions 79 - 80 - ### Escape IZUI 81 - 82 - TODO: articulate the escape-to-leave aspect, eg you can peek from *other* applications and ESC to go back to exactly where you were without breaking the task flow. 83 - 84 - Escape is an inverted zooming user interface (IZUI) design for a flexible window manager that makes possible a web user agent application than can have multiple entry points and a heterogeneous windowing ecosystem. 85 - 86 - IZUI vs ZUI 87 - 88 - * ZUIs navigate by starting from a known root and user navigates by zooming ever further in, and then back out 89 - * Escape can enter a window stack at any point, and via a variety of methods, often from outside the application 90 - * Instead of navigating by zooming in, all interfaces can zoom out to go back, using the Escape key 91 - * This design allows unbounded and diverse entry points, but with predictable behavior 92 - * Regardless of the entry point, the user always has a consistent path to familiar ground 93 - 94 - Escape navigation model 95 - * navigation base can start at any level in stack 96 - * forward navigations are added on top of stack 97 - * backwards navigations walk the stack in reverse up the tree to the root 98 - 99 - ## Architecture / Implementation 100 - 101 - About this space: 102 - 103 - - Web pages can themselves be navigators of the web 104 - - Embrace the app-ness of the web platform, as a way to efficiently access the document-ness 105 - - Decouple html+js+css from http+dns+ssl - not entirely, but that trust+security model is not a required starting point 106 - - Javascript is ok here 107 - 108 - Peek is designed to be modular and configurable around the idea that parts of it 109 - can run in different environments. 110 - 111 - For example: 112 - - Planning on a mobile app which syncs and runs your peeks/slides/scripts 113 - - I'd like to have a decentralized compute option for running your scripts outside of your clients and syncing the data 114 - - Want cloud storage for all config and data, esp infinite history, so can do fun things with it 115 - 116 - ### Feature extensibility 117 - 118 - An extensibility model for achieving "personal web workbench" requires a few things: 119 - - UI extensibility requires OS-level window features beyond what the web allows today (also a baby step towards a minimal OS user interface) 120 - - Data harvest/transform/process/publish requires a method of moving data between features (web apps) *locally*, cf Web Actions/Intents/Applets, MCP, pubsub, MQTT etc 121 - - Portable ways of accessing network, storage and compute, which address 122 - 123 - The current implementation has only a few sketches of that world implemented, and has gone through a few iterations: 124 - - first proof of concept was all Electron - so, privileged JS 125 - - 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 126 - - third and current implementation bundles all features into one web app, with access to smallest possible custom API for platform-level capabilities 127 - 128 - 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. 129 - 130 - This is not ideal, as the extensibility vector is contributions to core, which too tightly bounds experimentation and innovation. 131 - 132 - However it's pretty portable given the small custom API surface area. 133 - 134 - It would be nice, but not required, to have some alignment with the WebExtension spec - blur your eyes and they're in a similar direction. 135 - 136 - ### Peek API 137 - 138 - Initially the prototype was all Electron. But that's not interesting, and doesn't 139 - really tell us anything about constraints of the web itself. 140 - 141 - So instead I asked this question: What's the minimum capability set that a web app would 142 - need to build the features I need? 13 + - **Peeks** - Keyboard-activated modal web pages (`Opt+0-9`) 14 + - **Slides** - Gesture-activated pages that slide in from screen edges (`Opt+arrows`) 15 + - **Scripts** - Background page monitors that extract and track data 16 + - **Commands** - Command palette for opening pages and executing actions 17 + - **Groups** - Tag-based page organization (like Firefox Panorama) 18 + - **Sync** - Cross-device sync between desktop, mobile, and server 143 19 144 - The answer, so far, is giving `peek` apps the following APIs: 20 + ## Quick Start 145 21 146 - - window open/close 147 - - global hotkey registration 148 - - pubsub messaging 22 + ```bash 23 + # Requirements: Node.js 24+ 24 + nvm use 24 149 25 150 - Custom window api might be able to away entirely, by passing window.open features, working on that. 151 - 152 - ### Desktop App 153 - 154 - Proof of concept is Electron. By far the best option today for cross-platform desktop apps which need a web rendering engine. There's really nothing else remotely suited (yet). 155 - 156 - User interface: 157 - - the built-in features are all modal chromeless web pages at this point 158 - - settings UI uses custom sidebar navigation with dark mode support 159 - 160 - TODO 161 - - Need to look at whether could library-ize some of what Agregore implemented for non-HTTP protocol support. 162 - - Min browser might be interesting as a forkable base to work from and contribute to, if they're open to it. At least, should look more at the architecture. 163 - 164 - ## Contribution 165 - 166 - - in proto stage 167 - - all dragons, no promises 168 - 169 - ## Development 170 - 171 - ``` 26 + # Install and run 172 27 yarn install 173 - yarn debug 28 + yarn debug # Development mode with devtools 29 + yarn start # Normal mode 174 30 ``` 175 31 176 - ### Mobile 177 - 178 - - Quick access to Script output and manual runs, as widgets (or output from cloud runners?) 179 - - Peeks still totes useful here - on mobile is more like "quick dial" features 180 - 181 - ### Cloud 182 - 183 - - Going full crypto payments for distributed compute on this one. 184 - 185 - ## Papercut / use-case log 186 - 187 - Core high level actions 188 - - open a web page on top/bottom/left/right 189 - - keep web pages persistent in the background 190 - - quickly open a web page modally, and close it 191 - 192 - Misc specific 193 - - open bandcamp in a window, move over to 2nd display, accidently close it while moving around between other windows 194 - - recent books or recipes from newsletters i subscribe to (but probably didn't read) 195 - - extract a table from a page periodically, send it somewhere as csv or whatever (chained actions) 196 - - collect microformats, metadata, events 197 - - web page w/ some locations as an input to a map (creates overlay) "map this page" 198 - - be able to see where a book/etc recommendation came from 199 - - save a tweet, with URL / image / relevant text, but not whole page webrecorder style 200 - - "watch local event listings, rate against my music listening patterns and send me shows i might be interested in going to" 201 - 202 - Content scripts 203 - - extract+log shazams 204 - - extract+log spotify playlist 205 - 206 - Calculators (variant of script + cmd?) 207 - - page -> table 208 - - page -> summary 209 - - page -> microsummaries 210 - - page -> dates 211 - - page -> events 212 - 213 - Workflow deconstructing a "why" task flavour of bookmarking 214 - - save https://www.criterionchannel.com/hong-kong-in-new-york 215 - - extract the movies 216 - - get reference metadata for each (?!) 217 - - add to "to watch list", with pointer back to source url 218 - 219 - ## Groups 220 - 221 - - panorama/tabcandy-ish 222 - - all browser history 223 - - smart groups vs curated groups 224 - - autoclustering on topic/date 225 - - escape from a new page enters default group? 226 - 227 - Groups + Cmds 228 - - top/bottom inputs for filtering/grouping/etc 229 - - implemented is a cmd input? 230 - - cmds for opening/searching/finding/viewing/filtering/piping 231 - - cmds for moving pages into groups 232 - - groups -> {x} (eg export/pipe) could depend on the chaining/piping bit (see below) 233 - 234 - Architecture 235 - - internally is tags? 236 - - static vs dynamic groups tho? 237 - 238 - Publishing 239 - - publishing groups as internal/public feeds? 240 - - to pinboard? 241 - 242 - ## History view/search 243 - 244 - A lot of groups work depends on history being in place, and being accessable and annotate-able. 245 - 246 - ideally use chromium history 247 - 248 - storage+access 249 - - check out Agregore history viewing approach 250 - - check out state of electron+webext 251 - - other way of accessing underlying chromium history? 252 - 253 - features 254 - - awesomebar algo scoring 255 - - adaptive matching 256 - 257 - ## Chaining / piping 258 - 259 - investigate: vague thought re chaining: 260 - - dynamic interstitial representations 261 - - mime type detection? 262 - - eg image previews 263 - - or a table of data 264 - - previews of cmds? 265 - 266 - interfaces 267 - - horizontal vs vertical chains 268 - - back/forward navigation? 269 - - each step is a cmd+preview? 270 - - dynamic cmd+previews? 271 - 272 - import/export/undo/redo 273 - - record/replay? 274 - - save a chain as a compound action (cmd)? 275 - 276 - architecture 277 - - look at web actions/intents/applets 278 - - xml pipeline language 279 - 280 - ## Mobile 281 - 282 - - some of the features don't make sense as-is on mobile 283 - - but maybe quick access on mobile to slides/peeks would be nice 284 - - and seeing output of content scripts, or ability to re-run locally on demand 285 - - needs some sync facility (inevitable anyway) 286 - 287 - ## Use-cases 288 - 289 - Peeks 290 - - translate 291 - - calendar 292 - - ai chat 293 - - currency conversion 294 - - everytimezone 295 - - tldraw 296 - 297 - Slides 298 - - music: Soundcloud, Hypem 299 - - stock prices 300 - - notepad 301 - - todo list 302 - 303 - Scripts 304 - - weather change, eg upcoming weather 305 - - crypto prices 306 - 307 - Cmd - web 308 - - open url 309 - - web search 310 - - image search 311 - - conversions? 312 - - ddg !actions 313 - 314 - Cmd - system 315 - - search browser history 316 - - set peeks/slides 317 - - open settings 318 - - restart app 319 - - llm prompts 320 - 321 - Future 322 - - address something to switch between 323 - - pipe from/to? 32 + See [DEVELOPMENT.md](DEVELOPMENT.md) for full development guide. 324 33 325 - Publishing high level 326 - - author web content 327 - - pull in bits from the web 328 - - share preview for feedback 329 - - publish (or at least get output) 34 + ## Architecture 330 35 331 - Publishing examples 332 - - writing an event recap 36 + Peek supports multiple backends sharing the same renderer code: 333 37 334 - Publishing: event recap post 335 - - make a new markdown doc 336 - - sections titled for each video title 337 - - each video's embed code in each section 338 - - navigate around the document for review and updates 339 - - need to easily preview rendered content 340 - - share preview link 341 - - publish (somewhere?) 38 + ``` 39 + peek/ 40 + ├── app/ # Renderer (backend-agnostic) 41 + ├── extensions/ # Built-in extensions 42 + ├── backend/ 43 + │ ├── electron/ # Desktop (primary) 44 + │ ├── tauri/ # Desktop (Rust alternative) 45 + │ ├── tauri-mobile/ # iOS/Android 46 + │ └── server/ # Sync server (Node.js/Hono) 47 + └── docs/ # Documentation 48 + ``` 342 49 343 - Music 344 - - commands 345 - - views 346 - - last.fm of my own, to POSSE out 50 + ## Documentation 347 51 348 - ## Unfiled 52 + | Doc | Description | 53 + |-----|-------------| 54 + | [DEVELOPMENT.md](DEVELOPMENT.md) | Development setup, commands, architecture | 55 + | [docs/api.md](docs/api.md) | Peek API reference (`window.app`) | 56 + | [docs/extensions.md](docs/extensions.md) | Extension development | 57 + | [docs/datastore.md](docs/datastore.md) | Data storage and schema | 58 + | [docs/sync.md](docs/sync.md) | Cross-device sync | 59 + | [docs/MOBILE.md](docs/MOBILE.md) | Mobile development | 349 60 350 - markdown hot reload previewer w/ toc 351 - - markdown support, with sidebar nav 352 - - reader mode 353 - - hot reload for file:// (other?) 354 - - add side-by-side view 355 - - once md and side-by-side, add side-by-side so the md is the nav, content is the preview 356 - - what's the cmd chain for this? 61 + ## Design Philosophy 357 62 358 - content types + chaining 359 - - cmd: view as… table, feed, markdown, data points, named entities 360 - - chain: static archive, publish, save, share (os), mailto 361 - - cmd params, eg {url}, which can themselves autocomplete (eg history) 63 + - Web pages can be navigators of the web 64 + - User tasks on the web are transient, chained, persistent, or data-oriented - none well-served by tabbed browsers 65 + - The "Escape IZUI" pattern: enter at any point, ESC always returns to familiar ground 66 + - Minimum viable API surface for web apps to access platform capabilities 362 67 363 - multiprotocol 364 - - at 365 - - ipfs/ipns 366 - - pragmatic addressing+rendering for data (r/d/masl + mime handlers) 68 + See [notes/extensibility.md](notes/extensibility.md) for detailed design notes. 367 69 368 - broader patterns (chatting w/ luke) 369 - - why do we have to copy/paste? 370 - - devtools and ide are divorced 70 + ## Contributing 371 71 372 - chainframe/framechain 373 - - (web intents/applets/actions) + (webxdc/miniapps/tiles/farcasterframes) 72 + Concept stage - contributions welcome but expect dragons. 374 73 375 - Small examples of agency 74 + ## License 376 75 377 - - users can move, resize, change things to their requirements 378 - - eg, browsers restrict min-height of a window, but i should be able make as short as i like 379 - 380 - ## History 381 - 382 - In working on Firefox and related things at Mozilla from 2006 - 2019, there were a few specific initiatives which best aligned with my needs as a user on the web: 383 - 384 - - The Awesomebar: infinite history + personalized local search index 385 - - Ubiquity: Natural language commands + chaining 386 - - Jetpack: The Mozilla Labs version - web-platfrom-centric extensibility 387 - - Panorama: née TabCandy, web pages as groups instead of tabs in windows 388 - 389 - A few others which were in the right direction but didn't achieve their optimal form: 390 - 391 - - Greasemonkey 392 - - Microsummaries 393 - - Contacts extension 394 - 395 - The first version of the Peek application has some bits of each of these, and the original Peek browser extension. 396 - 397 - ### Peek browser extension 398 - 399 - Peek was a browser extension that let you quickly peek at your favorite web pages without breaking your flow - loading pages mapped to keyboard shortcuts into a modal window with no controls, closable via the `Escape` key. 400 - 401 - However, as browser extension APIs became increasingly limited, it was not possible to create a decent user experience and I abandoned it. You can access the extension in this repo [in the extension directory](/autonome/peek/extension/). 402 - 403 - The only way to create the ideal user experience for a web user agent that *Does What I Want* is to make it a browser-ish application, and that's what Peek is now. 404 - 405 - 406 - 76 + MIT
+3 -3
backend/README.md
··· 5 5 ## Design Principles 6 6 7 7 1. **Backend Abstraction**: The `app/` directory contains all renderer code and must work unchanged with any backend 8 - 2. **Shared API Contract**: All backends expose the same Peek API (`window.app`) - see `docs/PEEK-API.md` 8 + 2. **Shared API Contract**: All backends expose the same Peek API (`window.app`) - see `docs/api.md` 9 9 3. **Shared Data**: Backends use the same SQLite schema and can share database files 10 10 4. **Profile Isolation**: Data is separated by profile (dev, default, etc.) 11 11 ··· 72 72 Database location: `{app_data}/{profile}/datastore.sqlite` 73 73 74 74 ### 4. Peek API Injection 75 - Inject the `window.app` API before any page scripts run. See `docs/PEEK-API.md` for the complete API reference. 75 + Inject the `window.app` API before any page scripts run. See `docs/api.md` for the complete API reference. 76 76 77 77 ## API Contract 78 78 ··· 185 185 2. Implement custom protocol handler for `peek://` 186 186 3. Implement SQLite datastore with shared schema 187 187 4. Implement window management commands 188 - 5. Implement the Peek API (`window.app`) matching `docs/PEEK-API.md` 188 + 5. Implement the Peek API (`window.app`) matching `docs/api.md` 189 189 6. Inject the API before page scripts run 190 190 191 191 The renderer code (`app/`) should work without modification.
docs/Peek-API.md docs/api.md
+180
docs/datastore.md
··· 1 + # Peek Datastore 2 + 3 + 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). 4 + 5 + ## Architecture 6 + 7 + All data operations are handled in the main/backend process, with renderer processes accessing via IPC through `window.app.datastore`. 8 + 9 + ``` 10 + Renderer Process Main/Backend Process 11 + ┌─────────────────┐ ┌─────────────────────┐ 12 + │ window.app. │ IPC invoke() │ IPC Handlers │ 13 + │ datastore.* │───────────────▶│ ├─ add-item │ 14 + │ │ │ ├─ query-items │ 15 + │ │ │ ├─ add-visit │ 16 + │ │ │ └─ ... │ 17 + └─────────────────┘ │ │ │ 18 + │ ▼ │ 19 + │ ┌─────────────┐ │ 20 + │ │ SQLite │ │ 21 + │ │ Database │ │ 22 + │ └─────────────┘ │ 23 + └─────────────────────┘ 24 + ``` 25 + 26 + ### Why IPC-Based? 27 + 28 + 1. **Backend Portability** - Same renderer code works with Electron, Tauri, or mobile 29 + 2. **Storage Flexibility** - Can swap SQLite for other backends without renderer changes 30 + 3. **Security** - Datastore logic in trusted main process 31 + 4. **Sync Readiness** - Same API can route to local or remote storage 32 + 33 + ## Database Schema 34 + 35 + Location: `{app_data}/{profile}/datastore.sqlite` 36 + 37 + ### Core Tables 38 + 39 + #### `items` - Unified Item Storage 40 + Stores all user content types: URLs, text notes, tagsets, images. 41 + 42 + | Column | Type | Description | 43 + |--------|------|-------------| 44 + | id | TEXT | Primary key (UUID) | 45 + | type | TEXT | `url`, `text`, `tagset`, `image` | 46 + | content | TEXT | The actual content (URL, note text, etc.) | 47 + | title | TEXT | Display title | 48 + | metadata | TEXT | JSON for flexible extra data | 49 + | createdAt | TEXT | ISO timestamp | 50 + | updatedAt | TEXT | ISO timestamp | 51 + | syncedAt | TEXT | Last sync timestamp | 52 + | sync_id | TEXT | Server-assigned ID for sync | 53 + 54 + #### `visits` - Navigation History 55 + Tracks page visits with timing and context. 56 + 57 + | Column | Type | Description | 58 + |--------|------|-------------| 59 + | id | TEXT | Primary key | 60 + | addressId | TEXT | FK to items | 61 + | timestamp | INTEGER | Unix timestamp (ms) | 62 + | duration | INTEGER | Time spent (ms) | 63 + | source | TEXT | `peek`, `slide`, `direct`, `link` | 64 + | windowType | TEXT | `modal`, `persistent`, `main` | 65 + 66 + #### `tags` - Tag Definitions 67 + | Column | Type | Description | 68 + |--------|------|-------------| 69 + | id | TEXT | Primary key | 70 + | name | TEXT | Tag name (unique) | 71 + | frecency | REAL | Usage frequency score | 72 + 73 + #### `item_tags` - Tag Associations 74 + | Column | Type | Description | 75 + |--------|------|-------------| 76 + | item_id | TEXT | FK to items | 77 + | tag_id | TEXT | FK to tags | 78 + 79 + #### `extension_settings` - Extension Config 80 + Key-value storage for extension and core settings. 81 + 82 + | Column | Type | Description | 83 + |--------|------|-------------| 84 + | key | TEXT | Setting key (e.g., `sync.serverUrl`) | 85 + | value | TEXT | JSON-encoded value | 86 + 87 + ## API Reference 88 + 89 + Access via `window.app.datastore` in any `peek://` page. 90 + 91 + ### Items 92 + 93 + ```javascript 94 + // Add item 95 + const result = await api.datastore.addItem({ 96 + type: 'url', 97 + content: 'https://example.com', 98 + title: 'Example', 99 + tags: ['bookmark', 'work'] 100 + }); 101 + 102 + // Query items 103 + const urls = await api.datastore.queryItems({ type: 'url' }); 104 + const tagged = await api.datastore.queryItems({ tag: 'bookmark' }); 105 + 106 + // Update item 107 + await api.datastore.updateItem(id, { title: 'New Title' }); 108 + 109 + // Delete item 110 + await api.datastore.deleteItem(id); 111 + ``` 112 + 113 + ### Tags 114 + 115 + ```javascript 116 + // Get or create tag 117 + const tag = await api.datastore.getOrCreateTag('bookmark'); 118 + 119 + // Tag an item 120 + await api.datastore.tagItem(itemId, tagId); 121 + 122 + // Untag 123 + await api.datastore.untagItem(itemId, tagId); 124 + 125 + // Get item's tags 126 + const tags = await api.datastore.getItemTags(itemId); 127 + 128 + // Get items by tag 129 + const items = await api.datastore.getItemsByTag(tagId); 130 + ``` 131 + 132 + ### Visits 133 + 134 + ```javascript 135 + // Record visit 136 + await api.datastore.addVisit(addressId, { 137 + source: 'peek', 138 + windowType: 'modal' 139 + }); 140 + 141 + // Query visits 142 + const history = await api.datastore.queryVisits({ 143 + limit: 100, 144 + offset: 0 145 + }); 146 + ``` 147 + 148 + ### Settings 149 + 150 + ```javascript 151 + // Get setting 152 + const value = await api.datastore.getSetting('sync.serverUrl'); 153 + 154 + // Set setting 155 + await api.datastore.setSetting('sync.serverUrl', 'https://...'); 156 + ``` 157 + 158 + ### Stats 159 + 160 + ```javascript 161 + const stats = await api.datastore.getStats(); 162 + // Returns: { items: 150, visits: 1200, tags: 25 } 163 + ``` 164 + 165 + ## Sync 166 + 167 + The datastore syncs between backends via the server API: 168 + 169 + 1. **Desktop ↔ Server**: Bidirectional sync in `backend/electron/sync.ts` 170 + 2. **Mobile → Server**: Push sync from mobile app 171 + 3. **Conflict Resolution**: Last-write-wins based on `updatedAt` 172 + 173 + See `docs/sync.md` for sync architecture details. 174 + 175 + ## Files 176 + 177 + - `backend/electron/datastore.ts` - Electron SQLite implementation 178 + - `backend/tauri/src-tauri/src/datastore.rs` - Tauri SQLite implementation 179 + - `backend/server/db.js` - Server SQLite implementation 180 + - `app/datastore/` - Shared schema and helpers
+347
docs/extensions.md
··· 1 + # Peek Extensions 2 + 3 + Extensions are isolated modules that communicate with the core app via IPC and pubsub messaging. 4 + 5 + ## Hybrid Extension Architecture 6 + 7 + Peek uses a **hybrid extension loading model** that balances memory efficiency with crash isolation: 8 + 9 + ### Built-in Extensions (Consolidated) 10 + Built-in extensions (`cmd`, `groups`, `peeks`, `slides`) run as **iframes in a single extension host window**: 11 + - Share a single Electron BrowserWindow process 12 + - Memory efficient (~80-120MB vs ~200-400MB for separate windows) 13 + - Origin isolation via unique URL hosts (`peek://cmd/`, `peek://groups/`, etc.) 14 + - If one crashes, others in the same host are affected 15 + 16 + ### External Extensions (Separate Windows) 17 + External extensions (including `example` and user-installed) run in **separate BrowserWindows**: 18 + - Each has its own Electron process 19 + - Crash isolation - one extension crashing doesn't affect others 20 + - Uses `peek://ext/{id}/` URL scheme 21 + - Better for untrusted or experimental extensions 22 + 23 + ``` 24 + ┌─────────────────────────────────────────────────────────────┐ 25 + │ Extension Host Window (peek://app/extension-host.html) │ 26 + │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────┐ │ 27 + │ │ <iframe> │ │ <iframe> │ │ <iframe> │ │<iframe> │ │ 28 + │ │ peek://cmd/ │ │peek://groups│ │peek://peeks/│ │peek:// │ │ 29 + │ │ │ │ │ │ │ │slides/ │ │ 30 + │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────┘ │ 31 + └─────────────────────────────────────────────────────────────┘ 32 + 33 + ┌─────────────────┐ ┌─────────────────┐ 34 + │ BrowserWindow │ │ BrowserWindow │ 35 + │ peek://ext/ │ │ peek://ext/ │ 36 + │ example/ │ │ user-ext/ │ 37 + │ (separate proc) │ │ (separate proc) │ 38 + └─────────────────┘ └─────────────────┘ 39 + ``` 40 + 41 + ### Origin Isolation 42 + 43 + Each extension gets a unique origin regardless of loading mode: 44 + - Built-in: `peek://cmd/background.html` → origin `peek://cmd` 45 + - External: `peek://ext/example/background.html` → origin `peek://ext` 46 + 47 + This prevents cross-extension access to localStorage, DOM, and globals. 48 + 49 + ## Extension Structure 50 + 51 + Each extension lives in its own directory under `extensions/`: 52 + 53 + ``` 54 + extensions/ 55 + example/ 56 + manifest.json # Extension metadata 57 + settings-schema.json # Settings UI schema (optional) 58 + background.html # Entry point (loads background.js) 59 + background.js # Main extension logic 60 + *.html, *.js, *.css # Additional UI files 61 + ``` 62 + 63 + ### manifest.json 64 + 65 + Required fields: 66 + ```json 67 + { 68 + "id": "example", 69 + "shortname": "example", 70 + "name": "Example Extension", 71 + "description": "What this extension does", 72 + "version": "1.0.0", 73 + "background": "background.html" 74 + } 75 + ``` 76 + 77 + Optional fields: 78 + ```json 79 + { 80 + "builtin": true, 81 + "settingsSchema": "./settings-schema.json" 82 + } 83 + ``` 84 + 85 + ### settings-schema.json 86 + 87 + Defines the settings UI for the extension. Used by Settings to render configuration forms. 88 + 89 + ```json 90 + { 91 + "prefs": { 92 + "type": "object", 93 + "properties": { 94 + "greeting": { 95 + "type": "string", 96 + "description": "Custom greeting message", 97 + "default": "Hello World" 98 + } 99 + } 100 + }, 101 + "storageKeys": { 102 + "PREFS": "prefs" 103 + }, 104 + "defaults": { 105 + "prefs": { 106 + "greeting": "Hello World" 107 + } 108 + } 109 + } 110 + ``` 111 + 112 + For extensions with list-based settings (like peeks/slides), add an `item` schema: 113 + ```json 114 + { 115 + "prefs": { ... }, 116 + "item": { 117 + "type": "object", 118 + "properties": { 119 + "title": { "type": "string", "title": "Title" }, 120 + "enabled": { "type": "boolean", "title": "Enabled" } 121 + } 122 + }, 123 + "storageKeys": { 124 + "PREFS": "prefs", 125 + "ITEMS": "items" 126 + }, 127 + "defaults": { 128 + "prefs": { ... }, 129 + "items": [] 130 + } 131 + } 132 + ``` 133 + 134 + ### background.html 135 + 136 + Entry point that loads the extension as an ES module: 137 + 138 + ```html 139 + <!DOCTYPE html> 140 + <html> 141 + <head> 142 + <meta charset="UTF-8"> 143 + <title>My Extension</title> 144 + </head> 145 + <body> 146 + <script type="module"> 147 + import extension from './background.js'; 148 + 149 + const api = window.app; 150 + const extId = extension.id; 151 + 152 + console.log(`[ext:${extId}] background.html loaded`); 153 + 154 + // Signal ready to main process 155 + api.publish('ext:ready', { 156 + id: extId, 157 + manifest: { 158 + id: extension.id, 159 + labels: extension.labels, 160 + version: '1.0.0' 161 + } 162 + }, api.scopes.SYSTEM); 163 + 164 + // Initialize extension 165 + if (extension.init) { 166 + console.log(`[ext:${extId}] calling init()`); 167 + extension.init(); 168 + } 169 + 170 + // Handle shutdown 171 + api.subscribe('app:shutdown', () => { 172 + if (extension.uninit) extension.uninit(); 173 + }, api.scopes.SYSTEM); 174 + 175 + api.subscribe(`ext:${extId}:shutdown`, () => { 176 + if (extension.uninit) extension.uninit(); 177 + }, api.scopes.SYSTEM); 178 + </script> 179 + </body> 180 + </html> 181 + ``` 182 + 183 + ### background.js 184 + 185 + Main extension logic as an ES module: 186 + 187 + ```javascript 188 + const api = window.app; 189 + 190 + const extension = { 191 + id: 'example', 192 + labels: { 193 + name: 'Example' 194 + }, 195 + 196 + init() { 197 + console.log('[example] init'); 198 + 199 + // Register commands 200 + api.commands.register({ 201 + name: 'my-command', 202 + description: 'Does something', 203 + execute: () => { 204 + console.log('Command executed!'); 205 + } 206 + }); 207 + 208 + // Register shortcuts 209 + api.shortcuts.register('Option+x', () => { 210 + console.log('Shortcut triggered!'); 211 + }); 212 + 213 + // Subscribe to events 214 + api.subscribe('some:event', (msg) => { 215 + console.log('Event received:', msg); 216 + }, api.scopes.GLOBAL); 217 + }, 218 + 219 + uninit() { 220 + console.log('[example] uninit'); 221 + api.commands.unregister('my-command'); 222 + api.shortcuts.unregister('Option+x'); 223 + } 224 + }; 225 + 226 + export default extension; 227 + ``` 228 + 229 + ## Extension API 230 + 231 + Extensions access the Peek API via `window.app`. See `docs/api.md` for the complete reference. 232 + 233 + Common APIs used by extensions: 234 + 235 + ### Commands 236 + ```javascript 237 + api.commands.register({ name, description, execute }) 238 + api.commands.unregister(name) 239 + ``` 240 + 241 + ### Shortcuts 242 + ```javascript 243 + api.shortcuts.register(shortcut, callback) // e.g., 'Option+1' 244 + api.shortcuts.unregister(shortcut) 245 + ``` 246 + 247 + ### Pubsub Messaging 248 + ```javascript 249 + api.publish(topic, data, scope) 250 + api.subscribe(topic, callback, scope) 251 + 252 + // Scopes 253 + api.scopes.SELF // Only this window 254 + api.scopes.SYSTEM // System-level events 255 + api.scopes.GLOBAL // All windows 256 + ``` 257 + 258 + ### Windows 259 + ```javascript 260 + api.window.open(url, options) 261 + // Options: modal, keepLive, transparent, height, width, key 262 + ``` 263 + 264 + ### Datastore 265 + ```javascript 266 + await api.datastore.getRow(table, id) 267 + await api.datastore.setRow(table, id, data) 268 + await api.datastore.deleteRow(table, id) 269 + await api.datastore.getTable(table) 270 + ``` 271 + 272 + ### Extension Settings 273 + ```javascript 274 + await api.extensions.getSettings(extId) 275 + await api.extensions.setSettings(extId, key, value) 276 + ``` 277 + 278 + ## Extension Loading 279 + 280 + ### Load Order and the cmd Extension 281 + 282 + The `cmd` extension is the command registry - all other extensions register their commands with it via `api.commands.register()`. Because of this dependency: 283 + 284 + 1. **cmd loads first** (sequential) - must be ready before other extensions register commands 285 + 2. **Other extensions load in parallel** - for faster startup 286 + 3. **cmd cannot be disabled** - it's required infrastructure, not optional functionality 287 + 288 + This is enforced in `isBuiltinExtensionEnabled()` which always returns `true` for cmd. 289 + 290 + ### Hybrid Loading Process 291 + 292 + Extensions are loaded in hybrid mode by `loadExtensions()` in `backend/electron/main.ts`: 293 + 294 + 1. **Create extension host window** - Single BrowserWindow at `peek://app/extension-host.html` 295 + 2. **Load built-in extensions as iframes** - `cmd`, `groups`, `peeks`, `slides` loaded via IPC into the host 296 + 3. **Load external extensions as separate windows** - Each gets its own BrowserWindow 297 + 298 + ```typescript 299 + // Which extensions use consolidated mode (defined in main.ts) 300 + const CONSOLIDATED_EXTENSION_IDS = ['cmd', 'groups', 'peeks', 'slides']; 301 + ``` 302 + 303 + ### Built-in Extensions 304 + 305 + Built-in extensions are registered in `index.js`: 306 + ```javascript 307 + registerExtensionPath('example', path.join(__dirname, 'extensions', 'example')); 308 + ``` 309 + 310 + Built-in extensions that are NOT in `CONSOLIDATED_EXTENSION_IDS` (like `example`) are treated as external and get separate windows. This is intentional - it exercises external extension code paths during development. 311 + 312 + ### External Extensions 313 + 314 + External extensions are: 315 + 1. Added via Settings UI (stored in datastore `extensions` table) 316 + 2. Loaded on startup if `enabled === 1` and have a valid `path` 317 + 3. Always run in separate BrowserWindows for crash isolation 318 + 319 + ## Settings Integration 320 + 321 + Extensions with `settingsSchema` in their manifest automatically get a settings section in the Settings UI. The schema is loaded at runtime when the extension window is created. 322 + 323 + Settings are stored in the `extension_settings` datastore table with: 324 + - `extensionId`: The extension's ID 325 + - `key`: Setting key (e.g., 'prefs', 'items') 326 + - `value`: JSON-encoded setting value 327 + 328 + Extensions can listen for settings changes: 329 + ```javascript 330 + api.subscribe(`${extId}:settings-changed`, (msg) => { 331 + // Reload configuration 332 + }, api.scopes.GLOBAL); 333 + ``` 334 + 335 + ## Lifecycle Events 336 + 337 + - `ext:ready` - Published when extension is initialized 338 + - `ext:all-loaded` - Published when all extensions finish loading 339 + - `app:shutdown` - Sent before app closes 340 + - `ext:{id}:shutdown` - Sent when specific extension is being unloaded 341 + - `{extId}:settings-changed` - Sent when extension settings are modified 342 + 343 + ## Debugging 344 + 345 + Console logs from extensions are forwarded to stdout with prefix `[ext:{id}]`. 346 + 347 + Run with `DEBUG=1 yarn start` for verbose logging.
+115
docs/sync.md
··· 1 + # Peek Sync 2 + 3 + Cross-platform data synchronization between mobile, desktop, and server. 4 + 5 + ## Architecture 6 + 7 + ``` 8 + ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ 9 + │ Mobile │────▶│ Server │◀────│ Desktop │ 10 + │ (Tauri) │ │ (Hono) │ │ (Electron) │ 11 + └─────────────┘ └─────────────┘ └─────────────┘ 12 + │ │ │ 13 + ▼ ▼ ▼ 14 + SQLite SQLite SQLite 15 + ``` 16 + 17 + The server acts as the source of truth. All clients push and pull data via the server's REST API. 18 + 19 + ## Item Types 20 + 21 + All platforms use the same unified types: 22 + 23 + | Type | Description | Content Field | 24 + |------|-------------|---------------| 25 + | `url` | Saved URLs/bookmarks | The URL string | 26 + | `text` | Text content/notes | The text content | 27 + | `tagset` | Tag-only items | null | 28 + | `image` | Binary images | Base64 data or filename | 29 + 30 + ## Sync Protocol 31 + 32 + ### Pull (Server → Client) 33 + 34 + 1. Fetch: `GET /items` (full) or `GET /items/since/:timestamp` (incremental) 35 + 2. For each server item: 36 + - Find local item by `syncId` matching server `id` 37 + - If not found: insert new item 38 + - If found and server is newer: update local 39 + - If local is newer: skip (will push later) 40 + 41 + ### Push (Client → Server) 42 + 43 + 1. Query items where `syncSource = ''` OR `updatedAt > lastSyncTime` 44 + 2. For each item: `POST /items` with type, content, tags 45 + 3. On success: update local `syncId` and `syncSource` 46 + 47 + ### Conflict Resolution 48 + 49 + **Last-write-wins** based on `updatedAt` timestamp. 50 + 51 + ## API Endpoints 52 + 53 + ### Server 54 + 55 + ``` 56 + GET /items # All items 57 + GET /items/since/:timestamp # Items modified after timestamp 58 + GET /items/:id # Single item 59 + POST /items # Create item 60 + PATCH /items/:id/tags # Update tags 61 + DELETE /items/:id # Delete item 62 + ``` 63 + 64 + ### Desktop IPC 65 + 66 + ```javascript 67 + await window.app.sync.getConfig() // Get server URL, API key 68 + await window.app.sync.setConfig(cfg) // Save config 69 + await window.app.sync.pull() // Pull from server 70 + await window.app.sync.push() // Push to server 71 + await window.app.sync.full() // Full bidirectional sync 72 + ``` 73 + 74 + ## Configuration 75 + 76 + ### Desktop 77 + 78 + Sync settings stored in `extension_settings` table: 79 + - `sync.serverUrl` - Server URL 80 + - `sync.apiKey` - API key 81 + - `sync.lastSyncTime` - Last sync timestamp 82 + - `sync.autoSync` - Enable auto-sync 83 + 84 + ### Mobile 85 + 86 + Settings keys in Tauri: 87 + - `webhook_url` - Server URL 88 + - `webhook_api_key` - API key 89 + 90 + ## Known Limitations 91 + 92 + ### Deletes Not Synced (HIGH) 93 + 94 + Deleted items are local-only. Items may "resurrect" on other devices after sync. 95 + 96 + **Current behavior:** 97 + 1. Delete on desktop sets `deletedAt` locally 98 + 2. Deleted items excluded from push query 99 + 3. Server never learns about deletion 100 + 4. Other devices still see the item 101 + 102 + **Workaround:** Delete on all devices manually. 103 + 104 + ### Push Failures Not Retried (HIGH) 105 + 106 + Failed push operations are logged but not retried. After sync, `lastSyncTime` advances, so failed items won't be picked up on next sync. 107 + 108 + **Workaround:** Manually trigger sync if network issues occurred. 109 + 110 + ## Files 111 + 112 + - `backend/electron/sync.ts` - Desktop sync implementation 113 + - `backend/server/db.js` - Server database with sync support 114 + - `backend/tauri-mobile/src-tauri/src/commands/sync.rs` - Mobile sync 115 + - `notes/sync-edge-cases.md` - Detailed edge case documentation
+1 -1
extensions/README.md
··· 228 228 229 229 ## Extension API 230 230 231 - Extensions access the Peek API via `window.app`. See `docs/PEEK-API.md` for the complete reference. 231 + Extensions access the Peek API via `window.app`. See `docs/api.md` for the complete reference. 232 232 233 233 Common APIs used by extensions: 234 234
notes/datastore-architecture.md notes/research/datastore-architecture.md
notes/datastore-integration.md notes/research/datastore-integration.md
notes/datastore-research.md notes/research/datastore-research.md
notes/datastore-schema.md notes/research/datastore-schema.md
notes/datastore.md notes/research/datastore.md
-402
notes/development.md
··· 1 - # Peek Development Guide 2 - 3 - ## Project Overview 4 - 5 - 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. 6 - 7 - **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}/`. 8 - 9 - ## Requirements 10 - 11 - - **Node.js 24+** - Required for both desktop (Electron 40) and server 12 - - Install via `nvm install 24` or download from nodejs.org 13 - 14 - ## Key Commands 15 - 16 - ### Development 17 - ```bash 18 - nvm use 24 # Ensure Node 24 is active 19 - yarn install # Install dependencies 20 - yarn debug # Run in development mode (with devtools) 21 - yarn start # Start normally 22 - yarn package # Package (output: out/mac-arm64/) 23 - yarn package:install # Package and install to /Applications (macOS) 24 - yarn make # Build distributable packages 25 - yarn npm audit # Check for security vulnerabilities 26 - 27 - # Tauri backend 28 - yarn tauri:dev # Run Tauri in development 29 - yarn tauri:build # Build Tauri for production 30 - yarn tauri:check # Check Tauri compiles 31 - yarn tauri:test # Run Tauri smoke tests (Rust) 32 - 33 - # Tauri Mobile (iOS/Android) 34 - yarn tauri:ios:dev # Run on iOS simulator 35 - yarn tauri:ios:build # Build for iOS 36 - yarn tauri:ios:xcode # Open Xcode project 37 - yarn tauri:android:dev # Run on Android emulator 38 - 39 - # Server (webhook API for mobile sync) 40 - yarn server:install # Install server dependencies 41 - yarn server:start # Run production server 42 - yarn server:dev # Run with hot reload 43 - yarn server:test # Run server tests 44 - yarn server:healthcheck # Verify server starts 45 - ``` 46 - 47 - ### Testing 48 - ```bash 49 - yarn test # Run all tests (headless) 50 - yarn test:electron # Run Electron tests only 51 - yarn test:visible # Run with visible windows 52 - yarn test:grep "pattern" # Run specific test by name 53 - ``` 54 - 55 - **Testing Policy**: When fixing bugs or adding features, always add tests to cover the change. Tests live in `tests/desktop/smoke.spec.ts`. 56 - 57 - ## Architecture Overview 58 - 59 - ### Core Structure 60 - 61 - 1. **Main Process** (`index.js`): 62 - - Manages app lifecycle, windows, shortcuts, IPC communication 63 - - Implements custom `peek://` protocol for internal navigation 64 - - Handles profile management and data persistence 65 - - Hosts the TinyBase datastore with IPC handlers for renderer access 66 - 67 - 2. **Renderer Process** (`app/`): 68 - - Core app logic loads from `peek://app/background.html` 69 - - Extension loader at `app/extensions/loader.js` 70 - - Feature modules: scripts, cmd (registered in `app/features.js`) 71 - - Built-in extensions: groups, peeks, slides (in `./extensions/`) 72 - - Settings UI at `peek://app/settings/settings.html` 73 - 74 - 3. **Peek API** (`window.app`): 75 - - `api.window.*` for window management 76 - - `api.shortcuts.*` for hotkeys (local by default, `{ global: true }` for OS-level) 77 - - `api.datastore.*` for data persistence 78 - - `api.commands.*` for command palette integration 79 - - `api.theme.*` for theme management 80 - - `api.publish()`/`api.subscribe()` for cross-window messaging 81 - 82 - ### Backend Structure 83 - ``` 84 - backend/ 85 - ├── electron/ # Electron desktop backend (current primary) 86 - │ ├── datastore.ts # SQLite via better-sqlite3 87 - │ ├── ipc.ts # IPC handlers 88 - │ └── protocol.ts # peek:// protocol handler 89 - ├── tauri/ # Tauri desktop backend (Rust) 90 - │ ├── src-tauri/ 91 - │ │ ├── src/ 92 - │ │ │ ├── lib.rs # App setup 93 - │ │ │ ├── datastore.rs # SQLite via rusqlite 94 - │ │ │ └── commands/ # IPC command handlers 95 - │ │ └── tests/smoke.rs # Rust smoke tests 96 - │ └── preload.js # Peek API implementation (Tauri) 97 - ├── tauri-mobile/ # Tauri mobile app (iOS/Android) 98 - │ ├── src/ # React frontend 99 - │ └── src-tauri/ # Rust backend 100 - └── server/ # Webhook API server for mobile sync 101 - ├── index.js # Hono HTTP server 102 - ├── db.js # SQLite via better-sqlite3 103 - └── users.js # Multi-user API key auth 104 - ``` 105 - 106 - **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). 107 - 108 - ### Custom Protocol 109 - - `peek://app/...` - Core application files (from `app/` directory) 110 - - `peek://ext/{shortname}/...` - Extension files 111 - - `peek://tauri/...` - Tauri backend files (only in Tauri) 112 - 113 - ### Extensions Architecture 114 - 115 - Extensions run in isolated BrowserWindow processes at `peek://ext/{id}/background.html`. 116 - 117 - ``` 118 - extensions/ 119 - ├── example/ # Hello world example 120 - ├── groups/ 121 - ├── peeks/ 122 - └── slides/ 123 - ├── manifest.json # Extension metadata 124 - ├── settings-schema.json # Settings UI schema (optional) 125 - ├── background.html # Entry point 126 - └── background.js # Main logic (ES module export) 127 - ``` 128 - 129 - **Background script pattern:** 130 - ```javascript 131 - const api = window.app; 132 - 133 - const extension = { 134 - id: 'example', 135 - labels: { name: 'Example' }, 136 - 137 - init() { 138 - api.shortcuts.register('Option+x', handler, { global: true }); 139 - api.commands.register({ name: 'my-cmd', description: '...', execute: fn }); 140 - }, 141 - 142 - uninit() { 143 - api.shortcuts.unregister('Option+x', { global: true }); 144 - api.commands.unregister('my-cmd'); 145 - } 146 - }; 147 - 148 - export default extension; 149 - ``` 150 - 151 - ### Window Management 152 - - Windows identified by keys for lifecycle management (e.g., `peek:${address}`) 153 - - Modal windows use `type: 'panel'` to return focus to previous app on close 154 - - Parameters: `modal`, `keepLive`, `persistState`, `transparent`, `height`, `width`, `key` 155 - - "Escape IZUI" design - ESC key always returns to previous context 156 - 157 - ### Data Storage 158 - 159 - **Settings Storage (localStorage)**: 160 - - Profile-based data separation in `{userData}/{PROFILE}/` directory 161 - - Features use `openStore(id, defaults, clear)` utility from `app/utils.js` 162 - 163 - **Datastore (TinyBase)**: 164 - - In-memory TinyBase store for structured data 165 - - Runs in main process with IPC handlers for renderer access 166 - - Schema defined in `app/datastore/schema.js` 167 - - Tables: `addresses`, `visits`, `content`, `tags`, `blobs`, `scripts_data`, `feeds` 168 - 169 - **Profile Management**: 170 - - Packaged app uses `default` profile 171 - - Running from source (`yarn start`) uses `dev` profile 172 - - `PROFILE` env var overrides automatic detection 173 - 174 - ## Common Pitfalls 175 - 176 - 1. Don't use relative paths in peek:// URLs - use absolute paths 177 - 2. Remember to unregister shortcuts when features unload 178 - 3. Windows opened by features should be tracked and closed on unload 179 - 4. Modal windows require both `modal: true` and `type: 'panel'` 180 - 5. Window keys must be unique - use pattern like `peek:${address}` 181 - 6. Check if items are enabled (`item.enabled == true`) before registering shortcuts 182 - 7. Datastore API returns `{ success, data }` - always check `result.success` 183 - 8. Pubsub subscriptions are keyed by source - same source subscribing twice overwrites 184 - 9. **Never put backend-specific code in `app/`** 185 - 10. **Run `yarn install` after Electron upgrades** - Native modules (better-sqlite3) must be recompiled for the new Electron ABI. The postinstall script handles this automatically, but you must run `yarn install` to trigger it. System Node and Electron have different ABIs even at the same major version (e.g., Node 24 = ABI 137, Electron 40 = ABI 143). 186 - 187 - ## Code Style 188 - 189 - - ES6 modules throughout (type: "module" in package.json) 190 - - Async/await preferred over callbacks 191 - - Console logging for debugging (controlled by DEBUG env var) 192 - - No TypeScript in app/, pure JavaScript 193 - - Uses `nodemon` for hot reload during development 194 - 195 - ## Security Notes 196 - 197 - - This is a concept preview, NOT production-ready 198 - - No formal security audit performed 199 - - Different security model than traditional browsers 200 - - Be cautious with cross-origin access and custom APIs 201 - 202 - ## Mobile Development (Tauri iOS/Android) 203 - 204 - Mobile development uses the separate `peek-save` app in `backend/tauri-mobile/`. 205 - 206 - **CRITICAL - Build with Xcode GUI for iOS:** 207 - - NEVER run `xcodebuild` commands from terminal for final builds 208 - - Use Xcode GUI (Product → Build, Product → Run) for reliable builds 209 - 210 - **CRITICAL - Do NOT run `xcodegen generate`:** 211 - - The Xcode project has custom settings that xcodegen overwrites 212 - 213 - ### Quick Start (One Command) 214 - 215 - The easiest way to set up mobile development with sync testing: 216 - 217 - ```bash 218 - cd backend/tauri-mobile 219 - 220 - # First time: build both debug and release Rust libraries 221 - npm run build # Build frontend 222 - cd src-tauri 223 - cargo build --target aarch64-apple-ios-sim # Debug (simulator) 224 - cargo build --target aarch64-apple-ios --release # Release (device) 225 - cd .. 226 - 227 - # Start everything (servers, configure iOS, seed test data, open Xcode) 228 - npm run dev:ios 229 - ``` 230 - 231 - `npm run dev:ios` does all of this automatically: 232 - 1. Resets server data for a clean slate 233 - 2. Starts backend server on a random port (10000-19999) 234 - 3. Starts frontend dev server on port 1420 235 - 4. Configures iOS simulator with server URL and API key 236 - 5. Copies both debug and release Rust libraries 237 - 6. Opens Xcode 238 - 7. Seeds test data (3 items on server, 3 on iOS) 239 - 240 - ### Available npm Scripts 241 - 242 - ```bash 243 - # Development 244 - npm run dev:ios # Full dev setup (servers + config + seed + Xcode) 245 - npm run xcode # Just copy libraries and open Xcode 246 - npm run seed # Seed test data (needs SERVER_URL and API_KEY env vars) 247 - npm run reset:server # Delete server data directory 248 - 249 - # Building 250 - npm run build # Build frontend (tsc + vite) 251 - npm run build:ios # Build frontend + debug Rust library 252 - npm run build:ios:release # Build frontend + release Rust library 253 - 254 - # Testing 255 - npm run test # Run integration tests 256 - npm run test:verbose # Run tests with verbose output 257 - ``` 258 - 259 - ### iOS Build Process (Manual) 260 - 261 - If you need to build manually instead of using `npm run dev:ios`: 262 - 263 - ```bash 264 - # 1. Build frontend 265 - cd backend/tauri-mobile && npm install && npm run build 266 - 267 - # 2. Build debug library (for simulator) 268 - cd src-tauri 269 - cargo tauri build --target aarch64-apple-ios-sim --debug 270 - mkdir -p gen/apple/Externals/arm64/Debug 271 - cp target/aarch64-apple-ios-sim/debug/deps/libpeek_save_lib.a gen/apple/Externals/arm64/Debug/libapp.a 272 - 273 - # 3. Build release library (for device) 274 - cargo tauri build --target aarch64-apple-ios 275 - mkdir -p gen/apple/Externals/arm64/Release 276 - cp target/aarch64-apple-ios/release/deps/libpeek_save_lib.a gen/apple/Externals/arm64/Release/libapp.a 277 - 278 - # 4. Create assets symlink (if missing) 279 - ln -s ../../../dist gen/apple/assets 280 - 281 - # 5. Open Xcode and build from GUI 282 - open gen/apple/peek-save.xcodeproj 283 - ``` 284 - 285 - ### Testing Bidirectional Sync 286 - 287 - After running `npm run dev:ios`, test data is automatically seeded: 288 - 289 - **Server has 3 items:** 290 - - URL: https://github.com/from-server (tags: server, github) 291 - - URL: https://example.com/from-server-1 (tags: server, test) 292 - - Text: "This is a text note from the server" (tags: server, note) 293 - 294 - **iOS Simulator has 3 items:** 295 - - URL: https://mobile-only-1.example.com (tags: mobile, local) 296 - - URL: https://mobile-news.example.com (tags: local) 297 - - Text: "This text was created on mobile only" (tags: mobile) 298 - 299 - **To test:** 300 - 1. Build and run in Xcode (Cmd+R) 301 - 2. Go to Settings in the app 302 - 3. Settings should already be configured (server URL and API key) 303 - 4. Tap **Sync All** to pull and push 304 - 5. After sync, both server and iOS should have 6 items 305 - 306 - **Gotchas:** 307 - - The `gen/apple/assets` symlink must exist or Xcode fails with "No such file or directory" 308 - - Debug scheme = simulator, Release scheme = device 309 - - If Rust code changes, rebuild the library and copy again 310 - - The "Build Rust Code" pre-build script in Xcode can hang indefinitely - pre-building avoids this 311 - - iOS simulator can't reach `localhost` - use Mac's IP address (dev:ios handles this automatically) 312 - 313 - ## Server Backend (Webhook API) 314 - 315 - 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. 316 - 317 - ### Server Commands 318 - ```bash 319 - # From project root 320 - yarn server:install # Install dependencies (first time) 321 - yarn server:start # Run production server (port 3000) 322 - yarn server:dev # Run with hot reload 323 - yarn server:test # Run unit tests (92 tests) 324 - yarn server:healthcheck # Verify server starts and responds 325 - 326 - # From backend/server/ 327 - npm install 328 - npm start 329 - npm run dev 330 - npm test 331 - npm run test:api:local # Test against local server (needs PEEK_LOCAL_KEY) 332 - npm run test:api:prod # Test against production (needs PEEK_PROD_KEY, PEEK_PROD_URL) 333 - ``` 334 - 335 - ### Server Architecture 336 - - **Hono** - Lightweight HTTP framework (like Express but faster) 337 - - **better-sqlite3** - SQLite database (same as Electron backend) 338 - - **Multi-user** - Each user gets isolated database in `./data/{userId}/` 339 - - **API key auth** - Bearer token authentication, keys hashed with SHA-256 340 - 341 - ### Server API Endpoints 342 - All endpoints except `/` require `Authorization: Bearer <api_key>` header. 343 - 344 - | Endpoint | Method | Description | 345 - |----------|--------|-------------| 346 - | `/` | GET | Health check (public) | 347 - | `/webhook` | POST | Receive items from mobile (`{ urls, texts, tagsets }`) | 348 - | `/urls` | GET | List saved URLs | 349 - | `/urls/:id` | DELETE | Delete URL | 350 - | `/urls/:id/tags` | PATCH | Update URL tags | 351 - | `/texts` | GET/POST | List or create texts | 352 - | `/tagsets` | GET/POST | List or create tagsets | 353 - | `/images` | GET/POST | List or upload images | 354 - | `/images/:id` | GET/DELETE | Get or delete image | 355 - | `/items` | GET/POST | Unified endpoint (filter with `?type=`) | 356 - | `/tags` | GET | List tags by frecency | 357 - 358 - ### Server Deployment (Railway) 359 - 360 - > **For the comprehensive deployment guide**, see the "Railway Deployment (Peek Server)" section in `CLAUDE.md`. This includes step-by-step workflow, user management, production testing, and troubleshooting. 361 - 362 - The server is configured for Railway deployment. 363 - 364 - **Initial Setup:** 365 - 1. Connect Railway to `backend/server/` subdirectory 366 - 2. Attach a persistent volume, set `DATA_DIR` env var to mount path 367 - 3. Create users via the `users.js` module 368 - 369 - **Deploying Updates:** 370 - ```bash 371 - # Link to project (one-time, from backend/server/) 372 - railway link -p <project-name> -s <service-name> -e production 373 - 374 - # Always run tests first 375 - npm test 376 - 377 - # Deploy 378 - railway up -d 379 - 380 - # Check logs 381 - railway logs -n 50 382 - 383 - # Health check 384 - curl https://peek-node.up.railway.app/ 385 - ``` 386 - 387 - **Deployment Order (Server + Mobile):** 388 - 1. **Server first** - stateless, has auto-migrations that run on first request 389 - 2. **Mobile second** - works offline, adapts to server changes 390 - 3. One-way sync only: mobile → server (no pull/download sync yet) 391 - 392 - **Migration Gotcha:** 393 - 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. 394 - 395 - ### Server Database Schema 396 - Different from desktop datastore - optimized for mobile sync: 397 - - `items` - Unified table (type: url/text/tagset/image) 398 - - `tags` - Tag names with frecency scoring 399 - - `item_tags` - Many-to-many junction 400 - - `settings` - Key-value config 401 - 402 - Images stored on disk in `./data/{userId}/images/` with content-hash deduplication.
notes/sync-architecture.md notes/research/sync-architecture.md
notes/sync-edge-cases.md notes/research/sync-edge-cases.md