experiments in a post-browser web
10
fork

Configure Feed

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

fix(cmd+tags): kill tag-widget + quit/restart full-suite flakes

+95 -37
+9
app/cmd/background.js
··· 339 339 api.pubsub.publish('cmd:query-commands-response', { commands }, api.scopes.GLOBAL); 340 340 }, api.scopes.GLOBAL); 341 341 342 + // Request every booted tile re-emit its cmd:register events. Tiles that 343 + // registered their commands during their own init may have done so before 344 + // this extension's `cmd:register` subscriber was live on main-side — the 345 + // broadcaster iterates tileWindows at publish time, so any tile registered 346 + // in tileWindows after the publish never saw those events. Each tile- 347 + // preload caches its `cmd:register` payloads and replays them in response 348 + // to this request. See smoke.spec.ts:1502 flake analysis. 349 + api.pubsub.publish('cmd:request-registers', {}, api.scopes.GLOBAL); 350 + 342 351 log('ext:cmd', 'Command registry initialized'); 343 352 }; 344 353
+63 -34
backend/electron/tile-preload.cts
··· 174 174 } 175 175 176 176 function subscribeImpl(topic: string, callback: (data: unknown) => void, _scope?: number) { 177 - // Register the IPC listener immediately (synchronous) so messages 178 - // received after validation completes are not dropped. 179 - // The upstream tile:pubsub:subscribe is sent only after the token 180 - // validates. If validation fails we tear down the listener. 177 + // Attach the IPC listener AND send the upstream subscribe synchronously 178 + // in the same tick. Deferring the upstream send behind validationPromise 179 + // created a race: publishers (e.g. tag:item-added from command execution) 180 + // that fire between "listener attached locally" and "main process has 181 + // registered this tile as a subscriber" are dropped. Main-side already 182 + // validates the token in the tile:pubsub:subscribe handler (tile-ipc.ts) 183 + // — an invalid token is silently ignored — so the client-side gate was 184 + // redundant. See commit history for the tag-widget flake that motivated 185 + // this change. 181 186 let unsubscribed = false; 182 187 const handler = (_event: unknown, msg: unknown) => { 183 188 callback(msg); 184 189 }; 185 190 ipcRenderer.on(`pubsub:${topic}`, handler); 186 - 187 - validationPromise.then((valid) => { 188 - if (!valid) { 189 - console.warn('[tile-preload] subscribe: token validation failed, not subscribing upstream for topic:', topic); 190 - ipcRenderer.removeListener(`pubsub:${topic}`, handler); 191 - return; 192 - } 193 - if (unsubscribed) return; 194 - ipcRenderer.send('tile:pubsub:subscribe', { 195 - token: tileToken, 196 - source: sourceAddress, 197 - topic, 198 - }); 191 + ipcRenderer.send('tile:pubsub:subscribe', { 192 + token: tileToken, 193 + source: sourceAddress, 194 + topic, 199 195 }); 200 196 201 197 return () => { 198 + if (unsubscribed) return; 202 199 unsubscribed = true; 203 200 ipcRenderer.removeListener(`pubsub:${topic}`, handler); 204 - validationPromise.then((valid) => { 205 - if (!valid) return; 206 - ipcRenderer.send('tile:pubsub:unsubscribe', { 207 - token: tileToken, 208 - source: sourceAddress, 209 - topic, 210 - }); 201 + ipcRenderer.send('tile:pubsub:unsubscribe', { 202 + token: tileToken, 203 + source: sourceAddress, 204 + topic, 211 205 }); 212 206 }; 213 207 } ··· 249 243 // re-registers the same command (swap handler in-place instead of adding a 250 244 // second `pubsub:cmd:execute:{name}` listener). 251 245 const registeredCommands = new Map<string, (params: unknown) => unknown>(); 246 + // Cache of cmd:register publish payloads so we can re-emit them if a later- 247 + // booting cmd extension asks via `cmd:request-registers`. Without this, a 248 + // tile that registers its commands during module init can have its events 249 + // dropped if the cmd extension's subscriber wasn't yet live on main-side — 250 + // the broadcaster iterates `tileWindows` unconditionally but only at the 251 + // moment of publish. See smoke.spec.ts:1502 flake analysis. 252 + const registeredPayloads = new Map<string, Record<string, unknown>>(); 253 + let cmdRequestRegistersListenerInstalled = false; 254 + 255 + function ensureCmdRequestRegistersListener(): void { 256 + if (cmdRequestRegistersListenerInstalled) return; 257 + cmdRequestRegistersListenerInstalled = true; 258 + // Synchronous upstream subscribe (same pattern as subscribeImpl post-fix). 259 + // On request, re-emit every cached cmd:register payload so a freshly- 260 + // booted cmd extension gets this tile's registry. 261 + ipcRenderer.send('tile:pubsub:subscribe', { 262 + token: tileToken, 263 + source: sourceAddress, 264 + topic: 'cmd:request-registers', 265 + }); 266 + ipcRenderer.on('pubsub:cmd:request-registers', () => { 267 + for (const payload of registeredPayloads.values()) { 268 + ipcRenderer.send('tile:pubsub:publish', { 269 + token: tileToken, 270 + source: sourceAddress, 271 + scope: 3, // GLOBAL 272 + topic: 'cmd:register', 273 + data: payload, 274 + }); 275 + } 276 + }); 277 + } 252 278 253 279 function hasCommandsCapability(): boolean { 254 280 const cc = grantedCapabilities?.commands; ··· 328 354 // object-form (name+metadata) registrations — string-form entries still 329 355 // need to appear in the registry so features-manager-style callers are 330 356 // visible to the cmd panel, even if they carry no accepts/produces. 357 + const registerPayload = { 358 + name, 359 + description, 360 + source: sourceAddress, 361 + scope: scope || 'global', 362 + modes: modes || [], 363 + accepts: accepts || [], 364 + produces: produces || [], 365 + params: params || [], 366 + }; 367 + registeredPayloads.set(name, registerPayload); 368 + ensureCmdRequestRegistersListener(); 331 369 ipcRenderer.send('tile:pubsub:publish', { 332 370 token: tileToken, 333 371 source: sourceAddress, 334 372 scope: 3, // GLOBAL 335 373 topic: 'cmd:register', 336 - data: { 337 - name, 338 - description, 339 - source: sourceAddress, 340 - scope: scope || 'global', 341 - modes: modes || [], 342 - accepts: accepts || [], 343 - produces: produces || [], 344 - params: params || [], 345 - }, 374 + data: registerPayload, 346 375 }); 347 376 348 377 // Listen for command execution via pubsub relay (`pubsub:cmd:execute:{name}`).
+23 -3
features/tags/background.js
··· 48 48 // ============================================================================ 49 49 50 50 /** 51 - * Get the most recently focused non-internal window 51 + * Get the most recently focused non-internal window. 52 + * 53 + * Primary: the tracker (`getFocusedVisibleWindowId()`) — survives cmd panel 54 + * focus changes and matches OS-level user intent. 55 + * 56 + * Fallback (tracker is null, common under headless where focus events may 57 + * not reliably fire): prefer a canvas page window 58 + * (`peek://app/page/index.html?url=...`) because that's the content surface 59 + * tagging typically targets, and take the newest by id. This keeps the 60 + * heuristic deterministic: if the user just opened a page, that's the one 61 + * we tag. Falls back to the last listed window only if no page window 62 + * exists at all. 52 63 */ 64 + const isPageCanvasUrl = (url) => 65 + typeof url === 'string' && url.startsWith('peek://app/page/index.html'); 66 + 53 67 const getActiveWindow = async () => { 54 68 const result = await api.window.list({ includeInternal: false }); 55 69 if (!result.success || !result.windows.length) { ··· 61 75 const focused = result.windows.find(w => w.id === focusedId); 62 76 if (focused) return focused; 63 77 } 64 - // Fall back to first window if no focused window found 65 - return result.windows[0]; 78 + // Fallback: newest page-canvas window, deterministic and user-intent 79 + // aligned. See smoke.spec.ts:2015 flake analysis. 80 + const pageWindows = result.windows.filter(w => isPageCanvasUrl(w.url)); 81 + if (pageWindows.length) { 82 + return pageWindows.reduce((a, b) => (a.id > b.id ? a : b)); 83 + } 84 + // No page windows at all — fall back to the newest window by id. 85 + return result.windows.reduce((a, b) => (a.id > b.id ? a : b)); 66 86 }; 67 87 68 88 /**