experiments in a post-browser web
10
fork

Configure Feed

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

docs: add session restoration research and design document

Comprehensive design for session tracking and restoration across app restarts.

Covers:
- Current state analysis (what's persisted, what's not)
- Proposed architecture (database schema, save/restore flows)
- 6-week implementation plan
- Extension integration (app:before-shutdown hook)
- Privacy controls and crash recovery
- Testing strategy and API reference

Key innovations:
- Coordinated shutdown with extension hooks
- Graceful degradation (skip invalid windows)
- Privacy-first with URL blacklists
- Crash detection via cleanShutdown flag

Generated by exploration agent on 2026-02-09.

+499
+499
notes/research-session-restoration.md
··· 1 + # Session Tracking and Restoration Design Document 2 + 3 + ## Executive Summary 4 + 5 + This document outlines a comprehensive architecture for saving and restoring complete application state across Peek restarts, including open windows (URLs, positions, sizes), groups state, active pages/tabs, peek configurations, and extension-specific UI state. The system leverages Peek's existing datastore infrastructure with new session state persistence tables and coordination mechanisms. 6 + 7 + --- 8 + 9 + ## 1. Current State Analysis 10 + 11 + ### 1.1 Existing State Persistence 12 + 13 + **Currently Persisted:** 14 + - **Extension Settings** (`extension_settings` table): Key-value storage per extension via `api.settings.get()/set()` 15 + - **Context History** (`context_history` table): Time-series window context tracking (mode, metadata, timestamps) 16 + - **Chromium Session**: Browser cookies/cache/localStorage via profile-specific sessions 17 + - **Active Theme**: Theme preference persisted via `setActiveThemeId()` 18 + 19 + **NOT Currently Persisted:** 20 + - Open window list with URLs, positions, and sizes 21 + - Window focus state and z-order 22 + - Window relationships (parent/child modals) 23 + - Extension session contexts (active peeks, active groups) 24 + - Navigation history per window (back/forward stacks) 25 + - Scroll positions within content 26 + 27 + ### 1.2 Window Lifecycle 28 + 29 + **Current Flow:** 30 + 1. Background window created and extensions load 31 + 2. User opens windows via shortcuts/commands → stored in in-memory `windowRegistry` only 32 + 3. On app quit: `before-quit` handler runs cleanup, `windowRegistry` discarded 33 + 4. **Gap**: No save point before shutdown 34 + 35 + ### 1.3 Extension State Management 36 + 37 + **Current Pattern:** 38 + - Extensions load settings on init: `await api.settings.get()` 39 + - Extensions save changes individually: `await api.settings.set()` 40 + - No coordinated shutdown sequence 41 + - No recovery for partially-saved state 42 + 43 + --- 44 + 45 + ## 2. Proposed Architecture 46 + 47 + ### 2.1 Core Components 48 + 49 + **1. Session State Manager** (`backend/electron/session.ts`) 50 + - Coordinates session save/restore 51 + - Manages autosave timer 52 + - Handles version compatibility 53 + - Implements recovery logic 54 + 55 + **2. New Database Tables** 56 + - `session_state`: Complete session snapshots (versioned) 57 + - `window_sessions`: Per-window metadata 58 + - `extension_session_state`: Extension-specific state 59 + - `session_metadata`: Restore tracking & crash detection 60 + 61 + **3. Enhanced Shutdown Sequence** 62 + - New pubsub event: `app:before-shutdown` 63 + - Extensions get 500ms to save state 64 + - Window state captured 65 + - Session snapshot created before exit 66 + 67 + **4. Startup Restore Phase** 68 + - Runs after extensions load 69 + - Validates session compatibility 70 + - Restores windows with error recovery 71 + - Handles crash detection 72 + 73 + ### 2.2 Database Schema 74 + 75 + **`session_state` table:** 76 + ```sql 77 + id TEXT PRIMARY KEY -- UUID 78 + profileId TEXT NOT NULL -- Profile folder 79 + name TEXT -- 'autosave', 'before-quit', or user-provided 80 + state TEXT NOT NULL -- JSON blob of session state 81 + version INTEGER NOT NULL -- Schema version for compatibility 82 + appVersion TEXT -- App version that created this 83 + createdAt INTEGER NOT NULL -- When snapshot taken 84 + restoredAt INTEGER -- When/if restored (NULL if never) 85 + metadata TEXT DEFAULT '{}' -- Crash/recovery metadata 86 + ``` 87 + 88 + **`window_sessions` table:** 89 + ```sql 90 + id TEXT PRIMARY KEY 91 + sessionId TEXT NOT NULL FK 92 + windowId INTEGER 93 + address TEXT NOT NULL -- URL 94 + title TEXT -- Page title 95 + width, height INTEGER 96 + x, y INTEGER 97 + zOrder INTEGER -- Stacking order 98 + visible INTEGER (0/1) 99 + modal INTEGER (0/1) 100 + parentWindowId INTEGER -- For child windows 101 + params TEXT DEFAULT '{}' -- Full BrowserWindow params 102 + contextState TEXT -- JSON of window context 103 + ``` 104 + 105 + **`extension_session_state` table:** 106 + ```sql 107 + id TEXT PRIMARY KEY 108 + sessionId TEXT NOT NULL FK 109 + extensionId TEXT NOT NULL 110 + state TEXT NOT NULL -- JSON blob (extension-specific) 111 + version INTEGER -- Extension schema version 112 + ``` 113 + 114 + **`session_metadata` table:** 115 + ```sql 116 + profileId TEXT PRIMARY KEY -- One row per profile 117 + lastSessionId TEXT FK 118 + lastRestoreAt INTEGER 119 + restoreCount INTEGER DEFAULT 0 120 + crashCount INTEGER DEFAULT 0 121 + cleanShutdown INTEGER DEFAULT 1 -- 0 if unclean exit detected 122 + ``` 123 + 124 + ### 2.3 Save Phase 125 + 126 + **Triggers:** 127 + 1. **Autosave**: Every 5 minutes (if enabled) 128 + 2. **Before Shutdown**: On `app.on('before-quit')` 129 + 3. **Manual Snapshot**: Via Settings UI 130 + 131 + **Save Algorithm:** 132 + 133 + ``` 134 + 1. Gather all visible windows from windowRegistry 135 + 2. Get bounds/visibility/focus state for each 136 + 3. Publish 'app:before-shutdown' pubsub event 137 + 4. Wait 500ms for extensions to save state 138 + 5. Query all extension_settings entries 139 + 6. Capture per-window context state 140 + 7. Create session JSON blob 141 + 8. Insert into session_state table 142 + 9. Create corresponding window_sessions entries 143 + 10. Insert extension_session_state entries 144 + 11. Update session_metadata 145 + 12. Cleanup old autosaves (keep last 3) 146 + ``` 147 + 148 + **Critical Path:** 149 + - Triggered in `before-quit` handler to ensure capture before windows close 150 + - Timeout if extensions don't respond (500ms max wait) 151 + - Continues even if extension state save fails 152 + 153 + ### 2.4 Restore Phase 154 + 155 + **Triggered:** App startup, after extensions loaded 156 + 157 + **Algorithm:** 158 + 159 + ``` 160 + 1. Check session_metadata for crash detection 161 + 2. If cleanShutdown=0, show "Recover from crash?" dialog 162 + 3. Load last session (or selected snapshot) 163 + 4. Validate session version compatibility 164 + 5. Sort windows by zOrder 165 + 6. For each window: 166 + a. Validate URL accessibility 167 + b. Restore with saved params + bounds 168 + c. Handle restore failures gracefully (skip window) 169 + d. Restore context state (mode, group, etc.) 170 + 7. Restore focused window 171 + 8. Send 'session:restore' event to each extension 172 + 9. Update session_metadata: restoreCount++, cleanShutdown=1 173 + 10. Return restore report (restored/failed/skipped counts) 174 + ``` 175 + 176 + **Error Recovery:** 177 + - Invalid URLs: Warn and skip (don't fail entire restore) 178 + - Bounds out-of-display: Move to center of primary display 179 + - Missing extension: Skip that extension's state 180 + - Corrupted JSON: Try previous session, else clean start 181 + 182 + ### 2.5 User Controls 183 + 184 + **Settings UI Section:** 185 + 186 + ``` 187 + Session Management 188 + ├─ [x] Restore session on startup 189 + ├─ [x] Autosave session (interval: [___] minutes) 190 + ├─ [Save Current Session...] button 191 + │ └─ Session name: [Research Project ________] 192 + │ [Create Snapshot] button 193 + ├─ [Restore from Snapshot...] button 194 + │ └─ List of named sessions with dates 195 + ├─ [Clear all session data] button (with confirmation) 196 + └─ Privacy 197 + ├─ [ ] Privacy mode (don't save any URLs) 198 + ├─ [ ] Skip private/incognito windows 199 + └─ URL patterns to exclude: [_________________________] 200 + ``` 201 + 202 + **Settings Keys** (stored in extension_settings): 203 + ```javascript 204 + session:restore_enabled = true 205 + session:autosave_enabled = true 206 + session:autosave_interval = 300000 // milliseconds 207 + session:privacy_mode = false 208 + session:save_private_windows = false 209 + session:url_patterns_to_skip = ['https://bank\..*', ...] 210 + ``` 211 + 212 + ### 2.6 Privacy and Security 213 + 214 + **Privacy Considerations:** 215 + 216 + 1. **Sensitive URLs** (banking, healthcare) 217 + - Privacy Mode setting: skip all window content 218 + - URL pattern blacklist: regex-based exclusion 219 + - User informed in settings that auth cookies auto-restore 220 + 221 + 2. **Incognito Windows** 222 + - Track window.webPreferences.sandbox flag 223 + - Skip restoring private windows on next launch 224 + - Option: `session:save_private_windows` setting 225 + 226 + 3. **Expiration** 227 + - Optional: auto-delete sessions older than N days 228 + - Setting: `session:expires_after` (0=never) 229 + 230 + --- 231 + 232 + ## 3. Implementation Phases 233 + 234 + ### Phase 1: Core Tables & Save (Week 1) 235 + - Add `session_state`, `window_sessions`, `extension_session_state`, `session_metadata` tables 236 + - Implement `backend/electron/session.ts` with `saveSession()` function 237 + - Add `before-quit` handler in `entry.ts` 238 + - IPC handlers for session operations 239 + - Unit tests for save logic 240 + 241 + ### Phase 2: Shutdown Coordination (Week 2) 242 + - Implement `app:before-shutdown` pubsub event 243 + - Autosave timer in background window 244 + - Extension lifecycle hooks documentation 245 + - Integration tests for shutdown sequence 246 + 247 + ### Phase 3: Restore on Startup (Week 3) 248 + - Implement `restoreSession()` function 249 + - Add restore phase to app startup 250 + - Error recovery for invalid URLs, missing windows 251 + - Crash detection and recovery dialog 252 + - Integration tests 253 + 254 + ### Phase 4: Settings UI (Week 4) 255 + - Add Session Settings section to settings.js 256 + - "Save Current Session" dialog 257 + - "Restore from Snapshot" UI with list 258 + - Privacy settings controls 259 + - E2E tests for settings persistence 260 + 261 + ### Phase 5: Error Handling (Week 5) 262 + - Crash detection via `cleanShutdown` flag 263 + - Corrupted session fallback (try previous version) 264 + - Session cleanup job (keep last N autosaves) 265 + - Recovery mode (if >50% restore fails, show offer for clean start) 266 + 267 + ### Phase 6: Extension Integration (Week 6) 268 + - Document `app:before-shutdown` for extensions 269 + - Update peeks extension: save active peek state 270 + - Update groups extension: save active group context 271 + - Update slides extension: save slide state 272 + - Tests for multi-extension state coordination 273 + 274 + --- 275 + 276 + ## 4. Extension Integration 277 + 278 + ### 4.1 Extension Shutdown Hook 279 + 280 + Extensions receive this event ~500ms before app quit: 281 + 282 + ```javascript 283 + // In extension background script 284 + api.subscribe('app:before-shutdown', async (evt) => { 285 + // Save extension state 286 + const state = { 287 + prefs: currentSettings.prefs, 288 + items: currentSettings.items, 289 + activeItem: currentActiveItem 290 + }; 291 + 292 + await api.settings.set(state); 293 + // Extension should complete within ~400ms 294 + }); 295 + ``` 296 + 297 + ### 4.2 Session Restore Hook 298 + 299 + Sent to extension after app starts (if restoring): 300 + 301 + ```javascript 302 + api.subscribe('session:restore', (evt) => { 303 + const { version, data } = evt.state; 304 + if (version === 1) { 305 + // Restore from v1 format 306 + currentSettings = data; 307 + reInitializeUI(); 308 + } 309 + }); 310 + ``` 311 + 312 + ### 4.3 Example: Peeks Extension 313 + 314 + **Before shutdown:** 315 + ```javascript 316 + // Save current peek configurations 317 + await api.settings.set({ 318 + prefs: { shortcutKeyPrefix: 'Option+' }, 319 + items: [ 320 + { enabled: true, keyNum: 0, address: 'https://github.com' }, 321 + { enabled: true, keyNum: 1, address: 'https://news.ycombinator.com' } 322 + ] 323 + }); 324 + ``` 325 + 326 + **On restore:** 327 + ```javascript 328 + api.subscribe('session:restore', (evt) => { 329 + const { items } = evt.state.data; 330 + // Re-initialize shortcuts with restored peek items 331 + initItems(currentSettings.prefs, items); 332 + }); 333 + ``` 334 + 335 + --- 336 + 337 + ## 5. Challenges and Solutions 338 + 339 + ### 5.1 URL Validity 340 + **Challenge:** Saved URLs may become invalid (deleted peeks, localhost, etc.) 341 + 342 + **Solution:** 343 + - Implement `isUrlAccessible()` to validate protocol handlers 344 + - Skip invalid URLs with warning (don't fail entire restore) 345 + - Offer user choice: delete from session or retry 346 + 347 + ### 5.2 Window Geometry 348 + **Challenge:** Saved position may be off-screen if displays changed 349 + 350 + **Solution:** 351 + - Validate bounds against current display on restore 352 + - If out-of-bounds, move to center of primary display 353 + - Store display info in metadata for debugging 354 + 355 + ### 5.3 Crash Detection 356 + **Challenge:** How to detect unclean exits? 357 + 358 + **Solution:** 359 + - `session_metadata.cleanShutdown` flag (set to 1 before quit) 360 + - On startup, if 0: show crash recovery dialog 361 + - After 3+ crashes, disable auto-restore 362 + 363 + ### 5.4 Extension Version Mismatch 364 + **Challenge:** Extension state saved with v1, app now has v2 365 + 366 + **Solution:** 367 + - Extensions declare compatible state versions 368 + - Session includes extension version info 369 + - Run migration if version < current 370 + - Skip if version > current 371 + 372 + ### 5.5 Large Session State 373 + **Challenge:** Session snapshots could get large 374 + 375 + **Solution:** 376 + - Compress if >100KB 377 + - Keep only last 3 autosaves 378 + - Limit to 50MB per session 379 + - Don't save if >100MB 380 + 381 + ### 5.6 Parent Window Closure 382 + **Challenge:** Modal child window persists after parent closes 383 + 384 + **Solution:** 385 + - Restore parent before children (track parentWindowId) 386 + - If parent restore fails, skip orphaned children 387 + - Offer user choice: adopt to primary window 388 + 389 + --- 390 + 391 + ## 6. Testing Strategy 392 + 393 + ### Unit Tests 394 + - Session save/restore with 1-5 windows 395 + - Context state preservation 396 + - Extension state merging 397 + - Version compatibility 398 + - URL validation 399 + - Corrupted session recovery 400 + 401 + ### Integration Tests 402 + - Full lifecycle: save → quit → restart → restore 403 + - Multi-extension coordination during shutdown 404 + - Autosave timer (mocked) 405 + - Privacy settings enforcement 406 + - Crash detection 407 + - Settings persistence 408 + 409 + ### E2E Tests (Playwright) 410 + - Open 5 windows, save, restart → all restored 411 + - Graceful quit saves session 412 + - Force-quit triggers crash recovery 413 + - Named snapshot restore works 414 + - Privacy mode hides URLs 415 + - Extension state survives restart 416 + 417 + ### Manual Testing 418 + - Open windows, quit with Cmd+Q, restart → verify restored 419 + - Set custom window size/position → verify bounds preserved 420 + - Open peek (Option+0), save, restart → peek restored 421 + - Kill app with force-quit → crash dialog shown 422 + - Enable privacy mode, open github.com, restart → URL not in DB 423 + - Wait 2+ minutes with autosave enabled → verify multiple autosaves created 424 + 425 + --- 426 + 427 + ## 7. API Reference 428 + 429 + ### IPC Handlers 430 + 431 + ```typescript 432 + // Renderer → Main 433 + 'session:save' // { reason, name? } 434 + 'session:restore' // { sessionId? } 435 + 'session:list' // {} → SessionMetadata[] 436 + 'session:delete' // { sessionId } 437 + 'session:get-settings' // {} 438 + 'session:update-settings' // { restoreEnabled, autosaveEnabled, ... } 439 + 440 + // Main → Renderer (pubsub) 441 + 'app:before-shutdown' // { reason: string } 442 + 'session:restore-complete' // { restored, failed, skipped } 443 + ``` 444 + 445 + ### Extension API 446 + 447 + ```javascript 448 + // Listen for shutdown 449 + window.app.subscribe('app:before-shutdown', async (evt) => { ... }); 450 + 451 + // Listen for restore 452 + window.app.subscribe('session:restore', (evt) => { ... }); 453 + ``` 454 + 455 + --- 456 + 457 + ## 8. Success Metrics 458 + 459 + 1. **Correctness**: 100% of test scenarios pass 460 + 2. **Completeness**: >95% of windows successfully restore 461 + 3. **Performance**: Save <2s, restore <5s 462 + 4. **Storage**: Session DB stays <100MB 463 + 5. **Privacy**: Privacy mode hides all URLs 464 + 6. **Recovery**: Crash recovery works >95% 465 + 466 + --- 467 + 468 + ## 9. Future Enhancements 469 + 470 + 1. Incremental snapshots (save only diffs) 471 + 2. Cloud sync of sessions across devices 472 + 3. Session merging (combine multiple sessions) 473 + 4. Time-travel history of past sessions 474 + 5. Mobile → desktop session sync 475 + 6. Pre-defined session templates 476 + 7. Collaborative session sharing 477 + 478 + --- 479 + 480 + ## Conclusion 481 + 482 + This comprehensive session tracking system builds on Peek's existing datastore and extension architecture. Key innovations: 483 + 484 + 1. **Coordinated Shutdown**: `app:before-shutdown` event ensures all extensions save atomically 485 + 2. **Graceful Degradation**: Invalid windows skipped, other windows still restored 486 + 3. **Crash Recovery**: Detects unclean exits and offers recovery 487 + 4. **Privacy-First**: Settings for URL patterns, privacy mode, incognito handling 488 + 5. **Extension Agnostic**: Each extension manages own state format/versioning 489 + 490 + The phased implementation prioritizes core functionality first, with error handling and advanced features following. By leveraging the existing context_history table and extension_settings infrastructure, implementation effort is minimized while providing a browser-like session restore experience tailored to Peek's multi-window, multi-extension architecture. 491 + 492 + --- 493 + 494 + ## Meta 495 + 496 + - **Author**: Exploration Agent 497 + - **Date**: 2026-02-09 498 + - **Status**: Design/Planning Phase 499 + - **Related**: Window management, extension lifecycle, datastore architecture