experiments in a post-browser web
10
fork

Configure Feed

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

fix(page): content-sized BrowserWindow instead of fullscreen canvas

The fullscreen transparent canvas approach broke slides and any
extension passing explicit x/y to position web pages. The window
was overridden to fullscreen at (0,0) and the webview CSS-positioned
inside, so extension-provided coordinates were ignored.

Now the BrowserWindow is sized to match the content directly:
- Webview fills the window via CSS (no JS positioning)
- Drag via native -webkit-app-region on navbar (no custom handler)
- Resize via window-set-bounds IPC to resize the BrowserWindow
- Slides pass final x/y directly (no off-screen animation)

All pages use the same infrastructure regardless of caller.

+36 -115
+6 -4
app/page/index.html
··· 19 19 background: transparent; 20 20 } 21 21 22 - /* Webview is positioned on the transparent canvas */ 22 + /* Webview fills the entire window */ 23 23 webview { 24 24 position: absolute; 25 + top: 0; left: 0; right: 0; bottom: 0; 25 26 border: none; 26 27 border-radius: 10px; 27 28 overflow: hidden; 28 - /* Use clip-path as fallback if border-radius doesn't clip webview content */ 29 29 -webkit-mask-image: -webkit-radial-gradient(white, white); 30 30 } 31 31 32 - /* Navbar floats above the webview */ 32 + /* Navbar overlays the top of the webview */ 33 33 .navbar { 34 34 position: absolute; 35 + top: 0; left: 0; right: 0; 35 36 height: 36px; 36 37 display: none; 37 38 align-items: center; ··· 106 107 background: var(--theme-bg, rgba(128, 128, 128, 0.18)); 107 108 } 108 109 109 - /* Trigger zone above webview */ 110 + /* Trigger zone at top edge of window */ 110 111 .trigger-zone { 111 112 position: absolute; 113 + top: 0; left: 0; right: 0; 112 114 height: 50px; 113 115 z-index: 50; 114 116 }
+14 -67
app/page/page.js
··· 1 1 /** 2 2 * peek://page - Container for web content 3 3 * 4 - * Full-screen transparent canvas with: 5 - * - Webview positioned at caller's coordinates 6 - * - Floating navbar above the webview (with gap) 7 - * - Webview is draggable and resizable 4 + * Content-sized BrowserWindow with: 5 + * - Webview filling the window 6 + * - Floating navbar overlay at top (with trigger zone) 7 + * - Drag via -webkit-app-region: drag on navbar 8 + * - Resize via IPC to resize the BrowserWindow 8 9 */ 9 10 10 11 import izui from '../izui.js'; ··· 15 16 // Parse URL parameters 16 17 const params = new URLSearchParams(window.location.search); 17 18 const targetUrl = params.get('url'); 18 - const initialX = parseInt(params.get('x')) || 100; 19 - const initialY = parseInt(params.get('y')) || 100; 20 - const initialWidth = parseInt(params.get('width')) || 1024; 21 - const initialHeight = parseInt(params.get('height')) || 768; 22 19 23 20 if (!targetUrl) { 24 21 console.error('[page] No URL provided'); ··· 26 23 throw new Error('No URL provided to peek://page'); 27 24 } 28 25 29 - DEBUG && console.log('[page] Loading:', targetUrl, 'at', initialX, initialY, initialWidth, initialHeight); 26 + DEBUG && console.log('[page] Loading:', targetUrl); 30 27 31 28 // DOM elements 32 29 const navbar = document.getElementById('navbar'); ··· 39 36 const urlText = document.getElementById('url-text'); 40 37 const modeIndicator = document.getElementById('mode-indicator'); 41 38 42 - // Current webview bounds 43 - let bounds = { x: initialX, y: initialY, width: initialWidth, height: initialHeight }; 44 - const NAVBAR_HEIGHT = 36; 45 - const NAVBAR_GAP = 8; 46 - 47 - // Position elements 48 - function updatePositions() { 49 - // Webview position 50 - webview.style.left = bounds.x + 'px'; 51 - webview.style.top = bounds.y + 'px'; 52 - webview.style.width = bounds.width + 'px'; 53 - webview.style.height = bounds.height + 'px'; 54 - 55 - // Navbar above webview with gap 56 - navbar.style.left = bounds.x + 'px'; 57 - navbar.style.top = (bounds.y - NAVBAR_HEIGHT - NAVBAR_GAP) + 'px'; 58 - navbar.style.width = bounds.width + 'px'; 59 - 60 - // Trigger zone spans navbar area 61 - triggerZone.style.left = bounds.x + 'px'; 62 - triggerZone.style.top = (bounds.y - NAVBAR_HEIGHT - NAVBAR_GAP - 10) + 'px'; 63 - triggerZone.style.width = bounds.width + 'px'; 64 - 65 - // Resize handle at bottom-right of webview 66 - resizeHandle.style.left = (bounds.x + bounds.width - 16) + 'px'; 67 - resizeHandle.style.top = (bounds.y + bounds.height - 16) + 'px'; 68 - } 69 - 70 - // Initialize positions 71 - updatePositions(); 72 - 73 39 // Set up webview partition for session isolation and load the target URL 74 40 async function initWebview() { 75 41 try { ··· 93 59 // Start initialization 94 60 initWebview(); 95 61 96 - // --- Drag to move webview --- 62 + // --- Resize via IPC (drag on navbar handled natively by -webkit-app-region: drag) --- 97 63 98 - let isDragging = false; 99 - let dragStart = { x: 0, y: 0 }; 64 + let isResizing = false; 100 65 101 - navbar.addEventListener('mousedown', (e) => { 102 - if (e.target.classList.contains('nav-btn') || e.target.classList.contains('url-text')) return; 103 - isDragging = true; 104 - dragStart = { x: e.clientX - bounds.x, y: e.clientY - bounds.y }; 66 + resizeHandle.addEventListener('mousedown', (e) => { 67 + isResizing = true; 105 68 e.preventDefault(); 69 + e.stopPropagation(); 106 70 }); 107 71 108 72 document.addEventListener('mousemove', (e) => { 109 - if (isDragging) { 110 - bounds.x = e.clientX - dragStart.x; 111 - bounds.y = e.clientY - dragStart.y; 112 - updatePositions(); 113 - } 114 73 if (isResizing) { 115 - bounds.width = Math.max(200, e.clientX - bounds.x); 116 - bounds.height = Math.max(150, e.clientY - bounds.y); 117 - updatePositions(); 74 + const newWidth = Math.max(200, e.screenX - window.screenX); 75 + const newHeight = Math.max(150, e.screenY - window.screenY); 76 + api.invoke('window-set-bounds', { width: newWidth, height: newHeight }); 118 77 } 119 78 }); 120 79 121 80 document.addEventListener('mouseup', () => { 122 - isDragging = false; 123 81 isResizing = false; 124 - }); 125 - 126 - // --- Resize webview --- 127 - 128 - let isResizing = false; 129 - 130 - resizeHandle.addEventListener('mousedown', (e) => { 131 - isResizing = true; 132 - e.preventDefault(); 133 - e.stopPropagation(); 134 82 }); 135 83 136 84 // --- State display --- ··· 163 111 } 164 112 165 113 function hide(e) { 166 - if (isDragging) return; // Don't hide while dragging 167 114 // Check if cursor is still within navbar or trigger zone bounds 168 115 if (isPointInElement(e.clientX, e.clientY, navbar)) return; 169 116 if (isPointInElement(e.clientX, e.clientY, triggerZone)) return;
+13 -7
backend/electron/ipc.ts
··· 1838 1838 }); 1839 1839 loadUrl = `peek://app/page/index.html?${pageParams.toString()}`; 1840 1840 DEBUG && console.log('Routing web page through peek://app/page:', url, '->', loadUrl); 1841 - 1842 - // Make the container window fullscreen and transparent 1843 - const primaryDisplay = screen.getPrimaryDisplay(); 1844 - const { width: screenWidth, height: screenHeight } = primaryDisplay.workAreaSize; 1845 - win.setSize(screenWidth, screenHeight); 1846 - win.setPosition(0, 0); 1847 - win.setBackgroundColor('#00000000'); // Fully transparent 1848 1841 } 1849 1842 1850 1843 await win.loadURL(loadUrl); ··· 2462 2455 const message = error instanceof Error ? error.message : String(error); 2463 2456 return { success: false, error: message }; 2464 2457 } 2458 + }); 2459 + 2460 + ipcMain.handle('window-set-bounds', (ev, msg) => { 2461 + const win = BrowserWindow.fromWebContents(ev.sender); 2462 + if (!win || win.isDestroyed()) return { success: false, error: 'Window not found' }; 2463 + const bounds = win.getBounds(); 2464 + win.setBounds({ 2465 + x: msg.x ?? bounds.x, 2466 + y: msg.y ?? bounds.y, 2467 + width: msg.width ?? bounds.width, 2468 + height: msg.height ?? bounds.height, 2469 + }); 2470 + return { success: true }; 2465 2471 }); 2466 2472 2467 2473 ipcMain.handle('window-exists', async (_ev, msg) => {
+3 -37
extensions/slides/background.js
··· 124 124 } 125 125 126 126 function openNewSlide() { 127 - // Calculate off-screen starting position for animation 128 - let startX = x, startY = y; 129 - const animationDuration = 150; // ms 130 - 131 - switch(item.screenEdge) { 132 - case 'Up': 133 - startY = -height; // Start above screen 134 - break; 135 - case 'Down': 136 - startY = screen.height; // Start below screen 137 - break; 138 - case 'Left': 139 - startX = -width; // Start left of screen 140 - break; 141 - case 'Right': 142 - startX = screen.width; // Start right of screen 143 - break; 144 - } 145 - 146 127 const params = { 147 128 address: item.address, 148 129 height, ··· 157 138 keepLive: item.keepLive || false, 158 139 persistState: item.persistState || false, 159 140 160 - // Start at off-screen position for animation 161 - x: startX, 162 - y: startY, 141 + x, 142 + y, 163 143 164 144 // tracking 165 145 trackingSource: 'slide', ··· 167 147 title: item.title || '' 168 148 }; 169 149 170 - api.window.open(item.address, params).then(async result => { 150 + api.window.open(item.address, params).then(result => { 171 151 if (result.success) { 172 152 console.log('[ext:slides] Successfully opened slide with ID:', result.id); 173 153 slideWindows.set(key, result.id); 174 - 175 - // Animate to final position 176 - if (startX !== x || startY !== y) { 177 - try { 178 - await api.invoke('window-animate', { 179 - id: result.id, 180 - to: { x, y, width, height }, 181 - duration: animationDuration 182 - }); 183 - console.log('[ext:slides] Animation complete'); 184 - } catch (err) { 185 - console.error('[ext:slides] Animation failed:', err); 186 - } 187 - } 188 154 } else { 189 155 console.error('[ext:slides] Failed to open slide:', result.error); 190 156 }