experiments in a post-browser web
10
fork

Configure Feed

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

fix(peek-card): delegate click/focus through shadow DOM boundary

peek-card.click() dispatched on host element but @click handler lives on
inner .card div — event never crossed shadow DOM boundary. Same timing
issue with focus() on initial render before Lit's first paint.

Fix: click() delegates to inner div, focus() awaits updateComplete.
Affects all extensions using activateSelected() (groups, tags, windows).

Also: Scripts extension CSP fix — replace new Function() with Web Worker
blob URLs for script execution, adding worker-src blob: to CSP headers.

108/108 tests pass including new Enter-key activation test.

+421 -180
+1 -1
app/background.html
··· 3 3 <head> 4 4 <meta charset="UTF-8"> 5 5 <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --> 6 - <meta http-equiv="Content-Security-Policy" content="script-src 'self';"> 6 + <meta http-equiv="Content-Security-Policy" content="script-src 'self'; worker-src blob:;"> 7 7 <title>peek:core:background</title> 8 8 </head> 9 9 <body>
+20 -3
app/components/peek-card.js
··· 286 286 } 287 287 288 288 /** 289 - * Focus the card (only works when interactive) 289 + * Focus the card (only works when interactive). 290 + * Waits for first render if shadow DOM isn't ready yet. 290 291 */ 291 - focus() { 292 - if (this.interactive) { 292 + async focus() { 293 + if (!this.interactive) return; 294 + const inner = this.shadowRoot?.querySelector('.card'); 295 + if (inner) { 296 + inner.focus(); 297 + } else { 298 + await this.updateComplete; 293 299 this.shadowRoot?.querySelector('.card')?.focus(); 300 + } 301 + } 302 + 303 + /** 304 + * Programmatic click — delegates to the inner .card div so the 305 + * @click handler fires and emits card-click. 306 + */ 307 + click() { 308 + const inner = this.shadowRoot?.querySelector('.card'); 309 + if (inner) { 310 + inner.click(); 294 311 } 295 312 } 296 313 }
+1 -1
extensions/scripts/background.html
··· 2 2 <html> 3 3 <head> 4 4 <meta charset="utf-8"> 5 - <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';"> 5 + <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'; worker-src blob:;"> 6 6 <title>Scripts Extension</title> 7 7 </head> 8 8 <body>
+86 -44
extensions/scripts/script-executor.js
··· 102 102 } 103 103 104 104 /** 105 - * Execute script with timeout, capturing console output 105 + * Execute script with timeout, capturing console output. 106 + * Uses a Web Worker via blob URL to avoid CSP unsafe-eval restrictions 107 + * and to provide true timeout protection (worker.terminate() kills infinite loops). 106 108 * @param {string} code - JavaScript code to execute 107 - * @param {Document} document - Document object 108 - * @param {Window} window - Window object 109 + * @param {Document} doc - Document object (not available inside Worker) 110 + * @param {Window} win - Window object (not available inside Worker) 109 111 * @param {number} timeout - Timeout in milliseconds 110 112 * @returns {Promise<object>} Result with time, output, and result 111 113 */ 112 - async runScriptInContext(code, document, window, timeout) { 114 + async runScriptInContext(code, doc, win, timeout) { 113 115 const startTime = performance.now(); 114 - const logs = []; 115 116 116 - // Capture console output 117 - const originalLog = console.log; 118 - const originalError = console.error; 119 - const originalWarn = console.warn; 117 + // Build Worker code that: 118 + // 1. Provides minimal document/window stubs so scripts don't throw on DOM access 119 + // 2. Executes the user code in an async wrapper 120 + // 3. Posts the result (or error) back to the main thread 121 + const workerCode = ` 122 + // Minimal DOM stubs - querySelector returns null, etc. 123 + const document = { 124 + querySelector: () => null, 125 + querySelectorAll: () => [], 126 + getElementById: () => null, 127 + getElementsByClassName: () => [], 128 + getElementsByTagName: () => [], 129 + createElement: () => ({ style: {}, setAttribute: () => {}, appendChild: () => {} }), 130 + head: { appendChild: () => {} }, 131 + body: { appendChild: () => {} } 132 + }; 133 + const window = self; 120 134 121 - console.log = (...args) => { 122 - logs.push({ level: 'log', message: this.formatArgs(args) }); 123 - originalLog(...args); 124 - }; 125 - console.error = (...args) => { 126 - logs.push({ level: 'error', message: this.formatArgs(args) }); 127 - originalError(...args); 128 - }; 129 - console.warn = (...args) => { 130 - logs.push({ level: 'warn', message: this.formatArgs(args) }); 131 - originalWarn(...args); 132 - }; 135 + const __logs = []; 136 + const __origLog = console.log; 137 + const __origError = console.error; 138 + const __origWarn = console.warn; 139 + console.log = (...args) => { 140 + __logs.push({ level: 'log', message: args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' ') }); 141 + __origLog(...args); 142 + }; 143 + console.error = (...args) => { 144 + __logs.push({ level: 'error', message: args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' ') }); 145 + __origError(...args); 146 + }; 147 + console.warn = (...args) => { 148 + __logs.push({ level: 'warn', message: args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' ') }); 149 + __origWarn(...args); 150 + }; 133 151 134 - try { 135 - // Wrap in async function to support await 136 - const wrappedCode = ` 137 - (async () => { 138 - ${code} 139 - })() 140 - `; 152 + (async () => { 153 + try { 154 + const result = await (async () => { 155 + ${code} 156 + })(); 157 + postMessage({ type: 'result', result, logs: __logs }); 158 + } catch (e) { 159 + postMessage({ type: 'error', error: e.message, stack: e.stack, logs: __logs }); 160 + } 161 + })(); 162 + `; 163 + 164 + const blob = new Blob([workerCode], { type: 'application/javascript' }); 165 + const blobUrl = URL.createObjectURL(blob); 166 + const worker = new Worker(blobUrl); 167 + 168 + return new Promise((resolve, reject) => { 169 + const timer = setTimeout(() => { 170 + worker.terminate(); 171 + URL.revokeObjectURL(blobUrl); 172 + reject(new Error('Script timeout')); 173 + }, timeout); 141 174 142 - const fn = new Function('document', 'window', `return ${wrappedCode}`); 143 - const result = await Promise.race([ 144 - fn(document, window), 145 - new Promise((_, reject) => 146 - setTimeout(() => reject(new Error('Script timeout')), timeout) 147 - ) 148 - ]); 175 + worker.onmessage = (e) => { 176 + clearTimeout(timer); 177 + worker.terminate(); 178 + URL.revokeObjectURL(blobUrl); 179 + 180 + const { type, result, error, stack, logs } = e.data; 181 + if (type === 'error') { 182 + // Re-throw as an Error so the caller gets status: 'error' 183 + const err = new Error(error); 184 + err.stack = stack; 185 + reject(err); 186 + } else { 187 + resolve({ 188 + time: Math.round(performance.now() - startTime), 189 + output: logs || [], 190 + result 191 + }); 192 + } 193 + }; 149 194 150 - return { 151 - time: Math.round(performance.now() - startTime), 152 - output: logs, 153 - result 195 + worker.onerror = (e) => { 196 + clearTimeout(timer); 197 + worker.terminate(); 198 + URL.revokeObjectURL(blobUrl); 199 + reject(new Error(e.message || 'Worker error')); 154 200 }; 155 - } finally { 156 - console.log = originalLog; 157 - console.error = originalError; 158 - console.warn = originalWarn; 159 - } 201 + }); 160 202 } 161 203 162 204 /**
+244 -131
peek-todo.md
··· 8 8 - This file is not for notes or description - link to documents in ./notes for that 9 9 - Checkbox states: `- [ ]` pending, `- [~]` in-progress (move to WIP.md), `- [x]` done (move to CHANGELOG.md) 10 10 11 - ## Design principles 11 + ## Design principles / capablities / etc 12 12 13 13 core 14 14 - feels like home: trust, comfort, control ··· 19 19 what makes a home 20 20 - everything is right where you need it, b/c you control what is where 21 21 - when you know what is where, you can make things without frustration 22 + - you generally know what’s happening - who’s around/coming/going, etc, not a lot of surprises 23 + 24 + what makes magical mind-readingness 25 + - frecency everywhere all of the time: user actions are training data for sorting 26 + 27 + what provides awareness 28 + - actions tracked at the core 29 + - metrics generated, rollups displayed, synthesis emergent 22 30 23 31 synthesis 24 32 - frecency + adaptive matching gives experience/feeling of magical mind-readingness 25 33 - ability to customize/create/generate interfaces gives the comfort of home 26 34 27 - cf 28 - - bulthaup - german kitchen company w/ designs based on carpentry workshops 29 - 30 35 the rules 31 36 - files > arcane/opaque boxes/formats 32 37 - metadata can be weird non-file, as long as consistent 33 38 - external systems require consent to touch my stuff (eg http caching rules) 34 39 40 + key capabilities from action/application history chain + addressibility 41 + 42 + - record/replay 43 + - state feedback loops 44 + - observability 45 + 46 + interface spectrum 47 + - consistent predicable interfaces are easy, off-the-shelf 48 + - weird/generative possible via power trio: backgrounding, wm apis, web surface 49 + 50 + cf 51 + - bulthaup - german kitchen maker w/ designs based on carpentry workshops 52 + 35 53 ## Unfiled 36 54 37 55 context 38 - - [ ] implement old context plan eg https://www.reddit.com/r/hackernews/comments/1qddidm/sun_position_calculator/ 39 - 40 - server 41 - - [ ] edgeworkernode/server vs what we got now? both? lite-version, or this it? 56 + - [ ] implement old context plan: https://github.com/autonome/context 57 + - [ ] implement as extension, expose as api or data emitter? 58 + - eg https://www.reddit.com/r/hackernews/comments/1qddidm/sun_position_calculator/ 42 59 43 60 peeks on links 44 61 - [ ] click modifier to one-off peek a link 45 62 - [ ] anchored to cursor w/in window bounds 46 63 - [ ] as an extension? hotkey + page viewer 47 64 48 - once we have cardinal ui 65 + ## To make 66 + 49 67 - [ ] option to flash keyboard shortcuts on screen 50 68 - [ ] pop up a board of built-in shortcuts/actions 51 69 - [ ] pop up a board of common shortcuts/actions you use 52 70 53 - ## Addessibility / Core history / feeds 71 + - [ ] daily ribbon along bottom of screen, populated with actions and favicons of pages loaded (filter on web only?) 54 72 55 - For record/replay, daily ribbon, state feedback loops and observability, etc we need a complete chained history. 56 - All of those require addressibility of all primary actions, and connections to prev/next actions. 57 - Includes any peek:// invocation and parameters passed. 58 - May require the connector/parameter context for each invocation, tbd. 59 - Requires explicit chaining. 60 - 61 - Review against impl 62 73 - [ ] step counter: app level interaction tracing/counting. when is reset? when does action end and new one start? 63 - - [ ] peeks/slides as tagged addresses with metadata properties? or urls? 74 + 75 + - may require connector/parameter context for each peek address load and connector invocation in a chained command 76 + 77 + - peeks/slides as addresses + metadata? or urls? eg open({json of peek url + context}) => localhost in left slide, or peek://slide?where=top&url=http://localhost? both? opens up the idea of packaging these declaratively, and no-code createable 64 78 65 79 ## UI Componentry 66 80 ··· 157 171 - [ ] tag input field 158 172 - [ ] combo of selected tags, input w/ filtering search, available tags 159 173 174 + ## Web page experience (reviewme: partially done) 175 + 176 + Page loading core 177 + - [ ] how to load pages - raw browserwindow (what we do now), webview in a default transparent page that hosts overlay?? 178 + - [ ] overlay infrastructure for showing metadata, security info 179 + - [ ] interaction with cmd actions (page mode again?) 180 + 181 + Basic nav etc 182 + - [ ] hotkey to select url 183 + - [ ] back/forward 184 + - [ ] reload 185 + - [ ] undo last close 186 + - [ ] if url selected in cmd is open in a window already, switch to it (for now) 187 + 188 + Page info/metadata/action widgets (depends on window templates maybe?) 189 + - [ ] defaults, eg sec ui 190 + - [ ] metadata (og, whatnot) 191 + - [ ] media (imgs, rss, etc) 192 + - [ ] actions (new extension cmd type?) 193 + - [ ] scripts (tbd) 194 + 195 + Titlebar 196 + - [x] show titlebar on hover at top edge of window for all pages 197 + 198 + ## Feeds, time-series data 199 + 200 + We need a generalized system for feeds and time-series data. 201 + It might be two different systems, based on analysis of the use-cases. 202 + It might be a new system + the items system we already have, and using metadata. 203 + The use-cases below cover a variety of feed-like needs. 204 + 205 + Use-case: Querying web pages for time-series data 206 + - Pattern: checking urls, collecting web data over time 207 + - Eg a bank account balance, stock price, weather report 208 + - Periodically load a web page in background hiddent window, and run a content script against it, returning a value and writing it to datastore (and metadata like page url, created/updated, custom metadata) 209 + - Have a widget that shows latest value and its age 210 + - Have a widget that shows graph of values over time 211 + 212 + Use-case: Checking web page for ticket availability 213 + - Pattern: checking urls for a one-time trigger 214 + - Want to know when tickets go on sale for an event 215 + - Periodically load a page in background, and run a content script against it 216 + - Notify user when page change matched some criteria 217 + 218 + Use-case: Pull data from external systems 219 + - Pattern: harvesting 3rd party content feeds 220 + - Eg RSS feeds or importing my posts/etc from other accounts - plays on Soundcloud, or thumbs-ups on Youtube, etc 221 + - Imagine an extension which walks you through Oauth login for various systems, and then pulls their data into Peek via the system's API. Could be URLs with custom metadata (like Soundcloud plays) or non-URL data (like sports scores) 222 + - Or someone could implement a feed reader extension focused on RSS reading 223 + 224 + Use-case: System metrics 225 + - Pattern: aggregating system metadata 226 + - We need to be able to log and reflect all kinds of system information 227 + - See the "Metadata, QS and reflection" section in this document for examples 228 + - Other examples: datastore size-on-disk, ongoing performance and resource usage data 229 + - Surface these in diagnostic panels, widgets, HUDs, etc 230 + 231 + Use-case: Tag streaks, group change feeds 232 + - Pattern: aggregating sysetm user data 233 + - tag streaks - eg one day you add tags "pushups, 10", next day "pushups, 20", then be able to query feed data for number of days in a row, max pushups, graph of pushups over time, longest streak, etc 234 + - show in widgets 235 + - push individual records or any rollup as an atproto record to get social streaking 236 + 237 + ## Entity-centrism (aka NER indices) 238 + 239 + Many significant entities in our lives are singletons. 240 + A person, a place, an event. 241 + Even replaceable objects are singletons, eg "my Ford Escort". 242 + The web makes the publisher the apex of understanding. 243 + Browsers do not not acknowledge entities across pages. 244 + If you load two pages about the band Tortoise, the browser only knows you loaded two different domains. 245 + Peek's entity-centrism projects uplifts the apex of understanding from publishers, into the user agent. 246 + The application identifies and records entities in the pages you load. 247 + Aims for approximate equivalancy via confidence threshold scoring. 248 + The expirement starts with coarse types, eg person, place, event, organization. 249 + Stores these in indices in local datastore. 250 + The indicies are syncable with other Peek instances. 251 + The indices store references to the URLs entities were detected on. 252 + The source references provide reproducibility, entity history/context, and bidirectional linking and interactions. 253 + Provides APIs for building on top of the indexes in Peek extensions. 254 + Uses Wikidata for a first experiment in entity validation and triangulation. 255 + Uses the sourcing data to infer links between entities. 256 + Scores entities based on frecency of observation and interaction. 257 + 258 + - [ ] get people, places, dates/times/events 259 + - [ ] get meaningful numbers, and their label, eg phone 260 + 261 + - [ ] extract a table as csv 262 + - [ ] layer outside of web page, and in between pages (eg event page -> event -> any calendar page) 263 + 264 + - [ ] Entity catalog definition (eg Wikidata defs, or custom to start?) 265 + - [ ] Datastore support 266 + - [ ] Basic NER testing (regex, etc) 267 + - [ ] Page metadata viz 268 + - [ ] Entity search/browse 269 + - [ ] ML NER 270 + 271 + ## Widgets 272 + 273 + notes/questions 274 + - maybe just cards in a sheet, fillable 275 + - api for extensions to publish into 276 + - fill either template+data 277 + - or extension publishing into, iframe-like 278 + - review w3c spec 279 + - are widgets mini-apps (in spec sense) 280 + - what aspects of tiles do they have? 281 + - should be called widgets, or something else? 282 + 283 + core uses 284 + - page metadata panels 285 + - command previews 286 + - observability hud 287 + - calculators 288 + 289 + helpers 290 + - widget sheets? overlays of widgets, etc - like window manager views/templates, which don't require extensions, reusable 291 + 292 + - fun with feeds + widgets 293 + - daytum style custom tracking/displaying 294 + 295 + - sheet widgets - tabular data in a reactive resizable widget view (chain query -> tabular -> sheet -> widget) 296 + 160 297 ## Modes/scopes 161 298 162 299 notes ··· 175 312 - [ ] How does cmd indicate scope/target? 176 313 - [ ] eg "Target: [window title]" header when window-scoped command is selected? 177 314 178 - ## Web page experience (reviewme: partially done) 315 + ## Content-/User-scripts 316 + 317 + - [ ] Command support for blocking on a content script running 318 + - [ ] Extension api for executing arbitrary scripts against a page 319 + - [ ] Timeouts for page scripts in commands 320 + - [ ] Support for scheduling scripts (or maybe that's just in the extension... harder to manage tho) 321 + - [ ] Page load triggers for background scripts 179 322 180 - Page loading core 181 - - [ ] how to load pages - raw browserwindow (what we do now), webview in a default transparent page that hosts overlay?? 182 - - [ ] overlay infrastructure for showing metadata, security info 183 - - [ ] interaction with cmd actions (page mode again?) 323 + 324 + ## Browser status quo extensibility 184 325 185 - Basic nav etc 186 - - [ ] hotkey to select url 187 - - [ ] back/forward 188 - - [ ] reload 189 - - [ ] undo last close 190 - - [ ] if url selected in cmd is open in a window already, switch to it (for now) 326 + Status quo 327 + - [ ] Browser extensions (limited, to get a couple of popular ones working) 328 + - [ ] Opensearch plugins 329 + - [ ] Quicksearch 330 + - [ ] Bookmark keywords (equivalent) 331 + - [ ] Bookmarklets (equivalent) 332 + - [ ] Userscripts (cf general approach to content/user scripts) 333 + - [ ] Language packs (cf general approach to i18n/l10n) 191 334 192 - Page info/metadata/action widgets (depends on window templates maybe?) 193 - - [ ] defaults, eg sec ui 194 - - [ ] metadata (og, whatnot) 195 - - [ ] media (imgs, rss, etc) 196 - - [ ] actions (new extension cmd type?) 197 - - [ ] scripts (tbd) 335 + Search 336 + - [ ] Local 337 + - [ ] OpenSearch 198 338 199 - Titlebar 200 - - [ ] show titlebar on hover at top edge of window for all pages 339 + Web extensions 340 + - [x] WebExtension integration for bundled extensions (Electron, using electron-chrome-extensions) 341 + - [x] @cliqz/adblocker-electron for native ad blocking 342 + - [x] Consent-O-Matic for automatic cookie consent handling (MIT, Aarhus University) 343 + - [ ] Proton Pass for password management 344 + - [ ] Enable/disable toggles in Settings UI 201 345 202 346 ## Metadata, QS and reflection 203 347 204 - - [ ] tabstats for peek 348 + - [ ] basically tabstats but for peek: https://github.com/autonome/tabstatistics 205 349 - [ ] view: a page of different widgets showing this info, or a hud/dashboard 206 350 351 + collections (depends on feeds?) 352 + - interaction count 353 + - page load count 354 + - page open 355 + - page switch 356 + - page close 357 + - open window count 358 + - hidden+visible window count 359 + 360 + interaction tracing/counting 361 + - [ ] when is reset? what makes a discrete action start/stop/change? 362 + 363 + hud playground 364 + - [ ] hud for system data (number of windows, etc 365 + - [ ] using timeseries/feeds api 366 + - [ ] each as widgets 367 + 368 + ## History 369 + 370 + - [ ] history views (again using groups ui, maybe plug that into an extension itself?) 371 + 372 + History (depends on ui primitives) 373 + - [ ] history viewer 374 + - [ ] history search 375 + - [ ] Infinite lossless personal encrypted archive of web history 376 + 207 377 ## Files-ness 208 378 209 379 - [ ] access to notes folder(s) on filesystem to import+sync 210 380 - [ ] syncing peek-only ontes as markdown files in specified dir (or library, boo) 381 + 382 + manual imports 211 383 - [ ] import signal note-to-self archive into peek notes 384 + - [ ] stickiets 212 385 213 386 ## Accounts/profiles/sync safety/fidelity 214 387 ··· 223 396 - [ ] shared libs, eg utils 224 397 - [ ] language: call them feature or apps? other? extensions? mods? 225 398 226 - ## Izui 227 - 228 - formalizing and stabilizing Peek’s window management system 229 - 230 - immediate 231 - - [ ] hotfix: disable escape-to-close window when peek app is focused application in the OS 232 - 233 - model formalization 234 - - [ ] review the windowing approach used in ./app to manage windows by analyzing the source code of it and the peek extensions 235 - - [ ] formalize that review into a state machine or other declarative set of rules which let’s us easily reason about, revise, and generate code and tests for it 236 - - [ ] implement izui window manager based on those rules 237 - 238 - key pieces 239 - - [ ] esc works when global hotkeys are executed and peek is not focused 240 - - [ ] in-app navigations with escape, eg moving from sub items in settings back to settings default pane 241 - - [ ] centralized place we add to history chain 242 - 243 - ## Polish 244 - 245 - - [ ] (already done?) if no api key set, sync settings are disabled, and pull-to-sync on mobile 246 399 247 400 ## Window templates 248 401 ··· 253 406 254 407 ## Pagestream 255 408 256 - - a new peek web navigational system 409 + A web navigational system with a vertical chat-like view of web history. 410 + 257 411 - vertical up/down chat-style history of pages/actions 258 - - left/right for page-specific stuff 259 - - maybe uses carousels + window template from ui componentry? 412 + - but floating cards, not chat bubbles in a column 413 + - left/right for page-specific metadata/actions 414 + - uses vertical carousel component + page view 415 + - vim navigational keys 416 + - active card pops up/open 417 + - screenshots for pages 418 + - open new page == scroll to bottom == open a "new tab" kind of view 419 + - cmd+L -> url, adds to bottom of "chat" 260 420 261 421 ## Notes & Editor 262 422 ··· 272 432 - editing in command chaining interstitials (edit cmd can apply to anything text-ish) 273 433 - OS level handler for editing files on filesystem 274 434 275 - Implementation 276 - - [ ] import from ~/misc/peek-editor, put in ./extensions/editor for now 277 - - [ ] evaluate using raw codemirror which is like “toolkit for an editor" 278 - - [ ] evaluate using https://github.com/MarkEdit-app/MarkEdit or its approach 435 + Implementation 436 + - [x] CodeMirror integrated with three-panel layout (outline, editor, preview) 437 + - [x] Vim mode toggle with settings persistence 438 + - [x] Live markdown preview sidebar 439 + - [x] Outline navigation from headers 279 440 280 441 Features 281 442 - [ ] add support for paste operations ··· 404 565 - [ ] take and save screenshot of loaded windows for any new address 405 566 - [ ] store in profile screenshot cache, and save that as metadata record on the address 406 567 407 - ## Extension back-end 408 - 409 - - [ ] tbd 410 568 411 569 ## Server Backend 412 570 ··· 421 579 - [ ] Add migration dry-run mode 422 580 - [ ] Add automatic backup cleanup after grace period 423 581 424 - ## harvester / hearts and stars 582 + ## Extension: harvester 583 + 584 + - [ ] push all services to peek node webhook, eg bsky likes, reddit 585 + 586 + - oauthwonderwall? 587 + 588 + ## Extension: hearts and stars 425 589 426 - - [ ] push all services to peek node webhook, eg bsky like, reddit, oauthwonderwall? 590 + - tweetdeck-like column set, but narrower and each a feed 427 591 428 592 ## Mobile 429 593 ··· 436 600 - [ ] export/import 437 601 - [ ] session restore 438 602 439 - ## Browser status quo extensibility 440 - 441 - Status quo 442 - - [ ] Browser extensions (limited, to get a couple of popular ones working) 443 - - [ ] Opensearch plugins 444 - - [ ] Quicksearch 445 - - [ ] Bookmark keywords (equivalent) 446 - - [ ] Bookmarklets (equivalent) 447 - - [ ] Userscripts (cf general approach to content/user scripts) 448 - - [ ] Language packs (cf general approach to i18n/l10n) 449 - 450 - Search 451 - - [ ] Local 452 - - [ ] OpenSearch 453 - 454 - Web extensions 455 - - [ ] WebExtension integration for priority only, on some platforms, some back-ends 456 - - [ ] Electron first, using https://github.com/samuelmaddock/electron-browser-shell/tree/master/packages/electron-chrome-extensions 457 - - [ ] uBlock Origin 458 - - [ ] Proton Pass 459 - - [ ] Bypass Paywalls Clean 460 - 461 - ## Feeds, time-series, scripts 462 - 463 - - [ ] API for logging outputs to datastore (time series data, feeds) 464 - - [ ] Command support for blocking on a content script running 465 - - [ ] Extension api for executing arbitrary scripts against a page 466 - - [ ] Timeouts for page scripts in commands 467 - - [ ] Support for scheduling scripts (or maybe that's just in the extension... harder to manage tho) 468 - - [ ] Page load triggers for background scripts 469 - 470 - - [ ] tag streaks -> atproto streaks (feeds + daytum) 471 - - [ ] hud for system data (number of windows, etc - using timeseries/feeds in datastore + page metadata / daytum / widgets framework) (widget sheets? kinda like window manager views/templates?) 472 - 473 - ## Entity centrism (NER streams) 474 - 475 - - [ ] get people, places, dates/times/events 476 - - [ ] get meaningful numbers, and their label 477 - - [ ] extract a table as csv 478 - - [ ] layer outside of web page, and in between pages (eg event page -> event -> any calendar page) 479 - 480 - 481 - - [ ] Entity catalog definition (eg Wikidata defs, or custom to start?) 482 - - [ ] Datastore support 483 - - [ ] Basic NER testing (regex, etc) 484 - - [ ] Page metadata viz 485 - - [ ] Entity search/browse 486 - - [ ] ML NER 487 - 488 603 ## Archiving / expiration 489 604 490 605 - [ ] archived notes (lower score, hidden by default) ··· 492 607 ## Sorting/scoring/magic 493 608 494 609 Generally default is based on the old Firefox "awesomebar" scoring/search algorithms. 610 + 495 611 Frecency + adaptive matching. 612 + 496 613 The app *learns* you, and what you want magically appears w/o AI as requirement. 497 614 498 615 ## Desktop Performance ··· 525 642 - [ ] Peeks: translate, calendar, ai chat, currency conversion, everytimezone, tldraw 526 643 - [ ] Slides: soundcloud, crypto prices, notepad, todo list 527 644 - [ ] Scripts: stock price, weather change 528 - 529 - ## History 530 - 531 - - [ ] history views (again using groups ui, maybe plug that into an extension itself?) 532 - 533 - History (depends on ui primitives) 534 - - [ ] history viewer 535 - - [ ] history search 536 - - [ ] Infinite lossless personal encrypted archive of web history 537 645 538 646 ## Publishing, Provenance, Remote Extensions 539 647 648 + misc ideas 540 649 - [ ] share system 541 650 - [ ] poke at remote loading + provenance 542 651 - [ ] publish pages/apps? 543 - - [ ] local publishing w/ Helia or something like this 652 + 653 + experiment: helia 654 + - [ ] local publishing, eg use editor to write a blog post, hit publish… and share (handwave) 544 655 545 656 ## Minimum viable web workbench 546 657 ··· 558 669 - [ ] Devtools command to open devtools for a specific extension or window 559 670 - [ ] Fix `api.extensions.devtools()` - currently not working for consolidated extensions 560 671 561 - ## Later 672 + ## Storage 562 673 563 - storage backends 564 674 - [ ] try DuckDB as datastore storage backend instead of SQLite 675 + 676 + 677 + ## Parking lot 565 678 566 679 - [ ] Identities system 567 680 - [ ] Contacts integration
+69
tests/desktop/smoke.spec.ts
··· 433 433 } 434 434 }); 435 435 436 + test('peek-card: Enter key activates card via card-click event', async () => { 437 + const bgWindow = sharedBgWindow; 438 + 439 + // Create a group with an item 440 + const tagResult = await bgWindow.evaluate(async () => { 441 + return await (window as any).app.datastore.getOrCreateTag('enter-key-test'); 442 + }); 443 + expect(tagResult.success).toBe(true); 444 + const tagId = tagResult.data?.tag?.id || tagResult.data?.data?.id || tagResult.data?.id; 445 + 446 + const item = await bgWindow.evaluate(async () => { 447 + return await (window as any).app.datastore.addItem('url', { 448 + content: 'https://enter-key-test.example.com', 449 + metadata: JSON.stringify({ title: 'Enter Key Test' }) 450 + }); 451 + }); 452 + expect(item.success).toBe(true); 453 + 454 + if (tagId && item.data?.id) { 455 + await bgWindow.evaluate(async ({ itemId, tagId }) => { 456 + return await (window as any).app.datastore.tagItem(itemId, tagId); 457 + }, { itemId: item.data.id, tagId }); 458 + } 459 + 460 + // Open groups window 461 + const groupsResult = await bgWindow.evaluate(async () => { 462 + return await (window as any).app.window.open('peek://ext/groups/home.html', { 463 + role: 'workspace', 464 + width: 800, 465 + height: 600 466 + }); 467 + }); 468 + expect(groupsResult.success).toBe(true); 469 + 470 + const groupsWindow = await sharedApp.getWindow('groups/home.html', 5000); 471 + expect(groupsWindow).toBeTruthy(); 472 + await groupsWindow.waitForLoadState('domcontentloaded'); 473 + await groupsWindow.waitForSelector('peek-card.group-card', { timeout: 5000 }); 474 + 475 + // Verify we're in groups view 476 + const viewBefore = await groupsWindow.evaluate(() => (window as any)._groupsState.view); 477 + expect(viewBefore).toBe('groups'); 478 + 479 + // Programmatically activate the first card (simulates Enter key path) 480 + const activated = await groupsWindow.evaluate(async () => { 481 + const card = document.querySelector('peek-card.group-card') as any; 482 + if (!card) return false; 483 + card.click(); 484 + return true; 485 + }); 486 + expect(activated).toBe(true); 487 + 488 + // Should navigate to addresses view 489 + await groupsWindow.waitForSelector('peek-card.address-card', { timeout: 5000 }); 490 + const viewAfter = await groupsWindow.evaluate(() => (window as any)._groupsState.view); 491 + expect(viewAfter).toBe('addresses'); 492 + 493 + // Clean up 494 + if (groupsResult.id) { 495 + try { 496 + await bgWindow.evaluate(async (id: number) => { 497 + return await (window as any).app.window.close(id); 498 + }, groupsResult.id); 499 + } catch { 500 + // Window may already be closed 501 + } 502 + } 503 + }); 504 + 436 505 test('active mode: ESC at root does NOT close window', async () => { 437 506 const bgWindow = sharedBgWindow; 438 507