experiments in a post-browser web
10
fork

Configure Feed

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

refactor: migrate callers from legacy peek://ext/id to v2 peek://id

Both URL schemes resolved to the same filesystem path via the
backward-compat shim in backend/electron/protocol.ts. The legacy
form is a v1 leftover. Mixed usage caused real bugs this session
(groups resident:true broken by a Playwright substring-match
collision between resident URL and test-opened URL), and the
general pattern is a landmine waiting to surface in whatever
tile gets resident next.

This commit migrates all CALLERS to the canonical v2 form. The
protocol.ts compat shim is intentionally left in place — removing
it is a follow-up once we're confident no external callers
(chrome extensions, tauri, long-lived session data) still point
at the legacy form.

By bucket:
- backend/electron/: 14 files (main.ts, ipc.ts, tile-ipc.ts,
tile-launcher.ts, session.ts + test, entry.ts, cmd-glue.ts,
hud-glue.ts, closed-window.test.ts, pubsub.test.ts,
lazy-loading.test.ts, izui-state.test.ts, space-mode.test.ts)
- features/: 30 files (background.js, home.js, manifests)
- app/: 7 files (cmd, config, settings, hud)
- tests/: 10 files (smoke, cmd-state-machine, cmd-chain,
websearch-cmd, module-health, groups-context, pagestream, hud,
fixtures, helpers, unit)

Non-trivial edits:
- tests/helpers/window-utils.ts + tests/fixtures/desktop-app.ts:
generic 'peek://ext/' prefix checks changed to
startsWith('peek://') + substring filter on path
- backend/electron/session.ts: URL validation regex replaced with
URL parser that handles both forms (still need to parse
session data saved under the legacy URL)
- backend/electron/tile-ipc.ts: log source shortener updated

Included in this commit:

ALSO includes the hud.spec.ts:393 flake fix — added a setMode
default + waitForHudModeValue handshake at the start of the
'HUD displays group mode with name' test. The test was racing
the mode widget's webview subscription on a freshly reopened HUD,
passing in isolation but failing when run after test 349 in the
full suite. Mirrors the proven pattern in test 152.

Tests: HUD Extension 10/10, Command Execution 8/8, no regressions.

