experiments in a post-browser web
10
fork

Configure Feed

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

feat(pubsub): Phase 3 — collapse to one command-result path

+35 -40
+22
backend/electron/tile-ipc.test.ts
··· 17 17 18 18 import { describe, it, beforeEach, afterEach } from 'node:test'; 19 19 import * as assert from 'node:assert'; 20 + import { readFileSync } from 'node:fs'; 21 + import { fileURLToPath } from 'node:url'; 22 + import { dirname, join } from 'node:path'; 20 23 21 24 import { 22 25 generateToken, ··· 142 145 assert.strictEqual(drift.events[0].reason, 'sender-mismatch'); 143 146 }); 144 147 }); 148 + 149 + describe('Phase 3: tile:command:result is gone', () => { 150 + // At runtime under ELECTRON_RUN_AS_NODE=1 the test file lives at 151 + // dist/backend/electron/tile-ipc.test.js and the compiled handler 152 + // module is its sibling tile-ipc.js. We assert the compiled output 153 + // contains no reference to the legacy side-channel — neither the 154 + // preload send site nor the main-process handler registration can 155 + // leave a stub behind without this failing. 156 + const hereDir = dirname(fileURLToPath(import.meta.url)); 157 + const compiledTileIpc = join(hereDir, 'tile-ipc.js'); 158 + 159 + it('compiled tile-ipc.js does not register a tile:command:result handler', () => { 160 + const src = readFileSync(compiledTileIpc, 'utf8'); 161 + assert.ok( 162 + !src.includes("'tile:command:result'") && !src.includes('"tile:command:result"'), 163 + 'tile-ipc.js still contains a tile:command:result reference — Phase 3 removed that channel' 164 + ); 165 + }); 166 + });
-19
backend/electron/tile-ipc.ts
··· 627 627 ); 628 628 }); 629 629 630 - ipcMain.on('tile:command:result', (event, args: { 631 - token: string; 632 - name: string; 633 - result?: unknown; 634 - error?: string; 635 - }) => { 636 - const grant = verifyTokenSender(event, args.token, 'tile:command:result'); 637 - if (!grant) return; 638 - 639 - // Publish result so cmd panel can resolve 640 - const topic = `${args.name}:result`; 641 - publish( 642 - `peek://${grant.tileId}/background`, 643 - scopes.GLOBAL, 644 - topic, 645 - args.error ? { error: args.error } : { result: args.result } 646 - ); 647 - }); 648 - 649 630 // ── Shortcuts (strict) ──────────────────────────────────────────── 650 631 // 651 632 // Strict tile:shortcuts:* handlers that enforce the `shortcuts`
-8
backend/electron/tile-preload.cts
··· 406 406 data: error ? { error } : result, 407 407 }); 408 408 } 409 - 410 - // Also notify tile:command:result (useful for tile-level bookkeeping) 411 - ipcRenderer.send('tile:command:result', { 412 - token: tileToken, 413 - name, 414 - result, 415 - error: error || undefined, 416 - }); 417 409 }); 418 410 }, 419 411
+12 -12
docs/pubsub-state-machine.md
··· 268 268 every tile to explicitly declare every possible UUID-suffixed topic in 269 269 its manifest. 270 270 271 - **Legacy side-channel to remove**: tile-preload currently also sends a 272 - parallel `ipcRenderer.send('tile:command:result', ...)` frame 273 - (tile-preload.cts:411) that hits an unrestricted main-process publish 274 - handler (tile-ipc.ts `ipcMain.on('tile:command:result', ...)`). This 275 - duplicate path predates the allowlist carve-out and bypasses the gate 276 - entirely. It is redundant with the pubsub publish above and must be 277 - deleted — one auth path, one code path, one place to reason about who 278 - can emit a command result. 271 + **Legacy side-channel removed (Phase 3, 2026-04-23)**: tile-preload 272 + previously also sent a parallel `ipcRenderer.send('tile:command:result', 273 + ...)` frame that hit an unrestricted main-process publish handler 274 + (`ipcMain.on('tile:command:result', ...)` in tile-ipc.ts). That 275 + duplicate path predated the allowlist carve-out and bypassed the gate 276 + entirely. Both sites are gone — one auth path, one code path, one 277 + place to reason about who can emit a command result. 279 278 280 279 ``` 281 280 Dispatcher Owning tile ··· 300 299 resolve proxy promise 301 300 ``` 302 301 303 - **Concrete removals**: 302 + **Concrete removals (done Phase 3)**: 304 303 - `ipcRenderer.send('tile:command:result', ...)` call in 305 - `api.commands.register` handler (tile-preload.cts:411). Keep the 306 - `tile:pubsub:publish` call at line 401. 307 - - `ipcMain.on('tile:command:result', ...)` handler in tile-ipc.ts. 304 + `api.commands.register` handler — removed from tile-preload.cts. 305 + The `tile:pubsub:publish` to `msg.resultTopic` is now the sole path. 306 + - `ipcMain.on('tile:command:result', ...)` handler in tile-ipc.ts — 307 + removed. 308 308 309 309 ## Scope semantics — delete 310 310
+1 -1
docs/tasks.md
··· 14 14 15 15 - [ ] **Root-cause: only hello-world commands visible in cmd panel.** After Phases E/F, only hello-world's commands (`hello`, "hello world trace") appear in the cmd panel. Every other lazy tile's commands — tag, untag, kagi, google, ddg, lists, peeks, slides, and dozens more — are missing. Agent hypothesis: Phase E's `registerLazyTile()` publishes `cmd:register-batch` at boot before the cmd panel's core/background subscription lands. Do NOT band-aid; fix this only after the state machine is in place so the fix comes from the right abstraction. Manual test case: `yarn start`, open cmd panel, type a few characters, should see many matches — currently only sees hello-world's. 16 16 17 - - [ ] **Consolidate command-result paths into one.** Two paths exist today: (A) `tile:command:result` IPC from tile-preload's `api.commands.register` wrapper → main-process unrestricted `publish()`; (B) handlers that manually `tile:pubsub:publish` to `cmd:execute:X:result:...`. This duality is why the Phase E bug hid for so long — pre-Phase-E tests only exercised path (A), which bypassed the capability allowlist; path (B) was silently rejected until the 2026-04-23 fix (`tolwnovr f32063db`). Collapse to one (probably A). Pre-req: state machine task above. 17 + - [x] **Consolidate command-result paths into one.** DONE 2026-04-23 as Phase 3 of pubsub-state-machine.md. The `tile:command:result` side-channel (preload send site + main-process unrestricted `publish()` handler) is gone; all command results now flow through the capability-gated `tile:pubsub:publish` of `cmd:execute:{name}:result:{uuid}` via the infra carve-out (commit `f32063db`). 18 18 19 19 - [ ] **UI-level tests for cmd-panel repeat invocation.** The 2026-04-23 repro (`tests/desktop/cmd-execute-twice.spec.ts`) fires on the pubsub bus directly and passes 3/3. Manual testing caught a 3rd-invocation stall that the bus test misses — the difference must be panel UI state (`state.executionTimeout`, `urlSearchTimer`, subscription lifetime in `ensureCmdRequestRegistersListener`) leaking across repeats. Add Playwright tests that drive Cmd+K → type → Enter 3-5x in a row and assert panel closes + no spinner. 20 20