@tekyzinc/gsd-t 4.4.10 → 4.4.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  All notable changes to GSD-T are documented here. Updated with each release.
4
4
 
5
+ ## [4.4.11] - 2026-06-10 (Playwright No-Focus-Steal — patch)
6
+
7
+ ### Added — E2E tests must never steal keyboard focus (all projects)
8
+
9
+ Headed Playwright runs on macOS ACTIVATE the browser app and yank keyboard focus from the terminal — regardless of window position, so the old "off-screen window" mitigation stopped screen takeover but not focus theft (reported live from binvoice: extension specs commandeered the cursor on every spec). Root cause of the prior "new-headless can't load MV3 extensions" verdict was a binary mix-up: `headless: true` alone launches the chromium_headless_shell (OLD headless, silently no extensions) and a raw `--headless=new` arg fights it. The fix: `channel: 'chromium'` + `headless: true` selects the FULL build's new headless — extensions load, service workers register, zero windows (binvoice: 21/21 in 9.8s).
10
+
11
+ - `templates/CLAUDE-global.md`: new "Playwright No-Focus-Steal Invariant" section — headless default everywhere, one launch helper owns visibility, `HEADED=1` opt-in only, the channel pitfall documented; mirrored to the live `~/.claude/CLAUDE.md`.
12
+ - `templates/test-helpers/launch-extension.ts`: NEW generalized MV3-extension launch helper (newheadless default / offscreen fallback / HEADED=1), proven in binvoice (commit 87e3233 there).
13
+
14
+ No code-path changes; propagates via `gsd-t update-all` (CLAUDE-global section sync).
15
+
5
16
  ## [4.4.10] - 2026-06-09 (M85 Model-Tier Policy + Fable 5 — minor)
6
17
 
7
18
  ### Added — single source of truth for model-tier assignments + the Fable 5 tier
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekyzinc/gsd-t",
3
- "version": "4.4.10",
3
+ "version": "4.4.11",
4
4
  "description": "GSD-T: Contract-Driven Development for Claude Code — 54 slash commands with headless-by-default workflow spawning, unattended supervisor relay with event stream, graph-powered code analysis, real-time agent dashboard, task telemetry, doc-ripple enforcement, backlog management, impact analysis, test sync, milestone archival, and PRD generation",
5
5
  "author": "Tekyz, Inc.",
6
6
  "license": "MIT",
@@ -210,6 +210,29 @@ Operator overrides:
210
210
 
211
211
  You no longer need to run a check yourself before testing commands — the Workflow stage runs the readiness gate before E2E.
212
212
 
213
+ ### Playwright No-Focus-Steal Invariant (MANDATORY — all projects)
214
+
215
+ **E2E tests must NEVER steal keyboard focus or pop visible windows during a normal run.** The developer keeps typing in their terminal while tests run. This is non-negotiable on every project.
216
+
217
+ ```
218
+ RULES:
219
+ ├── Headless is the DEFAULT everywhere. A visible browser is opt-in only (HEADED=1).
220
+ ├── Specs/configs MUST NOT hardcode `headless: false` — visibility is decided in ONE
221
+ │ launch helper (or the config), controlled by env var, never per-spec.
222
+ ├── MV3 Chrome extensions are NOT an exception: Chrome's NEW headless loads
223
+ │ extensions. The load-bearing invocation is `channel: 'chromium'` + `headless: true`
224
+ │ (Playwright ≥1.49). PITFALL: `headless: true` alone launches the
225
+ │ chromium_headless_shell (OLD headless — silently cannot load extensions), and
226
+ │ passing `--headless=new` as a raw arg fights that binary. Template:
227
+ │ `templates/test-helpers/launch-extension.ts`.
228
+ └── Off-screen windows (`--window-position=-2400,-2400`) are NOT a focus fix on
229
+ macOS — a headed launch ACTIVATES the app and steals the cursor regardless of
230
+ window position. Off-screen is a screen-takeover mitigation only; use it solely
231
+ as a fallback if new-headless regresses extension support.
232
+ ```
233
+
234
+ Origin: binvoice 2026-06-10 — extension specs forced `headless: false`, commandeering the cursor on every spec; the prior "off-screen" mitigation stopped the window takeover but not the focus theft. Fixed permanently by the channel-selected new headless (21/21 in 9.8s, zero windows).
235
+
213
236
  ### Playwright Cleanup
