experiments in a post-browser web
10
fork

Configure Feed

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

fix(pagestream): fix initial focus, card borders, duplicates, and page loading

- Start with most recent history card focused instead of auto-focusing navbar URL input
- Remove blue outline/border on active/focused/hovered cards by overriding peek-card
shadow DOM styles via ::part(card) selectors
- Deduplicate consecutive history entries with the same URL in getFilteredVisits()
- Replace broken webview-based inline browsing with api.window.open() — extension
windows don't have webviewTag enabled in webPreferences, so pages were blank white;
now URLs open in proper page windows with full canvas + webview support
- Remove unused browse-overlay CSS

+44 -95
+21 -25
extensions/pagestream/home.css
··· 67 67 transition: transform 0.25s ease, box-shadow 0.25s ease, padding 0.25s ease, font-size 0.25s ease; 68 68 } 69 69 70 + /* Remove focus ring / outline / hover border from interactive cards */ 71 + .stream-container peek-card::part(card) { 72 + outline: none !important; 73 + box-shadow: none; 74 + } 75 + 76 + .stream-container peek-card:hover::part(card), 77 + .stream-container peek-card:focus::part(card), 78 + .stream-container peek-card:focus-visible::part(card), 79 + .stream-container peek-card:focus-within::part(card) { 80 + border-color: transparent !important; 81 + outline: none !important; 82 + } 83 + 70 84 .stream-container peek-card[selected] { 71 85 --peek-card-bg: var(--base02); 72 86 --peek-card-border: transparent; ··· 82 96 --peek-card-border: transparent; 83 97 --peek-card-padding: 16px; 84 98 --peek-card-gap: 8px; 85 - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); 86 99 z-index: 10; 100 + } 101 + 102 + .stream-container peek-card.active-card::part(card) { 103 + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3) !important; 104 + border-color: transparent !important; 105 + outline: none !important; 87 106 } 88 107 89 108 .stream-container peek-card.active-card .card-title { ··· 254 273 color: var(--base05); 255 274 } 256 275 257 - /* Browse overlay - inline page viewer */ 258 - .browse-overlay { 259 - position: fixed; 260 - top: 0; 261 - left: 0; 262 - right: 0; 263 - bottom: 0; 264 - z-index: 100; 265 - display: flex; 266 - flex-direction: column; 267 - background: var(--base00, #1e1e2e); 268 - } 269 - 270 - .browse-overlay webview, 271 - .browse-overlay iframe { 272 - flex: 1; 273 - border: none; 274 - background: #fff; 275 - } 276 - 277 - .browse-overlay .navbar-container { 278 - padding: 8px 16px 12px 16px; 279 - flex-shrink: 0; 280 - } 276 + /* Browse overlay styles removed - pages now open in proper page windows */
+23 -70
extensions/pagestream/home.js
··· 174 174 const getFilteredVisits = () => { 175 175 let filtered = state.visits; 176 176 177 + // Deduplicate consecutive entries with the same URL 178 + filtered = filtered.filter(({ item }, i, arr) => { 179 + if (i === 0) return true; 180 + return item.content !== arr[i - 1].item.content; 181 + }); 182 + 177 183 // Text search filter 178 184 if (state.searchQuery) { 179 185 const q = state.searchQuery.toLowerCase(); ··· 390 396 // ===== Inline Browsing ===== 391 397 392 398 /** 393 - * Open a URL inline in a browse overlay (iframe/webview) 399 + * Open a URL in a proper page window via api.window.open(). 400 + * Extension windows don't support <webview> tags (webviewTag is not enabled), 401 + * so we open the URL in a full page window which has canvas + webview support. 394 402 */ 395 - const openInline = (url) => { 403 + const openInline = async (url) => { 396 404 if (state.browsing) return; 397 405 state.browsing = true; 398 406 state.browseUrl = url; 399 407 400 - debug && console.log('[pagestream] Opening inline:', url); 408 + debug && console.log('[pagestream] Opening in page window:', url); 401 409 402 - // Create the overlay 403 - const overlay = document.createElement('div'); 404 - overlay.className = 'browse-overlay'; 405 - overlay.id = 'browse-overlay'; 410 + try { 411 + await api.window.open(url, { 412 + trackingSource: 'pagestream', 413 + }); 414 + } catch (err) { 415 + console.error('[pagestream] Failed to open page window:', err); 416 + } 406 417 407 - // Create webview/iframe for the page 408 - const frame = document.createElement('webview'); 409 - frame.src = url; 410 - frame.id = 'browse-frame'; 411 - frame.setAttribute('allowpopups', ''); 412 - 413 - // Create a navbar for the browse session 414 - const navContainer = document.createElement('div'); 415 - navContainer.className = 'navbar-container'; 416 - const navbar = document.createElement('peek-navbar'); 417 - navbar.id = 'browse-navbar'; 418 - navbar.url = url; 419 - navbar.shortcuts = false; // host manages shortcuts 420 - navContainer.appendChild(navbar); 421 - 422 - overlay.appendChild(frame); 423 - overlay.appendChild(navContainer); 424 - document.body.appendChild(overlay); 425 - 426 - // Update navbar URL as webview navigates 427 - frame.addEventListener('did-navigate', (e) => { 428 - state.browseUrl = e.url || frame.src; 429 - navbar.setUrl(state.browseUrl); 430 - navbar.canGoBack = frame.canGoBack ? frame.canGoBack() : false; 431 - navbar.canGoForward = frame.canGoForward ? frame.canGoForward() : false; 432 - }); 433 - 434 - frame.addEventListener('did-navigate-in-page', (e) => { 435 - state.browseUrl = e.url || frame.src; 436 - navbar.setUrl(state.browseUrl); 437 - navbar.canGoBack = frame.canGoBack ? frame.canGoBack() : false; 438 - navbar.canGoForward = frame.canGoForward ? frame.canGoForward() : false; 439 - }); 440 - 441 - // Navbar navigation events 442 - navbar.addEventListener('navigate', (e) => { 443 - const navUrl = e.detail?.url; 444 - if (navUrl) { 445 - frame.src = navUrl; 446 - state.browseUrl = navUrl; 447 - } 448 - }); 449 - 450 - navbar.addEventListener('go-back', () => { 451 - if (frame.canGoBack && frame.canGoBack()) frame.goBack(); 452 - }); 453 - 454 - navbar.addEventListener('go-forward', () => { 455 - if (frame.canGoForward && frame.canGoForward()) frame.goForward(); 456 - }); 457 - 458 - navbar.addEventListener('reload', () => { 459 - if (frame.reload) frame.reload(); 460 - }); 418 + // Reset browsing state immediately so the card stream stays usable 419 + state.browsing = false; 420 + state.browseUrl = null; 461 421 }; 462 422 463 423 /** 464 - * Close the inline browse overlay and return to card view 424 + * Close browse mode and return to card view 465 425 */ 466 426 const closeBrowse = () => { 467 - const overlay = document.getElementById('browse-overlay'); 468 - if (overlay) { 469 - overlay.remove(); 470 - } 471 427 state.browsing = false; 472 428 state.browseUrl = null; 473 429 }; ··· 698 654 scrollToBottom(); 699 655 }); 700 656 701 - // Auto-focus the navbar URL input on open 702 - requestAnimationFrame(() => { 703 - navbarEl.focusUrl(); 704 - }); 657 + // Focus stays on the card stream (not the navbar) so the most recent entry is active 705 658 706 659 // Debounced refresh for reactive updates 707 660 const debouncedRefresh = debounce(async () => {