From 2d3c54490faa088e509b29b91d15937f8b573ad7 Mon Sep 17 00:00:00 2001 From: Lotus CI Date: Thu, 22 Oct 2026 20:36:47 -0400 Subject: [PATCH 1/4] ci(windows-smoke): a matrix: link while hidden or minimised (#8) Hide the window (then minimise it), open matrix:r/lotus-smoke-*:example.org through Windows (protocol handler -> second cinny.exe -> single-instance), and require the window to come back and the URL to reach the page as lotus-deeplink. Refs #8, #19 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5 --- scripts/windows-smoke.mjs | 46 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/scripts/windows-smoke.mjs b/scripts/windows-smoke.mjs index 1808d1b..3beadc2 100644 --- a/scripts/windows-smoke.mjs +++ b/scripts/windows-smoke.mjs @@ -3,7 +3,8 @@ // The app is started with WebView2's DevTools port open // (WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--remote-debugging-port=9222) and this // script drives its real page over CDP with playwright-core. No login: every -// check runs on the login screen or through the app's own Tauri commands. +// check runs on the login screen or through the app's own Tauri commands +// (including a `matrix:` deep link while hidden/minimised, #8). // // node scripts/windows-smoke.mjs // @@ -184,6 +185,49 @@ if (!page.url().startsWith(FOREIGN)) { } foreign.close(); await page.goto(APP).catch(() => undefined); +await until(async () => /Login|Homeserver/.test(await page.locator('body').innerText()), 60_000); + +// 8. A `matrix:` link opened from Windows while the window is hidden to the +// tray, then while minimised, must bring the window back AND reach the page +// as `lotus-deeplink` (#8). It goes the real route: Windows' protocol handler +// starts a second cinny.exe, and single-instance forwards the URL to this one. +// (Turning the URL into a room navigation is the web app's job, signed in.) +const win = (cmd) => + page.evaluate( + ([c]) => window.__TAURI_INTERNALS__.invoke(`plugin:window|${c}`, { label: 'main' }), + [cmd], + ); +await page.evaluate(() => { + window.__deeplinks = []; + window.addEventListener('lotus-deeplink', (e) => window.__deeplinks.push(e.detail)); +}); +for (const [state, put] of [ + ['hidden to the tray', 'hide'], + ['minimised', 'minimize'], +]) { + const name = `deep link while ${state} reveals the window and reaches the page`; + const url = `matrix:r/lotus-smoke-${put}:example.org`; + try { + await win(put); + await new Promise((r) => setTimeout(r, 1500)); + const away = put === 'hide' ? !(await win('is_visible')) : await win('is_minimized'); + execSync(`cmd /c start "" "${url}"`); + const got = await until( + () => page.evaluate((u) => window.__deeplinks.includes(u), url), + 20_000, + ); + const visible = await win('is_visible'); + const minimised = await win('is_minimized'); + const ok = away && got && visible && !minimised; + record( + name, + ok ? 'pass' : 'fail', + `${state} first: ${away}; event: ${!!got}; visible after: ${visible}; minimised after: ${minimised}`, + ); + } catch (e) { + record(name, 'fail', String(e).slice(0, 200)); + } +} await page.screenshot({ path: `${OUT}/02-end.png` }).catch(() => undefined); writeFileSync(`${OUT}/results.json`, JSON.stringify(results, null, 2)); From 0a887c29c4b0f042cbf850465a044310d29d2660 Mon Sep 17 00:00:00 2001 From: Lotus CI Date: Thu, 22 Oct 2026 20:37:44 -0400 Subject: [PATCH 2/4] ci(windows-smoke): system-wide voice hotkeys round trip (#2) Register PTT on F9 through set_global_hotkeys, press and release F9 via keybd_event, expect pressed and released lotus-global-hotkey events, and none after clearing the bindings. Refs #2, #19 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5 --- scripts/windows-smoke.mjs | 60 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/scripts/windows-smoke.mjs b/scripts/windows-smoke.mjs index 3beadc2..d94c0a6 100644 --- a/scripts/windows-smoke.mjs +++ b/scripts/windows-smoke.mjs @@ -228,6 +228,66 @@ for (const [state, put] of [ record(name, 'fail', String(e).slice(0, 200)); } } +// 9. System-wide voice hotkeys (#2): register PTT on F9, press and release F9 +// through Windows (keybd_event, as a game in front would see it), expect a +// pressed and a released `lotus-global-hotkey`; after clearing the bindings +// the same key must produce nothing (the poll stopped). +const pressScript = `${OUT}/press-f9.ps1`; +writeFileSync( + pressScript, + [ + 'Add-Type -Name K -Namespace W -MemberDefinition @"', + '[DllImport("user32.dll")] public static extern void keybd_event(byte v, byte s, uint f, System.UIntPtr e);', + '"@', + '[W.K]::keybd_event(0x78, 0, 0, [System.UIntPtr]::Zero)', + 'Start-Sleep -Milliseconds 300', + '[W.K]::keybd_event(0x78, 0, 2, [System.UIntPtr]::Zero)', + ].join('\r\n'), +); +const pressF9 = () => execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${pressScript}"`); +try { + const supported = await page.evaluate(() => + window.__TAURI_INTERNALS__.invoke('global_hotkeys_supported'), + ); + if (!supported) { + record('global hotkeys reach the page', 'fail', 'global_hotkeys_supported = false on Windows'); + } else { + await page.evaluate(async () => { + window.__hotkeys = []; + window.addEventListener('lotus-global-hotkey', (e) => window.__hotkeys.push(e.detail)); + await window.__TAURI_INTERNALS__.invoke('set_global_hotkeys', { + bindings: [{ id: 'ptt', code: 'F9' }], + }); + }); + await new Promise((r) => setTimeout(r, 500)); + pressF9(); + const seen = await until( + () => + page.evaluate(() => { + const h = window.__hotkeys.map((e) => `${e.id}:${e.state}`); + return h.includes('ptt:pressed') && h.includes('ptt:released') ? h : undefined; + }), + 5_000, + 250, + ); + await page.evaluate(() => + window.__TAURI_INTERNALS__.invoke('set_global_hotkeys', { bindings: [] }), + ); + const before = await page.evaluate(() => window.__hotkeys.length); + pressF9(); + await new Promise((r) => setTimeout(r, 1500)); + const after = await page.evaluate(() => window.__hotkeys.length); + record( + 'global hotkeys reach the page, and stop when cleared', + seen && after === before ? 'pass' : 'fail', + `events: ${JSON.stringify(seen ?? (await page.evaluate(() => window.__hotkeys)))}; after clearing: ${after - before} more`, + ); + } +} catch (e) { + const msg = String(e).slice(0, 200); + record('global hotkeys reach the page', /not found/i.test(msg) ? 'n/a' : 'fail', msg); +} + await page.screenshot({ path: `${OUT}/02-end.png` }).catch(() => undefined); writeFileSync(`${OUT}/results.json`, JSON.stringify(results, null, 2)); From 043a44665f06fd254826da80846831983d8cdf8c Mon Sep 17 00:00:00 2001 From: Lotus CI Date: Thu, 22 Oct 2026 21:07:55 -0400 Subject: [PATCH 3/4] ci(windows-smoke): control check that a hidden window can be shown (#8) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5 --- scripts/windows-smoke.mjs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/scripts/windows-smoke.mjs b/scripts/windows-smoke.mjs index d94c0a6..552eb45 100644 --- a/scripts/windows-smoke.mjs +++ b/scripts/windows-smoke.mjs @@ -228,6 +228,19 @@ for (const [state, put] of [ record(name, 'fail', String(e).slice(0, 200)); } } +// Control for #8: can this machine show a hidden window at all? (A runner +// without an interactive desktop can't, and that isn't the app's fault.) +try { + await win('hide'); + await new Promise((r) => setTimeout(r, 1000)); + await win('show'); + await new Promise((r) => setTimeout(r, 1000)); + const shown = await win('is_visible'); + record('control: a hidden window can be shown again', shown ? 'pass' : 'info', `visible after show(): ${shown}`); +} catch (e) { + record('control: a hidden window can be shown again', 'info', String(e).slice(0, 160)); +} + // 9. System-wide voice hotkeys (#2): register PTT on F9, press and release F9 // through Windows (keybd_event, as a game in front would see it), expect a // pressed and a released `lotus-global-hotkey`; after clearing the bindings From f10a69ad19e1bc853529c6f18e00119420e7d186 Mon Sep 17 00:00:00 2001 From: Lotus CI Date: Thu, 22 Oct 2026 21:15:10 -0400 Subject: [PATCH 4/4] ci(windows-smoke): visibility and hotkey checks need an interactive desktop The windows runner can't show a hidden window or inject keys (no interactive desktop: even a direct show() leaves it hidden). Check that first; the deep link must still reach the page and un-minimise, but window visibility and the global-hotkey round trip are n/a on such a runner. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5 --- scripts/windows-smoke.mjs | 41 +++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/scripts/windows-smoke.mjs b/scripts/windows-smoke.mjs index 552eb45..c071157 100644 --- a/scripts/windows-smoke.mjs +++ b/scripts/windows-smoke.mjs @@ -201,6 +201,25 @@ await page.evaluate(() => { window.__deeplinks = []; window.addEventListener('lotus-deeplink', (e) => window.__deeplinks.push(e.detail)); }); +// Can this machine show a hidden window at all? A runner without an +// interactive desktop (e.g. act_runner as a Windows service, session 0) can't, +// and can't inject keys either. Then visibility and hotkeys are reported n/a +// instead of failing for a reason that isn't the app's. +let interactive = false; +try { + await win('hide'); + await new Promise((r) => setTimeout(r, 1000)); + await win('show'); + await new Promise((r) => setTimeout(r, 1000)); + interactive = await win('is_visible'); +} catch { + interactive = false; +} +record( + 'interactive desktop (windows can be shown, keys injected)', + interactive ? 'pass' : 'info', + interactive ? 'yes' : 'no: visibility and hotkey checks are n/a on this runner', +); for (const [state, put] of [ ['hidden to the tray', 'hide'], ['minimised', 'minimize'], @@ -218,29 +237,19 @@ for (const [state, put] of [ ); const visible = await win('is_visible'); const minimised = await win('is_minimized'); - const ok = away && got && visible && !minimised; + const delivered = away && got && !minimised; + const ok = delivered && (visible || !interactive); record( name, ok ? 'pass' : 'fail', - `${state} first: ${away}; event: ${!!got}; visible after: ${visible}; minimised after: ${minimised}`, + `${state} first: ${away}; event: ${!!got}; minimised after: ${minimised}; visible after: ${visible}${ + interactive ? '' : ' (not checked: no interactive desktop)' + }`, ); } catch (e) { record(name, 'fail', String(e).slice(0, 200)); } } -// Control for #8: can this machine show a hidden window at all? (A runner -// without an interactive desktop can't, and that isn't the app's fault.) -try { - await win('hide'); - await new Promise((r) => setTimeout(r, 1000)); - await win('show'); - await new Promise((r) => setTimeout(r, 1000)); - const shown = await win('is_visible'); - record('control: a hidden window can be shown again', shown ? 'pass' : 'info', `visible after show(): ${shown}`); -} catch (e) { - record('control: a hidden window can be shown again', 'info', String(e).slice(0, 160)); -} - // 9. System-wide voice hotkeys (#2): register PTT on F9, press and release F9 // through Windows (keybd_event, as a game in front would see it), expect a // pressed and a released `lotus-global-hotkey`; after clearing the bindings @@ -264,6 +273,8 @@ try { ); if (!supported) { record('global hotkeys reach the page', 'fail', 'global_hotkeys_supported = false on Windows'); + } else if (!interactive) { + record('global hotkeys reach the page', 'n/a', 'no interactive desktop: injected keys never reach GetAsyncKeyState'); } else { await page.evaluate(async () => { window.__hotkeys = [];