214
237
 
215
238
  After Playwright tests finish (pass or fail), **kill any app/server processes that were started for the tests**. Playwright often launches a dev server (via `webServer` config or manually). These processes must not be left running:
@@ -0,0 +1,81 @@
1
+ /**
2
+ * launch-extension.ts — GSD-T template: launch Chromium with an unpacked MV3
3
+ * extension WITHOUT stealing focus or taking over the developer's screen.
4
+ *
5
+ * Copy into e2e/helpers/ and set EXTENSION_PATH for your project.
6
+ * Origin: binvoice 2026-06-10 (see GSD-T CHANGELOG 4.4.11).
7
+ *
8
+ * THE INVARIANT: E2E tests must NEVER steal keyboard focus. On macOS a headed
9
+ * Chromium launch ACTIVATES the app — yanking the cursor out of the terminal —
10
+ * regardless of window position. Off-screen windows do NOT fix this; only a
11
+ * truly headless launch does.
12
+ *
13
+ * Modes:
14
+ * 1. NEW HEADLESS (DEFAULT) — full-Chromium new headless via
15
+ * `channel: 'chromium'` + `headless: true`. Loads MV3 extensions and
16
+ * registers service workers. No window, no activation, no focus theft.
17
+ * PITFALL this template exists to encode: `headless: true` ALONE launches
18
+ * Playwright's chromium_headless_shell (OLD headless — silently cannot
19
+ * load extensions), and passing `--headless=new` as a raw arg fights that
20
+ * binary instead of selecting the right one. `channel: 'chromium'` is the
21
+ * load-bearing line.
22
+ * 2. OFF-SCREEN HEADED — fallback if a future Chrome regresses new-headless
23
+ * extension support. Prevents screen takeover but NOT macOS focus theft.
24
+ * 3. HEADED — visible window for watching a run. Opt-in only, never default.
25
+ *
26
+ * Env:
27
+ * HEADED=1 → mode 3 (visible window).
28
+ * E2E_MODE=offscreen → mode 2 (fallback).
29
+ * (unset) → mode 1 (new headless — the default).
30
+ */
31
+
32
+ import { chromium, type BrowserContext } from '@playwright/test';
33
+ import { resolve } from 'path';
34
+
35
+ // Adjust for your project: path to the built unpacked extension.
36
+ export const EXTENSION_PATH = resolve(__dirname, '../../dist');
37
+
38
+ type Mode = 'newheadless' | 'offscreen' | 'headed';
39
+
40
+ function resolveMode(): Mode {
41
+ if (process.env['HEADED'] === '1') return 'headed';
42
+ if (process.env['E2E_MODE'] === 'offscreen') return 'offscreen';
43
+ if (process.env['E2E_MODE'] === 'headed') return 'headed';
44
+ return 'newheadless';
45
+ }
46
+
47
+ function baseArgs(): string[] {
48
+ return [
49
+ `--disable-extensions-except=${EXTENSION_PATH}`,
50
+ `--load-extension=${EXTENSION_PATH}`,
51
+ '--no-sandbox',
52
+ ];
53
+ }
54
+
55
+ export async function launchExtensionContext(): Promise<BrowserContext> {
56
+ const mode = resolveMode();
57
+ const args = baseArgs();
58
+
59
+ if (mode === 'newheadless') {
60
+ return chromium.launchPersistentContext('', {
61
+ channel: 'chromium', // load-bearing: full build → new headless → extensions work
62
+ headless: true,
63
+ args,
64
+ });
65
+ }
66
+
67
+ if (mode === 'offscreen') {
68
+ return chromium.launchPersistentContext('', {
69
+ headless: false,
70
+ args: [
71
+ ...args,
72
+ '--window-position=-2400,-2400',
73
+ '--window-size=400,300',
74
+ '--no-first-run',
75
+ '--no-default-browser-check',
76
+ ],
77
+ });
78
+ }
79
+
80
+ return chromium.launchPersistentContext('', { headless: false, args });
81
+ }