experiments in a post-browser web
10
fork

Configure Feed

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

fix(nav): fix floating navbar activation and add hover trigger

The floating navbar in page view was not appearing because:
1. The webview-push-down mechanism was missing -- Electron's <webview>
composites as a separate layer that renders over host DOM elements,
so the navbar was invisible even when its CSS class was set to visible.
2. The hover trigger zone was disabled (display: none), preventing
hover-based activation entirely.
3. The previous fix correctly added Cmd+L interception on the guest
webContents and fixed the pubsub scope to GLOBAL, but without the
webview push-down, the navbar remained hidden behind the webview layer.

Changes:
- Add .navbar-active class that shifts webview down 90px when navbar is
visible, with a smooth 150ms CSS transition
- Re-enable the trigger zone as a 12px strip at the top of the window
(z-index: 50, above the webview) for hover activation
- Add mouseenter/mouseleave handlers on trigger zone and navbar with
300ms debounced auto-hide (only for hover-triggered shows)
- Track show source (hover vs shortcut) so Cmd+L-triggered navbar
stays until explicitly dismissed (click outside or Escape)
- Add debug logging for navbar show/hide events

+143 -34
+21 -10
app/page/index.html
··· 19 19 background: transparent; 20 20 } 21 21 22 - /* Webview fills the entire window */ 22 + /* 23 + * Webview fills the entire window. 24 + * When the navbar is visible, the webview shifts down via .navbar-active 25 + * to avoid the Electron <webview> layer rendering over the navbar. 26 + */ 23 27 webview { 24 28 position: absolute; 25 29 top: 0; left: 0; right: 0; bottom: 0; ··· 27 31 border-radius: 10px; 28 32 overflow: hidden; 29 33 -webkit-mask-image: -webkit-radial-gradient(white, white); 34 + transition: top 0.15s ease; 35 + } 36 + 37 + /* Push webview down when navbar is active so it doesn't cover the floating bar */ 38 + webview.navbar-active { 39 + top: 90px; 40 + border-radius: 0 0 10px 10px; 30 41 } 31 42 32 43 /* 33 - * FLOATING NAVBAR — DO NOT ADD AN INLINE/EMBEDDED NAVBAR 34 - * 35 - * The navbar is a FLOATING overlay shown via Cmd+L or hover trigger. 36 - * It is NOT an embedded bar at the top of the page. 37 - * It does NOT use -webkit-app-region: drag. 44 + * FLOATING NAVBAR — NOT an inline/embedded bar. 38 45 * 39 - * Any inline/embedded navbar was deliberately removed. 40 - * Do not re-add it under any circumstances without explicit user approval. 46 + * Shown via Cmd+L or hovering near the top of the window. 47 + * Centered, floating above content with shadow. 48 + * Does NOT use -webkit-app-region: drag. 41 49 */ 42 50 .navbar { 43 51 position: fixed; ··· 120 128 background: var(--theme-bg, rgba(128, 128, 128, 0.18)); 121 129 } 122 130 123 - /* Trigger zone hidden — navbar is shown on demand via Cmd+L only */ 131 + /* Trigger zone at top of window — hover here to reveal the floating navbar */ 124 132 .trigger-zone { 125 - display: none; 133 + position: absolute; 134 + top: 0; left: 0; right: 0; 135 + height: 12px; 136 + z-index: 50; 126 137 } 127 138 128 139 /* Resize handle */
+70 -15
app/page/page.js
··· 3 3 * 4 4 * Content-sized BrowserWindow with: 5 5 * - Webview filling the entire window 6 - * - Floating navbar overlay (Cmd+L to show, Escape/click-outside to dismiss) 6 + * - Floating navbar overlay (Cmd+L or hover near top to show, Escape/click-outside to dismiss) 7 7 * NOT embedded in window chrome — floats over content like a command palette 8 + * - When shown, the webview shifts down so the Electron <webview> layer doesn't cover the navbar 8 9 * - Resize via IPC to resize the BrowserWindow 9 - * 10 - * FLOATING NAVBAR — DO NOT ADD AN INLINE/EMBEDDED NAVBAR 11 - * 12 - * The navbar is a FLOATING overlay shown via Cmd+L or hover trigger. 13 - * It is NOT an embedded bar at the top of the page. 14 - * It does NOT use -webkit-app-region: drag. 15 - * 16 - * Any inline/embedded navbar was deliberately removed. 17 - * Do not re-add it under any circumstances without explicit user approval. 18 10 */ 19 11 20 12 import api from '../api.js'; ··· 97 89 } 98 90 99 91 // --- Show / Hide navbar --- 92 + // The floating navbar is shown by: 93 + // 1. Cmd+L (published from main process via before-input-event on guest/host webContents) 94 + // 2. Hovering near the top of the window (trigger zone) 95 + // It is hidden by clicking outside, pressing Escape, or moving mouse away (hover mode). 96 + // 97 + // When visible, the <webview> element shifts down (via .navbar-active class) so the 98 + // Electron composited webview layer doesn't paint over the floating navbar. 99 + 100 + let hideTimer = null; 101 + let showSource = null; // 'hover' or 'shortcut' — determines dismiss behavior 100 102 101 103 function show(opts) { 104 + if (hideTimer) { 105 + clearTimeout(hideTimer); 106 + hideTimer = null; 107 + } 102 108 const wasHidden = !navbar.classList.contains('visible'); 103 109 navbar.classList.add('visible'); 104 - if (wasHidden) updateState(); 110 + webview.classList.add('navbar-active'); 111 + if (opts?.source) showSource = opts.source; 112 + if (wasHidden) { 113 + updateState(); 114 + DEBUG && console.log('[page] Navbar shown, source:', showSource); 115 + } 105 116 if (opts?.focusUrl) { 106 117 requestAnimationFrame(() => { 107 118 const range = document.createRange(); ··· 114 125 } 115 126 116 127 function hide() { 128 + if (hideTimer) { 129 + clearTimeout(hideTimer); 130 + hideTimer = null; 131 + } 117 132 navbar.classList.remove('visible'); 133 + webview.classList.remove('navbar-active'); 118 134 window.getSelection().removeAllRanges(); 135 + showSource = null; 136 + DEBUG && console.log('[page] Navbar hidden'); 119 137 } 120 138 121 - // Floating navbar: shown on demand via Cmd+L only, dismissed by clicking outside or Escape 122 - // No trigger zone hover — the navbar is a floating overlay, not an embedded bar. 139 + // Dismiss on click outside the navbar 123 140 document.addEventListener('mousedown', (e) => { 124 - if (navbar.classList.contains('visible') && !navbar.contains(e.target)) { 141 + if (navbar.classList.contains('visible') && !navbar.contains(e.target) && e.target !== triggerZone) { 125 142 hide(); 126 143 } 127 144 }); 128 145 146 + // Dismiss on Escape 129 147 document.addEventListener('keydown', (e) => { 130 148 if (e.key === 'Escape' && navbar.classList.contains('visible')) { 131 149 hide(); ··· 133 151 } 134 152 }); 135 153 154 + // --- Hover trigger zone --- 155 + // Mouse entering the thin strip at the top of the window shows the navbar. 156 + // Moving away from both the trigger zone AND the navbar hides it (with a small delay). 157 + 158 + function scheduleHide() { 159 + if (hideTimer) clearTimeout(hideTimer); 160 + hideTimer = setTimeout(() => { 161 + // Only auto-hide if shown by hover (not by Cmd+L) 162 + if (showSource === 'hover') { 163 + hide(); 164 + } 165 + }, 300); 166 + } 167 + 168 + function cancelHide() { 169 + if (hideTimer) { 170 + clearTimeout(hideTimer); 171 + hideTimer = null; 172 + } 173 + } 174 + 175 + triggerZone.addEventListener('mouseenter', () => { 176 + show({ source: 'hover' }); 177 + }); 178 + 179 + triggerZone.addEventListener('mouseleave', () => { 180 + if (showSource === 'hover') scheduleHide(); 181 + }); 182 + 183 + navbar.addEventListener('mouseenter', () => { 184 + cancelHide(); 185 + }); 186 + 187 + navbar.addEventListener('mouseleave', () => { 188 + if (showSource === 'hover') scheduleHide(); 189 + }); 190 + 136 191 // Cmd+L: show floating navbar with URL focus 137 192 // Published from main process with GLOBAL scope (both host and webview guest fire this) 138 - api.subscribe('page:show-navbar', () => show({ focusUrl: true }), api.scopes.GLOBAL); 193 + api.subscribe('page:show-navbar', () => show({ focusUrl: true, source: 'shortcut' }), api.scopes.GLOBAL); 139 194 140 195 // --- Nav button actions --- 141 196
+13 -4
extensions/tags/home.css
··· 252 252 flex: 1; 253 253 overflow-y: auto; 254 254 padding: 16px 24px; 255 + display: flex; 256 + flex-direction: column; 255 257 } 256 258 257 259 /* Cards grid - peek-grid custom properties */ ··· 377 379 378 380 .detail-view { 379 381 padding: 0; 382 + display: flex; 383 + flex-direction: column; 384 + flex: 1; 385 + min-height: 0; 380 386 } 381 387 382 388 .detail-header { ··· 435 441 margin-bottom: 20px; 436 442 display: flex; 437 443 flex-direction: column; 444 + flex: 1; 445 + min-height: 0; 438 446 } 439 447 440 448 .detail-editor-header { ··· 474 482 border-radius: 8px; 475 483 overflow: hidden; 476 484 min-height: 200px; 477 - max-height: 50vh; 478 485 display: flex; 479 486 flex-direction: column; 487 + flex: 1; 480 488 } 481 489 482 490 .detail-editor-container .cm-editor { 483 - height: 100%; 484 - min-height: 200px; 485 - max-height: 50vh; 491 + flex: 1; 492 + min-height: 0; 486 493 border-radius: 8px; 487 494 } 488 495 489 496 .detail-editor-container .cm-scroller { 490 497 overflow: auto; 498 + flex: 1; 491 499 } 492 500 493 501 /* Vim status line below embedded editor */ 494 502 .detail-editor-status-line { 495 503 border-radius: 0 0 8px 8px; 496 504 overflow: hidden; 505 + flex-shrink: 0; 497 506 } 498 507 499 508 .detail-editor-status-line .vim-status-line {
+15 -1
notes/plan-url-history-unification.md
··· 1 1 # Implementation Plan: URL/History Unification 2 2 3 - ## 1. Current State Audit 3 + ## Status: COMPLETED (Feb 11, 2026) 4 + 5 + All 6 phases implemented and committed: 6 + - `475e97fe` Phase 1: Remove dual-write 7 + - `f8beaef1` Phase 2: Redirect renderer address API calls to items 8 + - `67483a8a` Phase 3: IPC compatibility layer 9 + - `bca71da1` Phase 4: Update tests 10 + - `7ee8e9cb` Phase 5: Update Tauri backend 11 + - `6f90f258` Phase 6: Cleanup — drop tables, remove dead code and types 12 + 13 + Results: 231 unit tests pass, 155 electron tests pass (0 regressions). Actual effort: ~2-3 hours, not 4 weeks — data migration was already done by existing migrations. 14 + 15 + --- 16 + 17 + ## 1. Current State Audit (pre-implementation) 4 18 5 19 The codebase has two parallel data models that have been partially unified through migrations already applied. 6 20
+24 -4
notes/research-url-history-unification.md
··· 1 1 # Research: URL/History Unification Architecture for Peek 2 2 3 - ## Executive Summary 3 + ## Status: COMPLETED (Feb 11, 2026) 4 + 5 + The unification has been implemented across 6 phases. The legacy `addresses`/`visits`/`address_tags` tables have been dropped. All code now uses the unified `items`/`item_visits`/`item_tags` system. 6 + 7 + See `notes/plan-url-history-unification.md` for the implementation plan that was executed. 4 8 5 - The desktop app has **two parallel data models** that need unification: 9 + ### What was done: 10 + - Phase 1: Removed dual-write between address and item systems 11 + - Phase 2: Redirected renderer address API calls to items 12 + - Phase 3: IPC compatibility layer (address handlers → item functions) 13 + - Phase 4: Updated tests 14 + - Phase 5: Updated Tauri backend 15 + - Phase 6: Dropped legacy tables, removed dead code and types 16 + 17 + ### Key outcome: 18 + - Actual effort: ~2-3 hours (not 4 weeks) — data migration was already done by existing migrations 19 + - 231 unit tests pass, 155 electron tests pass 20 + - Legacy tables dropped via `dropLegacyAddressTables()` migration 21 + - IPC backward compat layer retained (address channel names redirect to item functions) 22 + 23 + --- 24 + 25 + ## Original Research (for historical reference) 26 + 27 + The desktop app had **two parallel data models** that needed unification: 6 28 7 29 1. **Addresses/Visits** - History tracking, frecency, revisit counting (not synced) 8 30 2. **Items** - User curation, sync, annotations (url, text, tagset, image types) 9 31 10 32 **Core insight**: Adopt a unified "everything is an item" model that merges both systems, enabling full sync, consistent frecency scoring, and advanced features (groups, addressing, chaining). 11 - 12 - **Effort estimate**: ~4 weeks mid-term refactor that unblocks numerous TODO items. 13 33 14 34 --- 15 35