experiments in a post-browser web
10
fork

Configure Feed

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

test(module-health): detect silent module-halt regressions from api drift

Adds end-of-module sentinels to app/page/page.js and app/cmd/panel.js
(both modules that start with top-level await api.initialize()).
If any intermediate top-level statement throws — e.g. an api.X.Y
method was renamed in tile-preload and the caller here wasn't
updated — the module halts as an unhandled rejection, DOM stays
partially initialized, and subscribers quietly fail to register.

module-health.spec.ts opens each core renderer and asserts the
sentinel flips within 5s of window open, catching the drift before
it surfaces as a confusing widget-doesn't-update bug elsewhere.

Context in feedback_top_level_await_silent_halt.md.

+111
+6
app/cmd/panel.js
··· 2682 2682 } 2683 2683 return machine.handleEscape(); 2684 2684 }); 2685 + 2686 + // Module-end sentinel. If any top-level statement above throws (e.g. an api.* 2687 + // surface was renamed or removed), module evaluation halts and this flag is 2688 + // never set. A regression test asserts this flips within a short timeout. 2689 + // See tests/desktop/module-health.spec.ts + feedback_top_level_await_silent_halt.md. 2690 + window.__cmdPanelModuleReady = true;
+6
app/page/page.js
··· 3824 3824 }); 3825 3825 3826 3826 DEBUG && console.log('[page] Minimal page host initialized for:', targetUrl); 3827 + 3828 + // Module-end sentinel. If any top-level statement above throws (e.g. an api.* 3829 + // surface was renamed or removed), module evaluation halts and this flag is 3830 + // never set. A regression test asserts this flips within a short timeout. 3831 + // See tests/desktop/module-health.spec.ts + feedback_top_level_await_silent_halt.md. 3832 + window.__pageModuleReady = true;
+99
tests/desktop/module-health.spec.ts
··· 1 + /** 2 + * Module Health Tests — detects silent module-halt regressions 3 + * 4 + * Core renderer modules like `app/page/page.js` and `app/cmd/panel.js` start 5 + * with `await api.initialize()` at the top level. If any later top-level 6 + * statement throws synchronously (e.g. an `api.*` surface was renamed or 7 + * removed from tile-preload), the module evaluation halts as an unhandled 8 + * promise rejection. DOM is usually already rendered so the window looks 9 + * fine, but no subscribers register and no event handlers wire up — the 10 + * widget quietly stops reacting. 11 + * 12 + * The modules now set a sentinel (`window.__pageModuleReady` / 13 + * `window.__cmdPanelModuleReady`) as their last top-level statement. If 14 + * the sentinel doesn't flip within a few seconds of window open, an earlier 15 + * top-level statement threw. These tests catch that class of drift before 16 + * it manifests as confusing widget-doesn't-update bugs in other tests. 17 + * 18 + * See feedback_top_level_await_silent_halt.md for the canonical incident. 19 + */ 20 + 21 + import { test, expect, DesktopApp, getSharedApp, closeSharedApp } from '../fixtures/desktop-app'; 22 + import { Page } from '@playwright/test'; 23 + import { waitForExtensionsReady } from '../helpers/window-utils'; 24 + 25 + let sharedApp: DesktopApp; 26 + let sharedBgWindow: Page; 27 + 28 + test.beforeAll(async () => { 29 + sharedApp = await getSharedApp(); 30 + sharedBgWindow = await sharedApp.getBackgroundWindow(); 31 + await waitForExtensionsReady(sharedBgWindow); 32 + }); 33 + 34 + test.afterAll(async () => { 35 + await closeSharedApp(); 36 + }); 37 + 38 + test.describe('Module Health @desktop', () => { 39 + test('page.js module evaluates to completion (no silent halt from api drift)', async () => { 40 + const openResult = await sharedBgWindow.evaluate(async () => { 41 + return await (window as any).app.window.open('https://example.com', { 42 + width: 800, 43 + height: 600, 44 + key: 'module-health-page', 45 + }); 46 + }); 47 + expect(openResult.success).toBe(true); 48 + const windowId = openResult.id; 49 + 50 + const pageWindow = await sharedApp.getWindow('page/index.html', 15000); 51 + expect(pageWindow).toBeTruthy(); 52 + 53 + // Sentinel is the last top-level statement in page.js. If any earlier 54 + // top-level `api.*.x` call throws (e.g. renamed method like getWindowId 55 + // → getId, or removed namespace like api.ipc), the module halts silently 56 + // and this flag never flips. 57 + await pageWindow.waitForFunction( 58 + () => (window as any).__pageModuleReady === true, 59 + undefined, 60 + { timeout: 5000 } 61 + ); 62 + 63 + await sharedBgWindow.evaluate( 64 + async (id: number) => (window as any).app.window.close(id), 65 + windowId 66 + ); 67 + }); 68 + 69 + test('cmd panel.js module evaluates to completion', async () => { 70 + const openResult = await sharedBgWindow.evaluate(async () => { 71 + return await (window as any).app.window.open('peek://ext/cmd/panel.html', { 72 + modal: true, 73 + width: 600, 74 + height: 50, 75 + frame: false, 76 + transparent: true, 77 + alwaysOnTop: true, 78 + center: true, 79 + keepLive: true, 80 + }); 81 + }); 82 + expect(openResult.success).toBe(true); 83 + const windowId = openResult.id; 84 + 85 + const cmdWindow = await sharedApp.getWindow('cmd/panel.html', 5000); 86 + expect(cmdWindow).toBeTruthy(); 87 + 88 + await cmdWindow.waitForFunction( 89 + () => (window as any).__cmdPanelModuleReady === true, 90 + undefined, 91 + { timeout: 5000 } 92 + ); 93 + 94 + await sharedBgWindow.evaluate( 95 + async (id: number) => (window as any).app.window.close(id), 96 + windowId 97 + ); 98 + }); 99 + });