···339339 api.pubsub.publish('cmd:query-commands-response', { commands }, api.scopes.GLOBAL);
340340 }, api.scopes.GLOBAL);
341341342342+ // Request every booted tile re-emit its cmd:register events. Tiles that
343343+ // registered their commands during their own init may have done so before
344344+ // this extension's `cmd:register` subscriber was live on main-side — the
345345+ // broadcaster iterates tileWindows at publish time, so any tile registered
346346+ // in tileWindows after the publish never saw those events. Each tile-
347347+ // preload caches its `cmd:register` payloads and replays them in response
348348+ // to this request. See smoke.spec.ts:1502 flake analysis.
349349+ api.pubsub.publish('cmd:request-registers', {}, api.scopes.GLOBAL);
350350+342351 log('ext:cmd', 'Command registry initialized');
343352};
344353
+63-34
backend/electron/tile-preload.cts
···174174 }
175175176176 function subscribeImpl(topic: string, callback: (data: unknown) => void, _scope?: number) {
177177- // Register the IPC listener immediately (synchronous) so messages
178178- // received after validation completes are not dropped.
179179- // The upstream tile:pubsub:subscribe is sent only after the token
180180- // validates. If validation fails we tear down the listener.
177177+ // Attach the IPC listener AND send the upstream subscribe synchronously
178178+ // in the same tick. Deferring the upstream send behind validationPromise
179179+ // created a race: publishers (e.g. tag:item-added from command execution)
180180+ // that fire between "listener attached locally" and "main process has
181181+ // registered this tile as a subscriber" are dropped. Main-side already
182182+ // validates the token in the tile:pubsub:subscribe handler (tile-ipc.ts)
183183+ // — an invalid token is silently ignored — so the client-side gate was
184184+ // redundant. See commit history for the tag-widget flake that motivated
185185+ // this change.
181186 let unsubscribed = false;
182187 const handler = (_event: unknown, msg: unknown) => {
183188 callback(msg);
184189 };
185190 ipcRenderer.on(`pubsub:${topic}`, handler);
186186-187187- validationPromise.then((valid) => {
188188- if (!valid) {
189189- console.warn('[tile-preload] subscribe: token validation failed, not subscribing upstream for topic:', topic);
190190- ipcRenderer.removeListener(`pubsub:${topic}`, handler);
191191- return;
192192- }
193193- if (unsubscribed) return;
194194- ipcRenderer.send('tile:pubsub:subscribe', {
195195- token: tileToken,
196196- source: sourceAddress,
197197- topic,
198198- });
191191+ ipcRenderer.send('tile:pubsub:subscribe', {
192192+ token: tileToken,
193193+ source: sourceAddress,
194194+ topic,
199195 });
200196201197 return () => {
198198+ if (unsubscribed) return;
202199 unsubscribed = true;
203200 ipcRenderer.removeListener(`pubsub:${topic}`, handler);
204204- validationPromise.then((valid) => {
205205- if (!valid) return;
206206- ipcRenderer.send('tile:pubsub:unsubscribe', {
207207- token: tileToken,
208208- source: sourceAddress,
209209- topic,
210210- });
201201+ ipcRenderer.send('tile:pubsub:unsubscribe', {
202202+ token: tileToken,
203203+ source: sourceAddress,
204204+ topic,
211205 });
212206 };
213207 }
···249243 // re-registers the same command (swap handler in-place instead of adding a
250244 // second `pubsub:cmd:execute:{name}` listener).
251245 const registeredCommands = new Map<string, (params: unknown) => unknown>();
246246+ // Cache of cmd:register publish payloads so we can re-emit them if a later-
247247+ // booting cmd extension asks via `cmd:request-registers`. Without this, a
248248+ // tile that registers its commands during module init can have its events
249249+ // dropped if the cmd extension's subscriber wasn't yet live on main-side —
250250+ // the broadcaster iterates `tileWindows` unconditionally but only at the
251251+ // moment of publish. See smoke.spec.ts:1502 flake analysis.
252252+ const registeredPayloads = new Map<string, Record<string, unknown>>();
253253+ let cmdRequestRegistersListenerInstalled = false;
254254+255255+ function ensureCmdRequestRegistersListener(): void {
256256+ if (cmdRequestRegistersListenerInstalled) return;
257257+ cmdRequestRegistersListenerInstalled = true;
258258+ // Synchronous upstream subscribe (same pattern as subscribeImpl post-fix).
259259+ // On request, re-emit every cached cmd:register payload so a freshly-
260260+ // booted cmd extension gets this tile's registry.
261261+ ipcRenderer.send('tile:pubsub:subscribe', {
262262+ token: tileToken,
263263+ source: sourceAddress,
264264+ topic: 'cmd:request-registers',
265265+ });
266266+ ipcRenderer.on('pubsub:cmd:request-registers', () => {
267267+ for (const payload of registeredPayloads.values()) {
268268+ ipcRenderer.send('tile:pubsub:publish', {
269269+ token: tileToken,
270270+ source: sourceAddress,
271271+ scope: 3, // GLOBAL
272272+ topic: 'cmd:register',
273273+ data: payload,
274274+ });
275275+ }
276276+ });
277277+ }
252278253279 function hasCommandsCapability(): boolean {
254280 const cc = grantedCapabilities?.commands;
···328354 // object-form (name+metadata) registrations — string-form entries still
329355 // need to appear in the registry so features-manager-style callers are
330356 // visible to the cmd panel, even if they carry no accepts/produces.
357357+ const registerPayload = {
358358+ name,
359359+ description,
360360+ source: sourceAddress,
361361+ scope: scope || 'global',
362362+ modes: modes || [],
363363+ accepts: accepts || [],
364364+ produces: produces || [],
365365+ params: params || [],
366366+ };
367367+ registeredPayloads.set(name, registerPayload);
368368+ ensureCmdRequestRegistersListener();
331369 ipcRenderer.send('tile:pubsub:publish', {
332370 token: tileToken,
333371 source: sourceAddress,
334372 scope: 3, // GLOBAL
335373 topic: 'cmd:register',
336336- data: {
337337- name,
338338- description,
339339- source: sourceAddress,
340340- scope: scope || 'global',
341341- modes: modes || [],
342342- accepts: accepts || [],
343343- produces: produces || [],
344344- params: params || [],
345345- },
374374+ data: registerPayload,
346375 });
347376348377 // Listen for command execution via pubsub relay (`pubsub:cmd:execute:{name}`).
+23-3
features/tags/background.js
···4848// ============================================================================
49495050/**
5151- * Get the most recently focused non-internal window
5151+ * Get the most recently focused non-internal window.
5252+ *
5353+ * Primary: the tracker (`getFocusedVisibleWindowId()`) — survives cmd panel
5454+ * focus changes and matches OS-level user intent.
5555+ *
5656+ * Fallback (tracker is null, common under headless where focus events may
5757+ * not reliably fire): prefer a canvas page window
5858+ * (`peek://app/page/index.html?url=...`) because that's the content surface
5959+ * tagging typically targets, and take the newest by id. This keeps the
6060+ * heuristic deterministic: if the user just opened a page, that's the one
6161+ * we tag. Falls back to the last listed window only if no page window
6262+ * exists at all.
5263 */
6464+const isPageCanvasUrl = (url) =>
6565+ typeof url === 'string' && url.startsWith('peek://app/page/index.html');
6666+5367const getActiveWindow = async () => {
5468 const result = await api.window.list({ includeInternal: false });
5569 if (!result.success || !result.windows.length) {
···6175 const focused = result.windows.find(w => w.id === focusedId);
6276 if (focused) return focused;
6377 }
6464- // Fall back to first window if no focused window found
6565- return result.windows[0];
7878+ // Fallback: newest page-canvas window, deterministic and user-intent
7979+ // aligned. See smoke.spec.ts:2015 flake analysis.
8080+ const pageWindows = result.windows.filter(w => isPageCanvasUrl(w.url));
8181+ if (pageWindows.length) {
8282+ return pageWindows.reduce((a, b) => (a.id > b.id ? a : b));
8383+ }
8484+ // No page windows at all — fall back to the newest window by id.
8585+ return result.windows.reduce((a, b) => (a.id > b.id ? a : b));
6686};
67876888/**