+230 -208
+1 -1
app/cmd/background.js
··· 36 36 let registeredShortcut = null; 37 37 38 38 // Panel window address 39 - const panelAddress = 'peek://ext/cmd/panel.html'; 39 + const panelAddress = 'peek://cmd/panel.html'; 40 40 41 41 // In-memory settings cache 42 42 let currentSettings = {
+1 -1
app/cmd/commands.js
··· 1 1 /** 2 2 * Commands manager - loads and provides commands to the command panel 3 3 * 4 - * Runs in panel window context (peek://ext/cmd/panel.html) 4 + * Runs in panel window context (peek://cmd/panel.html) 5 5 * Queries cmd extension background for registered commands 6 6 */ 7 7 import { id, labels, schemas, storageKeys, defaults } from './config.js';
+1 -1
app/cmd/nouns.js
··· 5 5 * Uses only window.app pubsub (works on Electron, Tauri, browser extension). 6 6 * 7 7 * Usage: 8 - * import { registerNoun, unregisterNoun } from 'peek://ext/cmd/nouns.js'; 8 + * import { registerNoun, unregisterNoun } from 'peek://cmd/nouns.js'; 9 9 */ 10 10 11 11 const api = window.app;
+3 -3
app/cmd/panel.js
··· 1 1 /** 2 2 * Cmd Panel - command input and execution UI 3 3 * 4 - * Runs in isolated panel window (peek://ext/cmd/panel.html) 4 + * Runs in isolated panel window (peek://cmd/panel.html) 5 5 * Uses api.settings for persistent adaptive matching data 6 6 * 7 7 * State management is handled by the state machine module (state-machine.js). ··· 694 694 // ===== Chain Popup Functions ===== 695 695 696 696 const DEFAULT_POPUPS = { 697 - 'text/': 'peek://ext/cmd/chain-editor.html', 697 + 'text/': 'peek://cmd/chain-editor.html', 698 698 }; 699 699 700 700 function getDefaultPopupForMime(mimeType) { ··· 2222 2222 if (!trimmedText) return; 2223 2223 2224 2224 log('cmd:panel', 'Opening search for:', trimmedText); 2225 - api.window.open(`peek://ext/search/home.html?q=${encodeURIComponent(trimmedText)}`, { 2225 + api.window.open(`peek://search/home.html?q=${encodeURIComponent(trimmedText)}`, { 2226 2226 role: 'workspace', 2227 2227 key: 'search-home', 2228 2228 width: 700,
+6 -6
app/config.js
··· 183 183 { id: '82de735f-a4b7-4fe6-a458-ec29939ae00d', 184 184 name: 'Groups', 185 185 description: 'View your web in groups', 186 - start_url: 'peek://ext/groups/background.js', 186 + start_url: 'peek://groups/background.js', 187 187 enabled: false, 188 - settings_url: 'peek://ext/groups/settings.html', 188 + settings_url: 'peek://groups/settings.html', 189 189 extension: true, 190 190 }, 191 191 { id: 'ef3bd271-d408-421f-9338-47b615571e43', 192 192 name: 'Peeks', 193 193 description: 'Peek at pages in a transient popup using keyboard shortcuts', 194 - start_url: 'peek://ext/peeks/background.js', 194 + start_url: 'peek://peeks/background.js', 195 195 enabled: false, 196 - settings_url: 'peek://ext/peeks/settings.html', 196 + settings_url: 'peek://peeks/settings.html', 197 197 extension: true, 198 198 }, 199 199 { id: '434108f3-18a6-437a-b507-2f998f693bb2', 200 200 name: 'Slides', 201 201 description: 'Open web pages as side/top/bottom bars', 202 - start_url: 'peek://ext/slides/background.js', 202 + start_url: 'peek://slides/background.js', 203 203 enabled: false, 204 - settings_url: 'peek://ext/slides/settings.html', 204 + settings_url: 'peek://slides/settings.html', 205 205 extension: true, 206 206 } 207 207 ]
+5 -5
app/hud/background.js
··· 21 21 const api = window.app; 22 22 const debug = api.debug; 23 23 24 - const HUD_ADDRESS = 'peek://ext/hud/hud.html'; 24 + const HUD_ADDRESS = 'peek://hud/hud.html'; 25 25 const STORAGE_KEY = 'hud_enabled'; 26 26 const HUD_SHEET_KEY = 'hud_sheet'; 27 27 const TOGGLE_SHORTCUT = 'CommandOrControl+H'; ··· 34 34 const STATS_WIDGET_HEIGHT = 120; // taller for 6 metric rows 35 35 36 36 const DEFAULT_HUD_WIDGETS = [ 37 - { id: 'mode', url: 'peek://ext/hud/widgets/mode.html' }, 38 - { id: 'izui', url: 'peek://ext/hud/widgets/izui.html' }, 39 - { id: 'window', url: 'peek://ext/hud/widgets/window.html' }, 40 - { id: 'stats', url: 'peek://ext/hud/widgets/stats.html', height: STATS_WIDGET_HEIGHT } 37 + { id: 'mode', url: 'peek://hud/widgets/mode.html' }, 38 + { id: 'izui', url: 'peek://hud/widgets/izui.html' }, 39 + { id: 'window', url: 'peek://hud/widgets/window.html' }, 40 + { id: 'stats', url: 'peek://hud/widgets/stats.html', height: STATS_WIDGET_HEIGHT } 41 41 ]; 42 42 43 43 /**
+1 -1
app/hud/hud.js
··· 3 3 * 4 4 * Renders HUD widget pages in a peek-grid freeform layout. 5 5 * Each widget is a webview pointing to an individual widget page 6 - * (e.g., peek://ext/hud/widgets/mode.html). 6 + * (e.g., peek://hud/widgets/mode.html). 7 7 * 8 8 * The layout config is loaded from feature_settings (created by background.js). 9 9 */
+1 -1
app/settings/settings.js
··· 2256 2256 const { id, labels, schemas, storageKeys, defaults } = feature; 2257 2257 2258 2258 // Use extension shortname for datastore key (e.g., 'peeks', 'slides', 'websearch') 2259 - // This must match what api.settings uses (derived from peek://ext/{shortname}/... URL) 2259 + // This must match what api.settings uses (derived from peek://{shortname}/... URL) 2260 2260 const extId = feature.shortname || labels.name.toLowerCase(); 2261 2261 2262 2262 // Load from datastore
+5 -5
backend/electron/closed-window.test.ts
··· 84 84 const isKeepLive = params.keepLive === true; 85 85 const isModal = params.modal === true; 86 86 const isWebUrl = address && (address.startsWith('http://') || address.startsWith('https://')); 87 - const isPeekUrl = address && (address.startsWith('peek://ext/') || address.startsWith('peek://app/')); 87 + const isPeekUrl = address && address.startsWith('peek://'); 88 88 const isBackgroundPage = address && (address.includes('background.html') || address.includes('extension-host.html')); 89 89 const isReopenable = (isWebUrl || (isPeekUrl && !isBackgroundPage)) && !!address; 90 90 const isContentRole = !role || role === 'content' || role === 'child-content' || role === 'workspace'; ··· 292 292 assert.notStrictEqual(entry, null); 293 293 }); 294 294 295 - it('saves peek://ext/ URLs (feature windows)', () => { 295 + it('saves peek://<id>/ URLs (feature windows)', () => { 296 296 const entry = createClosedWindowEntry( 297 - { address: 'peek://ext/groups/home.html' }, 297 + { address: 'peek://groups/home.html' }, 298 298 null, 299 299 null, 300 300 'test-ext', 301 301 ); 302 302 assert.notStrictEqual(entry, null); 303 - assert.strictEqual(entry!.url, 'peek://ext/groups/home.html'); 303 + assert.strictEqual(entry!.url, 'peek://groups/home.html'); 304 304 }); 305 305 306 306 it('saves peek://app/ URLs (app pages)', () => { ··· 486 486 it('peek:// URLs do NOT get canvas bounds adjustment', () => { 487 487 const bounds = { x: 100, y: 200, width: 800, height: 600 }; 488 488 const entry = createClosedWindowEntry( 489 - { address: 'peek://ext/groups/home.html' }, 489 + { address: 'peek://groups/home.html' }, 490 490 null, 491 491 bounds, 492 492 'test-ext',
+1 -1
backend/electron/cmd-glue.ts
··· 18 18 * 19 19 * The cmd panel window itself (the visible palette UI) is created 20 20 * on-demand from the resident renderer via 21 - * `api.window.open('peek://ext/cmd/panel.html', ...)`. That path resolves 21 + * `api.window.open('peek://cmd/panel.html', ...)`. That path resolves 22 22 * to `app/cmd/panel.html` via protocol.ts. 23 23 * 24 24 * An eventual Tauri port would add backend/tauri/src-tauri/src/cmd_glue.rs
+3 -3
backend/electron/entry.ts
··· 170 170 // access to window.app API while leaving external web content webviews untouched. 171 171 // 172 172 // Resolution order (Phase 3.11b-hud — v1 preload.js removed): 173 - // 1. HUD widget webviews: peek://ext/hud/widgets/*.html get a dedicated 173 + // 1. HUD widget webviews: peek://hud/widgets/*.html get a dedicated 174 174 // trustedBuiltin token (hud-widget). HUD has no v2 manifest but 175 175 // widgets use api.window/context/izui/pubsub — all covered by 176 176 // trustedBuiltin grant. ··· 187 187 const tilePreloadPath = getTilePreloadPath(); 188 188 if (!tilePreloadPath) return; 189 189 190 - // Special-case: HUD widget webviews (peek://ext/hud/widgets/*.html). 190 + // Special-case: HUD widget webviews (peek://hud/widgets/*.html). 191 191 // HUD has no v2 manifest; mint a trustedBuiltin token for all widgets 192 192 // under a unified hud-widget tile id so they can use api.window.*, 193 193 // api.context.*, api.izui.*, api.pubsub.*, etc. 194 - if (params.src.startsWith('peek://ext/hud/widgets/')) { 194 + if (params.src.startsWith('peek://hud/widgets/')) { 195 195 const HUD_WIDGET_ID = 'hud-widget'; 196 196 const HUD_WIDGET_ENTRY = 'main'; 197 197 const hudWidgetGrant = createTrustedBuiltinGrant(HUD_WIDGET_ID);
+1 -1
backend/electron/hud-glue.ts
··· 16 16 * pubsub round-trip through the renderer. 17 17 * 18 18 * The HUD overlay window itself is created on-demand from the resident 19 - * renderer via `api.window.open('peek://ext/hud/hud.html', ...)`; that 19 + * renderer via `api.window.open('peek://hud/hud.html', ...)`; that 20 20 * path resolves to `app/hud/hud.html` via `protocol.ts`. 21 21 * 22 22 * An eventual Tauri port would add backend/tauri/src-tauri/src/hud_glue.rs
+5 -5
backend/electron/ipc.ts
··· 1079 1079 DEBUG && console.log('[window-open] settings window: assigned trustedBuiltin tile-preload token'); 1080 1080 } 1081 1081 1082 - // Special-case: peek://ext/hud/hud.html is the HUD overlay BrowserWindow 1082 + // Special-case: peek://hud/hud.html is the HUD overlay BrowserWindow 1083 1083 // opened on-demand by app/hud/background.js via api.window.open(). It 1084 1084 // hosts webview widget cards (app/hud/hud.js) and needs tile-preload for 1085 1085 // api.settings.get / api.window.resize. No v2 manifest — trustedBuiltin. 1086 - if (!tileWebPrefs && url === 'peek://ext/hud/hud.html') { 1086 + if (!tileWebPrefs && url === 'peek://hud/hud.html') { 1087 1087 const HUD_OVERLAY_ID = 'hud-overlay'; 1088 1088 const HUD_OVERLAY_ENTRY = 'main'; 1089 1089 const hudOverlayGrant = createTrustedBuiltinGrant(HUD_OVERLAY_ID); ··· 1123 1123 DEBUG && console.log('[window-open] datastore-viewer window: assigned trustedBuiltin tile-preload token'); 1124 1124 } 1125 1125 1126 - // Special-case: peek://ext/cmd/panel.html is the cmd palette UI, opened 1127 - // on-demand from the cmd resident renderer. peek://ext/cmd/chain-editor.html 1126 + // Special-case: peek://cmd/panel.html is the cmd palette UI, opened 1127 + // on-demand from the cmd resident renderer. peek://cmd/chain-editor.html 1128 1128 // is the chain editor opened from the panel. Both need tile-preload to call 1129 1129 // api.window.*, api.context.*, api.datastore.*, api.publish/subscribe, etc. 1130 - if (!tileWebPrefs && (url === 'peek://ext/cmd/panel.html' || url === 'peek://ext/cmd/chain-editor.html')) { 1130 + if (!tileWebPrefs && (url === 'peek://cmd/panel.html' || url === 'peek://cmd/chain-editor.html')) { 1131 1131 const CMD_UI_ID = 'cmd-ui'; 1132 1132 const cmdUiEntry = url.endsWith('panel.html') ? 'panel' : 'chain-editor'; 1133 1133 const cmdUiGrant = createTrustedBuiltinGrant(CMD_UI_ID);
+1 -1
backend/electron/izui-state.test.ts
··· 34 34 _destroyed: options.destroyed ?? false, 35 35 _focused: options.focused ?? false, 36 36 _visible: options.visible ?? true, 37 - _url: options.url ?? `peek://ext/test/window-${id}.html`, 37 + _url: options.url ?? `peek://test/window-${id}.html`, 38 38 isDestroyed: function() { return this._destroyed; }, 39 39 isFocused: function() { return this._focused; }, 40 40 isVisible: function() { return this._visible; },
+12 -12
backend/electron/lazy-loading.test.ts
··· 38 38 pubsub.unsubscribe(stubSource, topic); 39 39 40 40 // Subscribe the "real" handler 41 - pubsub.subscribe('peek://ext/test/', pubsub.scopes.GLOBAL, topic, (m: unknown) => { 41 + pubsub.subscribe('peek://test/', pubsub.scopes.GLOBAL, topic, (m: unknown) => { 42 42 replayed = m; 43 43 }); 44 44 ··· 57 57 assert.deepStrictEqual(replayed, { arg: 'hello' }); 58 58 59 59 // Cleanup 60 - pubsub.unsubscribe('peek://ext/test/', topic); 60 + pubsub.unsubscribe('peek://test/', topic); 61 61 }); 62 62 63 63 it('concurrent invocations should buffer latest message only', async () => { ··· 90 90 pubsub.unsubscribe(stubSource, topic); 91 91 92 92 // Subscribe the "real" handler 93 - pubsub.subscribe('peek://ext/test2/', pubsub.scopes.GLOBAL, topic, (m: unknown) => { 93 + pubsub.subscribe('peek://test2/', pubsub.scopes.GLOBAL, topic, (m: unknown) => { 94 94 replayed = m; 95 95 }); 96 96 ··· 113 113 assert.deepStrictEqual(replayed, { version: 2 }, 'Should replay latest message'); 114 114 115 115 // Cleanup 116 - pubsub.unsubscribe('peek://ext/test2/', topic); 116 + pubsub.unsubscribe('peek://test2/', topic); 117 117 }); 118 118 119 119 it('per-command stub sources should be independent', () => { ··· 150 150 pubsub.unsubscribe(interceptorSource, topic); 151 151 152 152 // Subscribe the "real" handler 153 - pubsub.subscribe('peek://ext/editor/', pubsub.scopes.GLOBAL, topic, (m: unknown) => { 153 + pubsub.subscribe('peek://editor/', pubsub.scopes.GLOBAL, topic, (m: unknown) => { 154 154 replayed = m; 155 155 }); 156 156 ··· 164 164 165 165 assert.deepStrictEqual(replayed, { file: 'test.txt' }); 166 166 167 - pubsub.unsubscribe('peek://ext/editor/', topic); 167 + pubsub.unsubscribe('peek://editor/', topic); 168 168 }); 169 169 170 170 it('concurrent events should buffer latest', async () => { ··· 188 188 189 189 pubsub.unsubscribe(interceptorSource, topic); 190 190 191 - pubsub.subscribe('peek://ext/editor2/', pubsub.scopes.GLOBAL, topic, (m: unknown) => { 191 + pubsub.subscribe('peek://editor2/', pubsub.scopes.GLOBAL, topic, (m: unknown) => { 192 192 replayed = m; 193 193 }); 194 194 ··· 204 204 205 205 assert.deepStrictEqual(replayed, { file: 'b.txt' }, 'Should replay latest event'); 206 206 207 - pubsub.unsubscribe('peek://ext/editor2/', topic); 207 + pubsub.unsubscribe('peek://editor2/', topic); 208 208 }); 209 209 210 210 it('per-topic interceptor sources should be independent', () => { ··· 228 228 it('should detect when all command subscribers are present', () => { 229 229 const topics = ['cmd:execute:a', 'cmd:execute:b', 'cmd:execute:c']; 230 230 for (const t of topics) { 231 - pubsub.subscribe(`peek://ext/test/${t}`, pubsub.scopes.GLOBAL, t, () => {}); 231 + pubsub.subscribe(`peek://test/${t}`, pubsub.scopes.GLOBAL, t, () => {}); 232 232 } 233 233 234 234 const allPresent = topics.every(t => pubsub.hasSubscriber(t)); 235 235 assert.strictEqual(allPresent, true); 236 236 237 237 for (const t of topics) { 238 - pubsub.unsubscribe(`peek://ext/test/${t}`, t); 238 + pubsub.unsubscribe(`peek://test/${t}`, t); 239 239 } 240 240 }); 241 241 242 242 it('should detect missing subscribers', () => { 243 - pubsub.subscribe('peek://ext/test/x', pubsub.scopes.GLOBAL, 'cmd:execute:present', () => {}); 243 + pubsub.subscribe('peek://test/x', pubsub.scopes.GLOBAL, 'cmd:execute:present', () => {}); 244 244 245 245 assert.strictEqual(pubsub.hasSubscriber('cmd:execute:present'), true); 246 246 assert.strictEqual(pubsub.hasSubscriber('cmd:execute:missing'), false); 247 247 248 - pubsub.unsubscribe('peek://ext/test/x', 'cmd:execute:present'); 248 + pubsub.unsubscribe('peek://test/x', 'cmd:execute:present'); 249 249 }); 250 250 251 251 it('should work with zero expected topics', () => {
+5 -5
backend/electron/main.ts
··· 442 442 const isKeepLive = windowData.params.keepLive === true; 443 443 const isModal = windowData.params.modal === true; 444 444 const isWebUrl = address && (address.startsWith('http://') || address.startsWith('https://')); 445 - const isPeekUrl = address && (address.startsWith('peek://ext/') || address.startsWith('peek://app/')); 445 + const isPeekUrl = address && address.startsWith('peek://'); 446 446 const isBackgroundPage = address && (address.includes('background.html') || address.includes('extension-host.html')); 447 447 const isReopenable = (isWebUrl || (isPeekUrl && !isBackgroundPage)) && !!address; 448 448 const isContentRole = !role || role === 'content' || role === 'child-content' || role === 'workspace'; ··· 606 606 * the command metadata to the cmd registry. 607 607 */ 608 608 function registerDeclarativeCommand(extId: string, cmd: ManifestCommand): void { 609 - const source = `peek://ext/${extId}/`; 609 + const source = `peek://${extId}/`; 610 610 611 611 // Subscribe to execution requests from the cmd panel 612 612 subscribe(source, scopes.GLOBAL, `cmd:execute:${cmd.name}`, (msg: unknown) => { ··· 634 634 * Maps the shortcut keys to execute the associated command's action. 635 635 */ 636 636 function registerDeclarativeShortcut(extId: string, shortcut: ManifestShortcut, commands: ManifestCommand[]): void { 637 - const source = `peek://ext/${extId}/`; 637 + const source = `peek://${extId}/`; 638 638 639 639 // Find the associated command 640 640 const cmd = commands.find(c => c.name === shortcut.command); ··· 647 647 DEBUG && console.log(`[ext:declarative] Shortcut triggered: ${shortcut.keys} -> ${shortcut.command}`); 648 648 if (cmd.action.type === 'execute') { 649 649 // For execute-type commands, publish to cmd:execute so lazy stubs can intercept 650 - publish(`peek://ext/${extId}/`, scopes.GLOBAL, `cmd:execute:${cmd.name}`, {}); 650 + publish(`peek://${extId}/`, scopes.GLOBAL, `cmd:execute:${cmd.name}`, {}); 651 651 } else { 652 652 executeDeclarativeAction(cmd, {}); 653 653 } ··· 804 804 }); 805 805 806 806 // Register the command metadata with the cmd registry 807 - const extSource = `peek://ext/${extId}/`; 807 + const extSource = `peek://${extId}/`; 808 808 publish(extSource, scopes.GLOBAL, 'cmd:register', { 809 809 name: cmd.name, 810 810 description: cmd.description || '',
+2 -2
backend/electron/pubsub.test.ts
··· 140 140 // Only stub subscriber — should return false when excluding stubs 141 141 assert.strictEqual(pubsub.hasSubscriber('test:has:excl', undefined, 'lazy-stub/'), false); 142 142 // Add a real subscriber 143 - pubsub.subscribe('peek://ext/real/', pubsub.scopes.GLOBAL, 'test:has:excl', () => {}); 143 + pubsub.subscribe('peek://real/', pubsub.scopes.GLOBAL, 'test:has:excl', () => {}); 144 144 assert.strictEqual(pubsub.hasSubscriber('test:has:excl', undefined, 'lazy-stub/'), true); 145 145 pubsub.unsubscribe('lazy-stub/foo', 'test:has:excl'); 146 - pubsub.unsubscribe('peek://ext/real/', 'test:has:excl'); 146 + pubsub.unsubscribe('peek://real/', 'test:has:excl'); 147 147 }); 148 148 }); 149 149 });
+4 -4
backend/electron/session.test.ts
··· 1434 1434 1435 1435 it('skips non-focusable utility windows like HUD (role=utility, focusable=false)', () => { 1436 1436 const skip = shouldSkipWindow( 1437 - 'peek://ext/hud/panel.html', 1437 + 'peek://hud/panel.html', 1438 1438 { role: 'utility', focusable: false }, 1439 1439 true, 1440 1440 false ··· 1464 1464 1465 1465 it('skips modal windows', () => { 1466 1466 const skip = shouldSkipWindow( 1467 - 'peek://ext/cmd/panel.html', 1467 + 'peek://cmd/panel.html', 1468 1468 { modal: true }, 1469 1469 true, 1470 1470 false ··· 1524 1524 1525 1525 it('includes extension windows with non-utility role', () => { 1526 1526 const skip = shouldSkipWindow( 1527 - 'peek://ext/tags/home.html', 1527 + 'peek://tags/home.html', 1528 1528 { role: 'content' }, 1529 1529 true, 1530 1530 false ··· 1880 1880 const windowDescriptors = [ 1881 1881 makeDescriptor({ url: 'https://github.com' }), 1882 1882 makeDescriptor({ url: 'https://example.com' }), 1883 - makeDescriptor({ url: 'peek://ext/groups/home.html' }), // non-web, should be skipped 1883 + makeDescriptor({ url: 'peek://groups/home.html' }), // non-web, should be skipped 1884 1884 ]; 1885 1885 1886 1886 // Simulate the before-quit logic from session.ts
+19 -8
backend/electron/session.ts
··· 296 296 suppressClosedWindowPush(); 297 297 for (const descriptor of windowDescriptors) { 298 298 const isWebUrl = descriptor.url.startsWith('http://') || descriptor.url.startsWith('https://'); 299 - const isPeekUrl = descriptor.url.startsWith('peek://ext/') || descriptor.url.startsWith('peek://app/'); 299 + const isPeekUrl = descriptor.url.startsWith('peek://'); 300 300 const isBackgroundPage = descriptor.url.includes('background.html') || descriptor.url.includes('extension-host.html'); 301 301 const isReopenable = isWebUrl || (isPeekUrl && !isBackgroundPage); 302 302 if (!isReopenable) continue; ··· 330 330 } 331 331 DEBUG && console.log(`[session] Pushed ${windowDescriptors.filter(d => { 332 332 const isWeb = d.url.startsWith('http://') || d.url.startsWith('https://'); 333 - const isPeek = d.url.startsWith('peek://ext/') || d.url.startsWith('peek://app/'); 333 + const isPeek = d.url.startsWith('peek://'); 334 334 const isBg = d.url.includes('background.html') || d.url.includes('extension-host.html'); 335 335 return isWeb || (isPeek && !isBg); 336 336 }).length} open window(s) to closed window stack`); ··· 413 413 } 414 414 415 415 // Validate extension URLs reference a known extension 416 - // (e.g. peek://ext/me:core/... from a renamed extension would be stale) 417 - const extMatch = descriptor.url.match(/^peek:\/\/ext\/([^/]+)/); 418 - if (extMatch) { 419 - const extId = extMatch[1]; 416 + // (e.g. peek://me:core/... from a renamed extension would be stale) 417 + let extIdFromUrl: string | null = null; 418 + try { 419 + const u = new URL(descriptor.url); 420 + if (u.protocol === 'peek:') { 421 + if (u.host === 'ext') { 422 + // Legacy form: peek://ext/<id>/... 423 + extIdFromUrl = u.pathname.split('/').filter(Boolean)[0] ?? null; 424 + } else if (u.host !== 'app') { 425 + // v2 canonical: peek://<id>/... 426 + extIdFromUrl = u.host; 427 + } 428 + } 429 + } catch { /* unparseable URL */ } 430 + if (extIdFromUrl) { 420 431 const knownIds = getRegisteredExtensionIds(); 421 - if (!knownIds.includes(extId)) { 422 - DEBUG && console.log(`[session] Filtering stale extension window: ${extId}`); 432 + if (!knownIds.includes(extIdFromUrl)) { 433 + DEBUG && console.log(`[session] Filtering stale extension window: ${extIdFromUrl}`); 423 434 return false; 424 435 } 425 436 }
+1 -1
backend/electron/space-mode.test.ts
··· 311 311 }); 312 312 313 313 it('detects "default" for peek:// URLs', () => { 314 - assert.strictEqual(detectModeFromUrl('peek://ext/spaces/home.html'), 'default'); 314 + assert.strictEqual(detectModeFromUrl('peek://spaces/home.html'), 'default'); 315 315 }); 316 316 317 317 it('detects "default" for empty string', () => {
+2 -2
backend/electron/tile-ipc.ts
··· 5173 5173 5174 5174 // NOTE: `feature-publish:upload-blob` and `feature-publish:create-record` IPC 5175 5175 // handlers have been removed. The publish flow now calls lex's renderer-side 5176 - // atproto helpers (uploadBlob / xrpcPost from peek://ext/lex/atproto.js) 5176 + // atproto helpers (uploadBlob / xrpcPost from peek://lex/atproto.js) 5177 5177 // directly so that DPoP signing happens in the renderer via Web Crypto — 5178 5178 // no backend DPoP implementation is needed. 5179 5179 ··· 6615 6615 } 6616 6616 const shortSource = (args.source ?? grant.tileId) 6617 6617 .replace('peek://app/', '') 6618 - .replace('peek://ext/', 'ext/'); 6618 + .replace('peek://', ''); 6619 6619 console.log(`[${shortSource}]`, ...(args.args ?? [])); 6620 6620 }); 6621 6621
+3 -4
backend/electron/tile-launcher.ts
··· 413 413 * Resolve a peek:// URL to v2 tile web preferences (preload + token). 414 414 * 415 415 * Used by the `window-open` IPC handler so that windows opened via 416 - * `api.window.open('peek://ext/<tile>/<entry.url>')` or 417 416 * `api.window.open('peek://<tile>/<entry.url>')` get the strict tile 418 417 * preload with a capability token, instead of the legacy `preload.js` 419 418 * (which has the old v1 API surface and no `api.initialize()`). ··· 440 439 if (parsed.protocol !== 'peek:') return null; 441 440 const cleanPath = parsed.pathname.replace(/^\//, ''); 442 441 if (parsed.host === 'ext') { 443 - // peek://ext/<tileId>/<entry.url> 442 + // peek://ext/<tileId>/<entry.url> (legacy compat — handled by protocol.ts shim) 444 443 const segments = cleanPath.split('/'); 445 444 if (segments.length < 2) return null; 446 445 host = segments[0]; ··· 487 486 * correspond to a declared tile entry as belonging to the parent tile's 488 487 * grant. This is the loader path for HTML assets a tile loads inside a 489 488 * `<webview>` element (e.g. `widget-demo`'s widget cards loading 490 - * `peek://ext/widget-demo/widgets/clock.html` — `widgets/clock.html` is 489 + * `peek://widget-demo/widgets/clock.html` — `widgets/clock.html` is 491 490 * not declared as its own tile entry, but logically belongs to 492 491 * `widget-demo`). 493 492 * 494 493 * Resolution rules: 495 494 * - URL must be peek:// 496 - * - Tile-id (URL host or first path segment under `peek://ext/`) must 495 + * - Tile-id (URL host, or first path segment under `peek://ext/` for legacy compat) must 497 496 * match a registered v2 manifest 498 497 * - If a tile entry's `url` matches the path, mint a token for that 499 498 * specific entry (same as `getTileWebPreferencesForUrl`)
+1 -1
features/dropzone/manifest.json
··· 16 16 "description": "Open drop zone inspector", 17 17 "action": { 18 18 "type": "window", 19 - "url": "peek://ext/dropzone/home.html", 19 + "url": "peek://dropzone/home.html", 20 20 "options": { 21 21 "role": "workspace", 22 22 "key": "dropzone-home",
+2 -2
features/editor/background.js
··· 12 12 * This extension loads lazily on first command invocation. 13 13 */ 14 14 15 - import { registerNoun, unregisterNoun } from 'peek://ext/cmd/nouns.js'; 15 + import { registerNoun, unregisterNoun } from 'peek://cmd/nouns.js'; 16 16 17 17 // Feature detection 18 18 const hasPeekAPI = typeof window.app !== 'undefined'; ··· 23 23 */ 24 24 function openEditor(params) { 25 25 if (hasPeekAPI) { 26 - let url = 'peek://ext/editor/home.html'; 26 + let url = 'peek://editor/home.html'; 27 27 if (params) { 28 28 const qs = new URLSearchParams(params).toString(); 29 29 if (qs) url += '?' + qs;
+2 -2
features/entities/background.js
··· 14 14 import { processEntities } from './entity-matcher.js'; 15 15 import { getEntities, getObservations, setEntityFeedback } from './entity-store.js'; 16 16 import { ensureNameDatabase } from './name-validator.js'; 17 - import { registerNoun, unregisterNoun } from 'peek://ext/cmd/nouns.js'; 17 + import { registerNoun, unregisterNoun } from 'peek://cmd/nouns.js'; 18 18 19 19 const api = window.app; 20 20 ··· 315 315 // ==================== UI ==================== 316 316 317 317 function openEntityBrowser() { 318 - api.window.open('peek://ext/entities/home.html', { 318 + api.window.open('peek://entities/home.html', { 319 319 role: 'workspace', 320 320 key: 'entities-browser', 321 321 width: 900,
+1 -1
features/entities/home.js
··· 14 14 import { processEntities } from './entity-matcher.js'; 15 15 import { getEntities, getObservations, setEntityFeedback } from './entity-store.js'; 16 16 import { ensureNameDatabase } from './name-validator.js'; 17 - import { registerNoun, unregisterNoun } from 'peek://ext/cmd/nouns.js'; 17 + import { registerNoun, unregisterNoun } from 'peek://cmd/nouns.js'; 18 18 19 19 const api = window.app; 20 20
+1 -1
features/example/background.js
··· 99 99 */ 100 100 function openGallery() { 101 101 if (hasPeekAPI) { 102 - api.window.open('peek://ext/example/gallery.html', { 102 + api.window.open('peek://example/gallery.html', { 103 103 key: 'example-gallery', 104 104 width: 1024, 105 105 height: 768,
+4 -4
features/features-manager/background.js
··· 7 7 * - Commands: list features 8 8 */ 9 9 10 - import { registerNoun, unregisterNoun } from 'peek://ext/cmd/nouns.js'; 10 + import { registerNoun, unregisterNoun } from 'peek://cmd/nouns.js'; 11 11 // Note: updater.js exports compareSemver/compareCapabilities utilities used by 12 12 // browse.js. background.js delegates update checking entirely to the backend 13 13 // via api.features.updateCheckAll() + api.features.updateApply(), so no ··· 33 33 */ 34 34 function openManage() { 35 35 if (hasPeekAPI) { 36 - api.window.open('peek://ext/features-manager/manage.html', { 36 + api.window.open('peek://features-manager/manage.html', { 37 37 role: 'workspace', 38 38 key: 'features-manage', 39 39 width: 900, ··· 50 50 */ 51 51 function openPublish() { 52 52 if (hasPeekAPI) { 53 - api.window.open('peek://ext/features-manager/publish.html', { 53 + api.window.open('peek://features-manager/publish.html', { 54 54 role: 'workspace', 55 55 key: 'features-publish', 56 56 width: 700, ··· 67 67 */ 68 68 function openBrowse() { 69 69 if (hasPeekAPI) { 70 - api.window.open('peek://ext/features-manager/browse.html', { 70 + api.window.open('peek://features-manager/browse.html', { 71 71 role: 'workspace', 72 72 key: 'features-browse', 73 73 width: 960,
+1 -1
features/features-manager/browse.js
··· 493 493 494 494 manageBtn.addEventListener('click', () => { 495 495 if (hasPeekAPI) { 496 - api.window.open('peek://ext/features-manager/manage.html', { 496 + api.window.open('peek://features-manager/manage.html', { 497 497 role: 'workspace', 498 498 key: 'features-manage', 499 499 width: 900,
+1 -1
features/features-manager/manage.js
··· 365 365 e.stopPropagation(); 366 366 // Open publish.html with featureId pre-selected 367 367 if (hasPeekAPI) { 368 - api.window.open(`peek://ext/features-manager/publish.html?featureId=${encodeURIComponent(feature.id)}`, { 368 + api.window.open(`peek://features-manager/publish.html?featureId=${encodeURIComponent(feature.id)}`, { 369 369 key: 'features-publish', 370 370 width: 700, 371 371 height: 600,
+4 -4
features/features-manager/manifest.json
··· 98 98 "description": "Open features manager", 99 99 "action": { 100 100 "type": "window", 101 - "url": "peek://ext/features-manager/manage.html", 101 + "url": "peek://features-manager/manage.html", 102 102 "options": { 103 103 "role": "workspace", 104 104 "key": "features-manage", ··· 113 113 "description": "Open features manager", 114 114 "action": { 115 115 "type": "window", 116 - "url": "peek://ext/features-manager/manage.html", 116 + "url": "peek://features-manager/manage.html", 117 117 "options": { 118 118 "role": "workspace", 119 119 "key": "features-manage", ··· 128 128 "description": "Browse and search features by publisher", 129 129 "action": { 130 130 "type": "window", 131 - "url": "peek://ext/features-manager/browse.html", 131 + "url": "peek://features-manager/browse.html", 132 132 "options": { 133 133 "role": "workspace", 134 134 "key": "features-browse", ··· 143 143 "description": "Publish a local feature to AT Protocol", 144 144 "action": { 145 145 "type": "window", 146 - "url": "peek://ext/features-manager/publish.html", 146 + "url": "peek://features-manager/publish.html", 147 147 "options": { 148 148 "role": "workspace", 149 149 "key": "features-publish",
+1 -1
features/features-manager/publish.js
··· 7 7 * Step 3: Confirm and publish (upload blobs + create record) 8 8 */ 9 9 10 - import { uploadBlob, xrpcPost } from 'peek://ext/lex/atproto.js'; 10 + import { uploadBlob, xrpcPost } from 'peek://lex/atproto.js'; 11 11 12 12 const api = window.app; 13 13 const hasPeekAPI = !!api;
+2 -2
features/feeds/background.js
··· 8 8 * - Commands for subscribing and viewing feeds 9 9 */ 10 10 11 - import { registerNoun, unregisterNoun } from 'peek://ext/cmd/nouns.js'; 11 + import { registerNoun, unregisterNoun } from 'peek://cmd/nouns.js'; 12 12 13 13 const api = window.app; 14 14 const POLL_INTERVAL_MS = 15 * 60 * 1000; // 15 minutes ··· 296 296 // ==================== UI ==================== 297 297 298 298 function openFeedsUI() { 299 - api.window.open('peek://ext/feeds/feeds.html', { 299 + api.window.open('peek://feeds/feeds.html', { 300 300 key: 'feeds-reader', 301 301 width: 900, 302 302 height: 700,
+1 -1
features/feeds/manifest.json
··· 54 54 "description": "Open the feed reader", 55 55 "action": { 56 56 "type": "window", 57 - "url": "peek://ext/feeds/feeds.html", 57 + "url": "peek://feeds/feeds.html", 58 58 "options": { "key": "feeds-reader", "width": 900, "height": 700, "title": "Feed Reader" } 59 59 } 60 60 },
+2 -2
features/files/background.js
··· 3 3 * 4 4 * File and format commands (CSV, save) 5 5 * 6 - * Runs in isolated extension process (peek://ext/files/background.html) 6 + * Runs in isolated extension process (peek://files/background.html) 7 7 */ 8 8 9 9 const api = window.app; ··· 483 483 data: markdownOutput, 484 484 mimeType: 'text/markdown', 485 485 title: docTitle, 486 - interactive: 'peek://ext/cmd/chain-editor.html' 486 + interactive: 'peek://cmd/chain-editor.html' 487 487 } 488 488 }; 489 489 } catch (err) {
+3 -3
features/groups/home.js
··· 19 19 } from 'peek://app/lib/tag-action-affordances.js'; 20 20 import { createSearchResultCard } from 'peek://app/lib/search-result-card.js'; 21 21 import { labels, defaults } from './config.js'; 22 - import { registerNoun, unregisterNoun } from 'peek://ext/cmd/nouns.js'; 22 + import { registerNoun, unregisterNoun } from 'peek://cmd/nouns.js'; 23 23 24 24 const api = window.app; 25 25 const debug = api.debug; ··· 361 361 362 362 console.log('[ext:groups] background', labels.name); 363 363 364 - // Extension content is served from peek://ext/groups/ 365 - const bgAddress = 'peek://ext/groups/home.html'; 364 + // Extension content is served from peek://groups/ 365 + const bgAddress = 'peek://groups/home.html'; 366 366 367 367 // In-memory settings cache (loaded from datastore during bg init) 368 368 let currentSettings = {
+2 -2
features/groups/manifest.json
··· 14 14 "height": 600, 15 15 "title": "Groups", 16 16 "role": "workspace", 17 - "key": "peek://ext/groups/home.html" 17 + "key": "peek://groups/home.html" 18 18 } 19 19 ], 20 20 "capabilities": { ··· 62 62 "description": "Open groups browser", 63 63 "action": { 64 64 "type": "window", 65 - "url": "peek://ext/groups/home.html", 65 + "url": "peek://groups/home.html", 66 66 "options": { "role": "workspace", "width": 1024, "height": 768 } 67 67 } 68 68 },
+2 -2
features/helpdocs/background.js
··· 4 4 * Spawns a fullscreen transparent overlay window with animated 5 5 * documentation hints that appear from screen edges. 6 6 * 7 - * Runs in isolated extension process (peek://ext/helpdocs/background.html) 7 + * Runs in isolated extension process (peek://helpdocs/background.html) 8 8 */ 9 9 10 10 const api = window.app; ··· 12 12 13 13 console.log('[ext:helpdocs] background init'); 14 14 15 - const OVERLAY_ADDRESS = 'peek://ext/helpdocs/overlay.html'; 15 + const OVERLAY_ADDRESS = 'peek://helpdocs/overlay.html'; 16 16 const STORAGE_KEY = 'helpdocs_enabled'; 17 17 18 18 let enabled = false;
+1 -1
features/helpdocs/manifest.json
··· 37 37 "window": { 38 38 "create": true, 39 39 "manage": true, 40 - "urls": ["peek://ext/helpdocs/overlay.html"] 40 + "urls": ["peek://helpdocs/overlay.html"] 41 41 }, 42 42 "commands": true 43 43 },
+1 -1
features/lex/background.js
··· 7 7 * - browse: open the Lexicon Studio window 8 8 */ 9 9 10 - import { registerNoun, unregisterNoun } from 'peek://ext/cmd/nouns.js'; 10 + import { registerNoun, unregisterNoun } from 'peek://cmd/nouns.js'; 11 11 import { nsidToFriendlyName } from './lexicon-ui.js'; 12 12 13 13 const api = window.app;
+1 -1
features/lex/home.js
··· 14 14 nsidToFriendlyName, 15 15 } from './lexicon-ui.js'; 16 16 17 - import { registerNoun, unregisterNoun } from 'peek://ext/cmd/nouns.js'; 17 + import { registerNoun, unregisterNoun } from 'peek://cmd/nouns.js'; 18 18 19 19 import { getDefaultPack, listTemplatePackMetadata } from './templates/index.js'; 20 20 import { escapeHtml, rkeyFromUri } from './templates/raw/utils.js';
+3 -3
features/lists/background.js
··· 8 8 * The shortcut key is configurable — manifest provides the default (Option+F), 9 9 * and if the user customizes it, the extension registers the custom key on load. 10 10 * 11 - * Runs in isolated extension process (peek://ext/lists/background.html) 11 + * Runs in isolated extension process (peek://lists/background.html) 12 12 * Uses api.settings for datastore-backed settings storage 13 13 */ 14 14 ··· 17 17 const api = window.app; 18 18 const debug = api.debug; 19 19 20 - // Extension content is served from peek://ext/lists/ 21 - const address = 'peek://ext/lists/home.html'; 20 + // Extension content is served from peek://lists/ 21 + const address = 'peek://lists/home.html'; 22 22 23 23 // In-memory settings cache (loaded from datastore on init) 24 24 let currentSettings = {
+4 -4
features/pagestream/background.js
··· 3 3 * 4 4 * Vertical, chat-like navigational interface for web history. 5 5 * 6 - * Runs in isolated extension process (peek://ext/pagestream/background.html) 6 + * Runs in isolated extension process (peek://pagestream/background.html) 7 7 * Uses api.settings for datastore-backed settings storage 8 8 */ 9 9 10 10 import { id, labels, schemas, storageKeys, defaults } from './config.js'; 11 - import { registerNoun, unregisterNoun } from 'peek://ext/cmd/nouns.js'; 11 + import { registerNoun, unregisterNoun } from 'peek://cmd/nouns.js'; 12 12 13 13 const api = window.app; 14 14 const debug = api.debug; 15 15 16 - // Extension content is served from peek://ext/pagestream/ 17 - const address = 'peek://ext/pagestream/home.html'; 16 + // Extension content is served from peek://pagestream/ 17 + const address = 'peek://pagestream/home.html'; 18 18 19 19 // In-memory settings cache (loaded from datastore on init) 20 20 let currentSettings = {
+1 -1
features/pagestream/manifest.json
··· 19 19 "url": "home.html", 20 20 "windowHints": { 21 21 "role": "workspace", 22 - "key": "peek://ext/pagestream/home.html", 22 + "key": "peek://pagestream/home.html", 23 23 "transparent": true, 24 24 "title": "Pagestream" 25 25 }
+1 -1
features/peeks/background.js
··· 3 3 * 4 4 * Quick access modal windows for web pages via keyboard shortcuts (Option+0-9) 5 5 * 6 - * Runs in isolated extension process (peek://ext/peeks/background.html) 6 + * Runs in isolated extension process (peek://peeks/background.html) 7 7 * Uses api.settings for datastore-backed settings storage 8 8 */ 9 9
+2 -2
features/scripts/background.js
··· 9 9 * - Scripts manager UI 10 10 * - Command registration 11 11 * 12 - * Runs in isolated extension process (peek://ext/scripts/background.html) 12 + * Runs in isolated extension process (peek://scripts/background.html) 13 13 */ 14 14 15 15 import { scriptExecutor } from './script-executor.js'; 16 - import { registerNoun, unregisterNoun } from 'peek://ext/cmd/nouns.js'; 16 + import { registerNoun, unregisterNoun } from 'peek://cmd/nouns.js'; 17 17 18 18 const api = window.app; 19 19
+1 -1
features/search/background.js
··· 10 10 const api = window.app; 11 11 const debug = api.debug; 12 12 13 - const baseAddress = 'peek://ext/search/home.html'; 13 + const baseAddress = 'peek://search/home.html'; 14 14 15 15 let isOpeningSearch = false; 16 16
+1 -1
features/sheets/background.js
··· 3 3 * 4 4 * Widget sheets with freeform card layouts hosting webviews. 5 5 * 6 - * Runs in isolated extension process (peek://ext/sheets/background.html) 6 + * Runs in isolated extension process (peek://sheets/background.html) 7 7 */ 8 8 9 9 import { id, labels, schemas, storageKeys, defaults } from './config.js';
+1 -1
features/slides/background.js
··· 3 3 * 4 4 * Edge-anchored slide-in panels triggered by keyboard shortcuts (Option+Arrow) 5 5 * 6 - * Runs in isolated extension process (peek://ext/slides/background.html) 6 + * Runs in isolated extension process (peek://slides/background.html) 7 7 * Uses api.settings for datastore-backed settings storage 8 8 */ 9 9
+3 -3
features/spaces/background.js
··· 4 4 * Workspace mode with auto-tagging, screen border, and session management. 5 5 * All space-specific logic lives here — the backend provides only generic primitives. 6 6 * 7 - * Runs in isolated extension process (peek://ext/spaces/background.html) 7 + * Runs in isolated extension process (peek://spaces/background.html) 8 8 */ 9 9 10 10 import { ··· 57 57 if (!displayResult.success) return; 58 58 const { bounds } = displayResult.data; 59 59 60 - const borderHtml = `peek://ext/spaces/border.html?color=${encodeURIComponent(safeColor)}&name=${encodeURIComponent(name)}`; 60 + const borderHtml = `peek://spaces/border.html?color=${encodeURIComponent(safeColor)}&name=${encodeURIComponent(name)}`; 61 61 const result = await api.window.open(borderHtml, { 62 62 role: 'overlay', 63 63 x: bounds.x, ··· 267 267 268 268 if (!restored) { 269 269 // No saved state — open spaces home UI 270 - const homeUrl = `peek://ext/spaces/home.html?spaceId=${encodeURIComponent(tag.id)}&spaceName=${encodeURIComponent(activeSpaceName)}`; 270 + const homeUrl = `peek://spaces/home.html?spaceId=${encodeURIComponent(tag.id)}&spaceName=${encodeURIComponent(activeSpaceName)}`; 271 271 await api.window.open(homeUrl, { 272 272 role: 'workspace', 273 273 width: 1024,
+1 -1
features/spaces/manifest.json
··· 99 99 "description": "Browse all spaces", 100 100 "action": { 101 101 "type": "window", 102 - "url": "peek://ext/spaces/home.html", 102 + "url": "peek://spaces/home.html", 103 103 "options": { 104 104 "width": 800, 105 105 "height": 600,
+1 -1
features/sync/background.js
··· 3 3 * 4 4 * Provides sync commands and functionality 5 5 * 6 - * Runs in isolated extension process (peek://ext/sync/background.html) 6 + * Runs in isolated extension process (peek://sync/background.html) 7 7 */ 8 8 9 9 const api = window.app;
+2 -2
features/tag-actions/background.js
··· 14 14 * Internally converts pairs to action rules so resolveAffordances() works unchanged. 15 15 */ 16 16 17 - import { registerNoun, unregisterNoun } from 'peek://ext/cmd/nouns.js'; 17 + import { registerNoun, unregisterNoun } from 'peek://cmd/nouns.js'; 18 18 19 19 const api = window.app; 20 20 const debug = api.debug; ··· 141 141 // ==================== Commands ==================== 142 142 143 143 const openTagActions = () => { 144 - api.window.open('peek://ext/tag-actions/home.html', { 144 + api.window.open('peek://tag-actions/home.html', { 145 145 role: 'workspace', 146 146 key: 'tag-actions-home', 147 147 width: 560,
+2 -2
features/tag-actions/home.js
··· 17 17 * tag-actions:create:response, :update:response, :delete:response 18 18 */ 19 19 20 - import { registerNoun, unregisterNoun } from 'peek://ext/cmd/nouns.js'; 20 + import { registerNoun, unregisterNoun } from 'peek://cmd/nouns.js'; 21 21 22 22 const api = window.app; 23 23 const debug = api.debug; ··· 171 171 // ==================== Commands ==================== 172 172 173 173 const openTagActions = () => { 174 - api.window.open('peek://ext/tag-actions/home.html', { 174 + api.window.open('peek://tag-actions/home.html', { 175 175 role: 'workspace', 176 176 key: 'tag-actions-home', 177 177 width: 560,
+2 -2
features/tags/background.js
··· 9 9 * - Commands: tag, tags, untag, tagset 10 10 */ 11 11 12 - import { registerNoun, unregisterNoun } from 'peek://ext/cmd/nouns.js'; 12 + import { registerNoun, unregisterNoun } from 'peek://cmd/nouns.js'; 13 13 14 14 // Feature detection - check if Peek API is available 15 15 const hasPeekAPI = typeof window.app !== 'undefined'; ··· 20 20 */ 21 21 function openTags() { 22 22 if (hasPeekAPI) { 23 - api.window.open('peek://ext/tags/home.html', { 23 + api.window.open('peek://tags/home.html', { 24 24 role: 'workspace', 25 25 key: 'tags-home', 26 26 width: 900,
+1 -1
features/tags/manifest.json
··· 78 78 "description": "Open the tags browser", 79 79 "action": { 80 80 "type": "window", 81 - "url": "peek://ext/tags/home.html", 81 + "url": "peek://tags/home.html", 82 82 "options": { "role": "workspace", "key": "tags-home", "width": 900, "height": 700, "title": "Tags" } 83 83 } 84 84 }
+3 -3
features/timers/background.js
··· 9 9 * Commands and shortcuts are declared in manifest.json. 10 10 * This extension loads lazily on first command invocation. 11 11 * 12 - * Runs in isolated extension process (peek://ext/timers/background.html) 12 + * Runs in isolated extension process (peek://timers/background.html) 13 13 */ 14 14 15 15 import { id, labels, schemas, storageKeys, defaults } from './config.js'; ··· 17 17 const api = window.app; 18 18 const debug = api.debug; 19 19 20 - // Extension content is served from peek://ext/timers/ 21 - const address = 'peek://ext/timers/home.html'; 20 + // Extension content is served from peek://timers/ 21 + const address = 'peek://timers/home.html'; 22 22 23 23 // In-memory timer state: id -> { item, tickState } 24 24 const activeTimers = new Map();
+1 -1
features/timers/manifest.json
··· 19 19 "url": "home.html", 20 20 "windowHints": { 21 21 "role": "workspace", 22 - "key": "peek://ext/timers/home.html", 22 + "key": "peek://timers/home.html", 23 23 "width": 700, 24 24 "height": 600, 25 25 "title": "Timers"
+2 -2
features/websearch/background.js
··· 11 11 * Commands are declared in manifest.json. 12 12 * This extension loads lazily on first command invocation. 13 13 * 14 - * Runs in isolated extension process (peek://ext/websearch/background.html) 14 + * Runs in isolated extension process (peek://websearch/background.html) 15 15 * Uses api.settings for datastore-backed settings storage 16 16 */ 17 17 ··· 21 21 const debug = api.debug; 22 22 23 23 // ===== IPC pubsub messaging ===== 24 - // Background runs at peek://ext/websearch/ while home runs at peek://websearch/ 24 + // Background runs at peek://websearch/ while home runs at peek://websearch/ 25 25 // — different origins, so BroadcastChannel cannot work. Use IPC pubsub instead. 26 26 27 27 function onChannel(topic, handler) {
+1 -1
features/widget-demo/manifest.json
··· 10 10 { 11 11 "id": "sheet", 12 12 "url": "sheet.html", 13 - "key": "peek://ext/widget-demo/sheet.html", 13 + "key": "peek://widget-demo/sheet.html", 14 14 "title": "Widget Demo", 15 15 "resident": true 16 16 }
+5 -5
features/widget-demo/sheet.js
··· 46 46 47 47 // Widget definitions with default layout (same as was in background.js) 48 48 const DEMO_WIDGETS = [ 49 - { id: 'clock', url: 'peek://ext/widget-demo/widgets/clock.html', width: 200, height: 160 }, 50 - { id: 'counter', url: 'peek://ext/widget-demo/widgets/counter.html', width: 200, height: 160 }, 51 - { id: 'note', url: 'peek://ext/widget-demo/widgets/note.html', width: 280, height: 220 }, 52 - { id: 'bookmarks', url: 'peek://ext/widget-demo/widgets/bookmarks.html', width: 280, height: 220 }, 53 - { id: 'theme', url: 'peek://ext/widget-demo/widgets/theme.html', width: 280, height: 200 }, 49 + { id: 'clock', url: 'peek://widget-demo/widgets/clock.html', width: 200, height: 160 }, 50 + { id: 'counter', url: 'peek://widget-demo/widgets/counter.html', width: 200, height: 160 }, 51 + { id: 'note', url: 'peek://widget-demo/widgets/note.html', width: 280, height: 220 }, 52 + { id: 'bookmarks', url: 'peek://widget-demo/widgets/bookmarks.html', width: 280, height: 220 }, 53 + { id: 'theme', url: 'peek://widget-demo/widgets/theme.html', width: 280, height: 200 }, 54 54 ]; 55 55 56 56 /**
+1 -1
features/windows/background.js
··· 3 3 * 4 4 * Full-screen window switcher with transparent overlay 5 5 * 6 - * Runs in isolated extension process (peek://ext/windows/background.html) 6 + * Runs in isolated extension process (peek://windows/background.html) 7 7 */ 8 8 9 9 import { id, labels, schemas, storageKeys, defaults } from './config.js';
+1 -1
features/wonderwall/background.js
··· 10 10 11 11 function openWonderwall() { 12 12 if (hasPeekAPI) { 13 - api.window.open('peek://ext/wonderwall/home.html', { 13 + api.window.open('peek://wonderwall/home.html', { 14 14 role: 'workspace', 15 15 key: 'wonderwall-home', 16 16 width: 1024,
+1 -1
features/wonderwall/manifest.json
··· 59 59 "description": "Open the connected accounts manager", 60 60 "action": { 61 61 "type": "window", 62 - "url": "peek://ext/wonderwall/home.html", 62 + "url": "peek://wonderwall/home.html", 63 63 "options": { 64 64 "role": "workspace", 65 65 "key": "wonderwall-home",
+1 -1
tests/desktop/cmd-chain.spec.ts
··· 39 39 /** Open the cmd panel and return the window page + window id. */ 40 40 async function openCmdPanel(): Promise<{ cmdWindow: Page; windowId: number | null }> { 41 41 const openResult = await sharedBgWindow.evaluate(async () => { 42 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 42 + return await (window as any).app.window.open('peek://cmd/panel.html', { 43 43 modal: true, 44 44 width: 600, 45 45 height: 50,
+2 -2
tests/desktop/cmd-state-machine.spec.ts
··· 30 30 */ 31 31 async function openCmdPanel(): Promise<{ cmdWindow: Page; windowId: number | null }> { 32 32 const openResult = await sharedBgWindow.evaluate(async () => { 33 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 33 + return await (window as any).app.window.open('peek://cmd/panel.html', { 34 34 modal: true, 35 35 width: 600, 36 36 height: 50, ··· 279 279 280 280 // Reopen 281 281 const reopenResult = await sharedBgWindow.evaluate(async () => { 282 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 282 + return await (window as any).app.window.open('peek://cmd/panel.html', { 283 283 modal: true, 284 284 width: 600, 285 285 height: 50,
+1 -1
tests/desktop/groups-context.spec.ts
··· 78 78 79 79 async function openGroupsHome(bgWindow: Page): Promise<{ groupsWindow: Page; windowId: number }> { 80 80 const groupsResult = await bgWindow.evaluate(async () => { 81 - return await (window as any).app.window.open('peek://ext/groups/home.html', { 81 + return await (window as any).app.window.open('peek://groups/home.html', { 82 82 width: 800, 83 83 height: 600 84 84 });
+20 -10
tests/desktop/hud.spec.ts
··· 100 100 await sharedBgWindow.evaluate(async () => { 101 101 (window as any).app.publish('cmd:execute:hud', {}, (window as any).app.scopes.GLOBAL); 102 102 }); 103 - const hudWindow = await sharedApp.getWindow('ext/hud/hud.html', 5000); 103 + const hudWindow = await sharedApp.getWindow('hud/hud.html', 5000); 104 104 expect(hudWindow).toBeTruthy(); 105 105 106 106 // Verify the HUD has basic DOM structure ··· 122 122 await sleep(1000); 123 123 124 124 // Find HUD window - if command is registered, the window should appear 125 - const hudWindow = await sharedApp.getWindow('ext/hud/hud.html', 5000); 125 + const hudWindow = await sharedApp.getWindow('hud/hud.html', 5000); 126 126 expect(hudWindow).toBeTruthy(); 127 127 128 128 // Clean up - toggle off ··· 137 137 await toggleHUD(); 138 138 139 139 // Find HUD window 140 - const hudWindow = await sharedApp.getWindow('ext/hud/hud.html', 5000); 140 + const hudWindow = await sharedApp.getWindow('hud/hud.html', 5000); 141 141 expect(hudWindow).toBeTruthy(); 142 142 143 143 // Verify HUD container is visible ··· 153 153 // Open HUD 154 154 await toggleHUD(); 155 155 156 - const hudWindow = await sharedApp.getWindow('ext/hud/hud.html', 5000); 156 + const hudWindow = await sharedApp.getWindow('hud/hud.html', 5000); 157 157 158 158 // Set mode to 'default' AFTER HUD is open so the subscription receives it 159 159 await sharedBgWindow.evaluate(async () => { ··· 206 206 // Open HUD 207 207 await toggleHUD(); 208 208 209 - const hudWindow = await sharedApp.getWindow('ext/hud/hud.html', 5000); 209 + const hudWindow = await sharedApp.getWindow('hud/hud.html', 5000); 210 210 211 211 // Query IZUI widget value via webview.executeJavaScript 212 212 const start = Date.now(); ··· 258 258 // Open HUD 259 259 await toggleHUD(); 260 260 261 - const hudWindow = await sharedApp.getWindow('ext/hud/hud.html', 5000); 261 + const hudWindow = await sharedApp.getWindow('hud/hud.html', 5000); 262 262 263 263 // Query stats widget content via webview.executeJavaScript 264 264 // The stats widget renders a grid of stat-label/stat-value pairs in #stats-content ··· 301 301 await toggleHUD(); 302 302 303 303 // Verify HUD window exists via Playwright 304 - const hudWindow = await sharedApp.getWindow('ext/hud/hud.html', 5000); 304 + const hudWindow = await sharedApp.getWindow('hud/hud.html', 5000); 305 305 expect(hudWindow).toBeTruthy(); 306 306 307 307 // Verify the HUD window URL contains the expected path ··· 335 335 await toggleHUD(); 336 336 337 337 // Verify HUD is open via Playwright window lookup 338 - const hudWindow = await sharedApp.getWindow('ext/hud/hud.html', 5000); 338 + const hudWindow = await sharedApp.getWindow('hud/hud.html', 5000); 339 339 expect(hudWindow).toBeTruthy(); 340 340 341 341 // Verify HUD container is visible in the window ··· 350 350 // Open HUD 351 351 await toggleHUD(); 352 352 353 - const hudWindow = await sharedApp.getWindow('ext/hud/hud.html', 5000); 353 + const hudWindow = await sharedApp.getWindow('hud/hud.html', 5000); 354 354 355 355 // Set to default mode 356 356 await sharedBgWindow.evaluate(async () => { ··· 394 394 // Open HUD 395 395 await toggleHUD(); 396 396 397 - const hudWindow = await sharedApp.getWindow('ext/hud/hud.html', 5000); 397 + const hudWindow = await sharedApp.getWindow('hud/hud.html', 5000); 398 + 399 + // Reset to default first and confirm the mode widget is subscribed and 400 + // reflecting context changes. Without this, the subsequent setMode('group') 401 + // can race the webview subscription on a freshly (re-)opened HUD and 402 + // leave the widget showing a stale 'default' until the 5s refresh interval. 403 + // Mirrors the pattern used by 'HUD displays mode information' above. 404 + await sharedBgWindow.evaluate(async () => { 405 + return await (window as any).app.context.setMode('default'); 406 + }); 407 + await waitForHudModeValue(hudWindow, 'default', 10000); 398 408 399 409 // Set to group mode with metadata 400 410 await sharedBgWindow.evaluate(async () => {
+1 -1
tests/desktop/module-health.spec.ts
··· 68 68 69 69 test('cmd panel.js module evaluates to completion', async () => { 70 70 const openResult = await sharedBgWindow.evaluate(async () => { 71 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 71 + return await (window as any).app.window.open('peek://cmd/panel.html', { 72 72 modal: true, 73 73 width: 600, 74 74 height: 50,
+1 -1
tests/desktop/pagestream.spec.ts
··· 72 72 73 73 async function openPagestream(bgWindow: Page): Promise<{ pagestreamWindow: Page; windowId: number }> { 74 74 const result = await bgWindow.evaluate(async () => { 75 - return await (window as any).app.window.open('peek://ext/pagestream/home.html', { 75 + return await (window as any).app.window.open('peek://pagestream/home.html', { 76 76 width: 500, 77 77 height: 700 78 78 });
+1 -1
tests/desktop/pubsub-repro.spec.ts
··· 6 6 * websearch-specific. If this fails, the bug is architectural to any 7 7 * feature with bg + window tiles talking via pubsub. 8 8 * 9 - * bg (peek://ext/pubsub-repro/background.html) 9 + * bg (peek://pubsub-repro/background.html) 10 10 * subscribe GLOBAL reproduce:request -> publish GLOBAL reproduce:response 11 11 * home (peek://pubsub-repro/home.html) 12 12 * subscribe GLOBAL reproduce:response -> window._reproResponse
+38 -38
tests/desktop/smoke.spec.ts
··· 96 96 97 97 // Open cmd panel via window API 98 98 const openResult = await sharedBgWindow.evaluate(async () => { 99 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 99 + return await (window as any).app.window.open('peek://cmd/panel.html', { 100 100 modal: true, 101 101 width: 600, 102 102 height: 50, ··· 161 161 162 162 // Open cmd panel 163 163 const openResult = await sharedBgWindow.evaluate(async () => { 164 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 164 + return await (window as any).app.window.open('peek://cmd/panel.html', { 165 165 modal: true, 166 166 width: 600, 167 167 height: 50, ··· 378 378 379 379 // Open groups home 380 380 const groupsResult = await bgWindow.evaluate(async () => { 381 - return await (window as any).app.window.open('peek://ext/groups/home.html', { 381 + return await (window as any).app.window.open('peek://groups/home.html', { 382 382 width: 800, 383 383 height: 600 384 384 }); ··· 503 503 504 504 // Open groups window (background.js uses escapeMode: 'navigate') 505 505 const groupsResult = await bgWindow.evaluate(async () => { 506 - return await (window as any).app.window.open('peek://ext/groups/home.html', { 506 + return await (window as any).app.window.open('peek://groups/home.html', { 507 507 width: 800, 508 508 height: 600, 509 509 escapeMode: 'navigate' ··· 592 592 593 593 // Open groups window 594 594 const groupsResult = await bgWindow.evaluate(async () => { 595 - return await (window as any).app.window.open('peek://ext/groups/home.html', { 595 + return await (window as any).app.window.open('peek://groups/home.html', { 596 596 role: 'workspace', 597 597 width: 800, 598 598 height: 600 ··· 641 641 // Open groups window with role: 'workspace' (like the real groups extension does) 642 642 // In headless/test mode, appFocused defaults to true → session is 'active' 643 643 const groupsResult = await bgWindow.evaluate(async () => { 644 - return await (window as any).app.window.open('peek://ext/groups/home.html', { 644 + return await (window as any).app.window.open('peek://groups/home.html', { 645 645 role: 'workspace', 646 646 width: 400, 647 647 height: 300, ··· 692 692 693 693 // First open a workspace window (like groups) to establish an active session 694 694 const workspaceResult = await bgWindow.evaluate(async () => { 695 - return await (window as any).app.window.open('peek://ext/groups/home.html', { 695 + return await (window as any).app.window.open('peek://groups/home.html', { 696 696 role: 'workspace', 697 697 width: 400, 698 698 height: 300, ··· 712 712 // Now open a child-content window (simulates opening a web page from groups) 713 713 // Using the workspace window as the opener gives it child-content role 714 714 const contentResult = await workspaceWindow.evaluate(async () => { 715 - return await (window as any).app.window.open('peek://ext/search/home.html', { 715 + return await (window as any).app.window.open('peek://search/home.html', { 716 716 role: 'child-content', 717 717 width: 400, 718 718 height: 300, ··· 759 759 // Open a window with navigate escape mode but NO escape handler registered 760 760 // This simulates what happens when a window hasn't finished loading its IZUI 761 761 const result = await bgWindow.evaluate(async () => { 762 - return await (window as any).app.window.open('peek://ext/groups/home.html', { 762 + return await (window as any).app.window.open('peek://groups/home.html', { 763 763 width: 400, 764 764 height: 300, 765 765 escapeMode: 'navigate' ··· 812 812 813 813 // Open cmd panel 814 814 const openResult = await sharedBgWindow.evaluate(async () => { 815 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 815 + return await (window as any).app.window.open('peek://cmd/panel.html', { 816 816 modal: true, 817 817 width: 600, 818 818 height: 50, ··· 859 859 860 860 // Open cmd panel 861 861 const openResult = await sharedBgWindow.evaluate(async () => { 862 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 862 + return await (window as any).app.window.open('peek://cmd/panel.html', { 863 863 modal: true, 864 864 width: 600, 865 865 height: 50, ··· 905 905 906 906 // Open cmd panel 907 907 const openResult = await sharedBgWindow.evaluate(async () => { 908 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 908 + return await (window as any).app.window.open('peek://cmd/panel.html', { 909 909 modal: true, 910 910 width: 600, 911 911 height: 50, ··· 951 951 952 952 // Open cmd panel 953 953 const openResult = await sharedBgWindow.evaluate(async () => { 954 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 954 + return await (window as any).app.window.open('peek://cmd/panel.html', { 955 955 modal: true, 956 956 width: 600, 957 957 height: 50, ··· 1003 1003 1004 1004 // Open cmd panel 1005 1005 const openResult = await sharedBgWindow.evaluate(async () => { 1006 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 1006 + return await (window as any).app.window.open('peek://cmd/panel.html', { 1007 1007 modal: true, 1008 1008 width: 600, 1009 1009 height: 50, ··· 2595 2595 2596 2596 // Open groups home 2597 2597 const groupsResult = await bgWindow.evaluate(async () => { 2598 - return await (window as any).app.window.open('peek://ext/groups/home.html', { 2598 + return await (window as any).app.window.open('peek://groups/home.html', { 2599 2599 width: 800, 2600 2600 height: 600 2601 2601 }); ··· 2653 2653 2654 2654 // Open groups home 2655 2655 const groupsResult = await bgWindow.evaluate(async () => { 2656 - return await (window as any).app.window.open('peek://ext/groups/home.html', { 2656 + return await (window as any).app.window.open('peek://groups/home.html', { 2657 2657 width: 800, 2658 2658 height: 600, 2659 2659 key: 'groups-untagged-test' ··· 2818 2818 test('cmd panel loads with chain state initialized', async () => { 2819 2819 // Open cmd panel to verify it loads correctly with chain support 2820 2820 const openResult = await bgWindow.evaluate(async () => { 2821 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 2821 + return await (window as any).app.window.open('peek://cmd/panel.html', { 2822 2822 modal: true, 2823 2823 width: 600, 2824 2824 height: 300, ··· 2854 2854 test('MIME type matching works correctly', async () => { 2855 2855 // Test MIME matching logic in panel context 2856 2856 const openResult = await bgWindow.evaluate(async () => { 2857 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 2857 + return await (window as any).app.window.open('peek://cmd/panel.html', { 2858 2858 modal: true, 2859 2859 width: 600, 2860 2860 height: 50, ··· 2892 2892 test('cmd panel input works correctly', async () => { 2893 2893 // Open cmd panel 2894 2894 const openResult = await bgWindow.evaluate(async () => { 2895 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 2895 + return await (window as any).app.window.open('peek://cmd/panel.html', { 2896 2896 modal: true, 2897 2897 width: 600, 2898 2898 height: 400, ··· 2927 2927 test('panel has chain indicator, preview, and execution state elements', async () => { 2928 2928 // Open cmd panel 2929 2929 const openResult = await bgWindow.evaluate(async () => { 2930 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 2930 + return await (window as any).app.window.open('peek://cmd/panel.html', { 2931 2931 modal: true, 2932 2932 width: 600, 2933 2933 height: 300, ··· 2981 2981 test('list urls command produces array output and enters output selection mode', async () => { 2982 2982 // Open cmd panel 2983 2983 const openResult = await bgWindow.evaluate(async () => { 2984 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 2984 + return await (window as any).app.window.open('peek://cmd/panel.html', { 2985 2985 modal: true, 2986 2986 width: 600, 2987 2987 height: 400, ··· 3049 3049 }); 3050 3050 3051 3051 const openResult = await bgWindow.evaluate(async () => { 3052 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 3052 + return await (window as any).app.window.open('peek://cmd/panel.html', { 3053 3053 modal: true, width: 600, height: 400, frame: false, transparent: true, alwaysOnTop: true, center: true, 3054 3054 }); 3055 3055 }); ··· 3097 3097 }); 3098 3098 3099 3099 const openResult = await bgWindow.evaluate(async () => { 3100 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 3100 + return await (window as any).app.window.open('peek://cmd/panel.html', { 3101 3101 modal: true, width: 600, height: 400, frame: false, transparent: true, alwaysOnTop: true, center: true, 3102 3102 }); 3103 3103 }); ··· 3174 3174 }); 3175 3175 3176 3176 const openResult = await bgWindow.evaluate(async () => { 3177 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 3177 + return await (window as any).app.window.open('peek://cmd/panel.html', { 3178 3178 modal: true, width: 600, height: 400, frame: false, transparent: true, alwaysOnTop: true, center: true, 3179 3179 }); 3180 3180 }); ··· 3227 3227 }); 3228 3228 3229 3229 const openResult = await bgWindow.evaluate(async () => { 3230 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 3230 + return await (window as any).app.window.open('peek://cmd/panel.html', { 3231 3231 modal: true, width: 600, height: 400, frame: false, transparent: true, alwaysOnTop: true, center: true, 3232 3232 }); 3233 3233 }); ··· 3298 3298 3299 3299 // Open cmd panel 3300 3300 const openResult = await bgWindow.evaluate(async () => { 3301 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 3301 + return await (window as any).app.window.open('peek://cmd/panel.html', { 3302 3302 modal: true, 3303 3303 width: 600, 3304 3304 height: 400, ··· 3357 3357 const seedId = seedResult.data.id; 3358 3358 3359 3359 const openResult = await bgWindow.evaluate(async () => { 3360 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 3360 + return await (window as any).app.window.open('peek://cmd/panel.html', { 3361 3361 modal: true, width: 600, height: 400, frame: false, transparent: true, alwaysOnTop: true, center: true, 3362 3362 }); 3363 3363 }); ··· 3425 3425 3426 3426 // Open cmd panel 3427 3427 const openResult = await bgWindow.evaluate(async () => { 3428 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 3428 + return await (window as any).app.window.open('peek://cmd/panel.html', { 3429 3429 modal: true, 3430 3430 width: 600, 3431 3431 height: 400, ··· 3487 3487 test('new note without content signals editor open', async () => { 3488 3488 // Open cmd panel 3489 3489 const openResult = await bgWindow.evaluate(async () => { 3490 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 3490 + return await (window as any).app.window.open('peek://cmd/panel.html', { 3491 3491 modal: true, 3492 3492 width: 600, 3493 3493 height: 400, ··· 3562 3562 3563 3563 // Open cmd panel 3564 3564 const openResult = await bgWindow.evaluate(async () => { 3565 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 3565 + return await (window as any).app.window.open('peek://cmd/panel.html', { 3566 3566 modal: true, 3567 3567 width: 600, 3568 3568 height: 400, ··· 3642 3642 3643 3643 // Open cmd panel 3644 3644 const openResult = await bgWindow.evaluate(async () => { 3645 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 3645 + return await (window as any).app.window.open('peek://cmd/panel.html', { 3646 3646 modal: true, 3647 3647 width: 600, 3648 3648 height: 400, ··· 3715 3715 test('Tab in command mode completes name, does not execute', async () => { 3716 3716 // Open cmd panel 3717 3717 const openResult = await bgWindow.evaluate(async () => { 3718 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 3718 + return await (window as any).app.window.open('peek://cmd/panel.html', { 3719 3719 modal: true, 3720 3720 width: 600, 3721 3721 height: 400, ··· 4744 4744 // Step 1: Open a content window (groups home) from the background window. 4745 4745 // Since background.html is an internal URL, parentWindowId should be null. 4746 4746 const groupsResult = await bgWindow.evaluate(async () => { 4747 - return await (window as any).app.window.open('peek://ext/groups/home.html', { 4747 + return await (window as any).app.window.open('peek://groups/home.html', { 4748 4748 width: 600, 4749 4749 height: 400 4750 4750 }); ··· 4862 4862 4863 4863 // Open a content window 4864 4864 const result = await bgWindow.evaluate(async () => { 4865 - return await (window as any).app.window.open('peek://ext/groups/home.html', { 4865 + return await (window as any).app.window.open('peek://groups/home.html', { 4866 4866 width: 400, 4867 4867 height: 300, 4868 4868 escapeMode: 'navigate' ··· 4956 4956 4957 4957 // Open a groups window with navigate escape mode 4958 4958 const result = await bgWindow.evaluate(async () => { 4959 - return await (window as any).app.window.open('peek://ext/groups/home.html', { 4959 + return await (window as any).app.window.open('peek://groups/home.html', { 4960 4960 width: 400, 4961 4961 height: 300, 4962 4962 escapeMode: 'navigate' ··· 5206 5206 // Execute script - test executor directly 5207 5207 const executeResult = await sharedBgWindow.evaluate(async (scriptId) => { 5208 5208 const api = (window as any).app; 5209 - const { scriptExecutor } = await import('peek://ext/scripts/script-executor.js'); 5209 + const { scriptExecutor } = await import('peek://scripts/script-executor.js'); 5210 5210 5211 5211 // Get the script from datastore 5212 5212 const settingsTable = await api.datastore.getTable('feature_settings'); ··· 5251 5251 // Test pattern matching directly 5252 5252 const patternTests = await sharedBgWindow.evaluate(async () => { 5253 5253 // Import the script executor module 5254 - const { ScriptExecutor } = await import('peek://ext/scripts/script-executor.js'); 5254 + const { ScriptExecutor } = await import('peek://scripts/script-executor.js'); 5255 5255 const executor = new ScriptExecutor(); 5256 5256 5257 5257 return { ··· 5307 5307 // Execute with short timeout - test executor directly 5308 5308 const executeResult = await sharedBgWindow.evaluate(async (scriptId) => { 5309 5309 const api = (window as any).app; 5310 - const { scriptExecutor } = await import('peek://ext/scripts/script-executor.js'); 5310 + const { scriptExecutor } = await import('peek://scripts/script-executor.js'); 5311 5311 5312 5312 // Get the script from datastore 5313 5313 const settingsTable = await api.datastore.getTable('feature_settings');
+1 -1
tests/desktop/websearch-cmd.spec.ts
··· 40 40 // Helper to open the cmd panel and return the panel window + open result 41 41 async function openCmdPanel(bgWindow: Page, app: DesktopApp) { 42 42 const openResult = await bgWindow.evaluate(async () => { 43 - return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 43 + return await (window as any).app.window.open('peek://cmd/panel.html', { 44 44 modal: true, 45 45 width: 600, 46 46 height: 50,
+5 -3
tests/fixtures/desktop-app.ts
··· 272 272 273 273 getExtensionWindows: () => { 274 274 return electronApp.windows().filter(w => 275 - w.url().includes('peek://ext/') && w.url().includes('background.html') 275 + w.url().startsWith('peek://') && !w.url().startsWith('peek://app/') && w.url().includes('background.html') 276 276 ); 277 277 }, 278 278 ··· 527 527 let targetUrl = url; 528 528 if (url.startsWith('peek://app/')) { 529 529 targetUrl = `http://localhost:${MOCK_PORT}/${url.replace('peek://app/', '')}`; 530 - } else if (url.startsWith('peek://ext/')) { 531 - targetUrl = `http://localhost:${MOCK_PORT}/ext/${url.replace('peek://ext/', '')}`; 530 + } else if (url.startsWith('peek://') && !url.startsWith('peek://app/')) { 531 + // peek://<id>/... (v2 canonical) — map to mock server 532 + const withoutScheme = url.replace('peek://', ''); 533 + targetUrl = `http://localhost:${MOCK_PORT}/ext/${withoutScheme}`; 532 534 } else if (url.startsWith('http://') || url.startsWith('https://')) { 533 535 // External URLs - open as-is 534 536 targetUrl = url;
+1 -1
tests/helpers/window-utils.ts
··· 33 33 */ 34 34 export function getExtensionWindows(getWindows: () => Page[]): Page[] { 35 35 const windows = getWindows(); 36 - return windows.filter(w => w.url().includes('peek://ext/') && w.url().includes('background.html')); 36 + return windows.filter(w => w.url().startsWith('peek://') && !w.url().startsWith('peek://app/') && w.url().includes('background.html')); 37 37 } 38 38 39 39 /**
+1 -1
tests/unit/groups-quick-wins.test.js
··· 463 463 }); 464 464 465 465 it('returns false for peek:// URLs', () => { 466 - assert.equal(isWebUrl('peek://ext/groups/home.html'), false); 466 + assert.equal(isWebUrl('peek://groups/home.html'), false); 467 467 }); 468 468 469 469 it('returns false for empty string', () => {