experiments in a post-browser web
10
fork

Configure Feed

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

fix(cmd): editor:open lazy-load interceptor + cmd palette blink on hotkey

Bug 1: Edit command tab-completion closes panel without opening editor.
The editor extension is lazy-loaded and may not be running when editor:open
is published. Add pubsub interceptors in main.ts that detect when editor:open
or editor:add fires before the editor extension is loaded, load the extension
on demand, then re-publish the event so the editor receives it.

Bug 2: Cmd palette blinks (opens twice) on global hotkey invocation.
The did-resign-active handler was hiding ALL alwaysOnTop windows including
the cmd panel. When invoked via global hotkey while the app is not active,
the panel would show, then get hidden by did-resign-active, then re-shown
by did-become-active — causing a visible blink. Fix: only hide non-focusable
alwaysOnTop windows (like HUD), skip focusable ones (like cmd panel) that
have their own blur handler for dismiss behavior.

+44 -2
+44 -2
backend/electron/main.ts
··· 204 204 (app as any).on('did-resign-active', () => { 205 205 getIzuiCoordinator().setAppFocused(false); 206 206 publish(getSystemAddress(), scopes.GLOBAL, 'app:focus-changed', { focused: false }); 207 - // Hide alwaysOnTop overlay windows (e.g. HUD) when app loses focus 207 + // Hide non-focusable alwaysOnTop overlay windows (e.g. HUD) when app loses focus. 208 + // Skip focusable alwaysOnTop windows (e.g. cmd panel) — they have their own 209 + // blur handler for hide/show and would blink (show-hide-show) if hidden here. 208 210 for (const win of BrowserWindow.getAllWindows()) { 209 - if (!win.isDestroyed() && win.isAlwaysOnTop() && win.isVisible()) { 211 + if (!win.isDestroyed() && win.isAlwaysOnTop() && win.isVisible() && !win.isFocusable()) { 210 212 win.hide(); 211 213 (win as any).__hudHidden = true; 212 214 } ··· 683 685 } 684 686 685 687 /** 688 + * Register pubsub interceptors for lazy extension events. 689 + * Some events (like editor:open) are published by the cmd panel before 690 + * the target extension is loaded. These interceptors ensure the extension 691 + * is loaded first, then re-publish the event so the extension receives it. 692 + */ 693 + export function registerLazyEventInterceptors(): void { 694 + const editorEvents = ['editor:open', 'editor:add']; 695 + const interceptorSource = 'peek://ext/editor/lazy-interceptor'; 696 + 697 + for (const topic of editorEvents) { 698 + subscribe(interceptorSource, scopes.GLOBAL, topic, async (msg: unknown) => { 699 + // If editor is already loaded, nothing to do — the extension's own handler 700 + // will fire from the same publish (pubsub iterates all subscribers). 701 + if (lazyExtensionLoaded.has('editor')) return; 702 + 703 + DEBUG && console.log(`[ext:lazy-interceptor] ${topic} fired but editor not loaded, loading...`); 704 + 705 + // Load the editor extension 706 + await loadLazyExtension('editor'); 707 + 708 + // Unsubscribe this interceptor so it doesn't catch future events 709 + // (the extension's own handler is now registered) 710 + unsubscribe(interceptorSource, topic); 711 + 712 + // Re-publish the event so the extension's newly-registered handler picks it up 713 + DEBUG && console.log(`[ext:lazy-interceptor] Editor loaded, re-publishing ${topic}`); 714 + publish(interceptorSource, scopes.GLOBAL, topic, msg); 715 + }); 716 + } 717 + 718 + DEBUG && console.log('[ext:lazy-interceptor] Registered editor event interceptors'); 719 + } 720 + 721 + /** 686 722 * Required CSS variables that every theme must define. 687 723 * Used by validateThemeCSS() to verify theme completeness. 688 724 */ ··· 1166 1202 // Register lazy extension commands from manifests (after cmd is ready) 1167 1203 registerLazyExtensionCommands(); 1168 1204 1205 + // Register event interceptors for lazy extensions (e.g. editor:open) 1206 + registerLazyEventInterceptors(); 1207 + 1169 1208 // Load remaining consolidated extensions — skip declarative-only and lazy ones 1170 1209 const otherConsolidatedIds = consolidatedIds.filter(id => 1171 1210 id !== 'cmd' && !isDeclarativeOnly(id) && EAGER_EXTENSION_IDS.has(id) ··· 1203 1242 1204 1243 // Register lazy extension commands 1205 1244 registerLazyExtensionCommands(); 1245 + 1246 + // Register event interceptors for lazy extensions (e.g. editor:open) 1247 + registerLazyEventInterceptors(); 1206 1248 } 1207 1249 1208 1250 // Load external built-in extensions (like 'example') as separate windows