Files
msd-core/docs/how-to/enable-ui-interaction-capture.md
Jakub Zych a9a7a328e6 refactor: hard-fork GSD -> MSD (Make Software Done)
Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD
across contents and paths, upstream package/repo coordinates -> @golem15/msd-core
and golem15com/msd-core. Deep links into upstream history, sibling upstream
packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is.

Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line,
package/plugin identity, regenerated lockfile, install-tree fixtures, derived
registries and benchmark baseline; migration checksum baseline re-locked
(MSD keeps its own install state, so no install had applied the old sums);
sort-order and regex-escaped expectations in tests adjusted.
2026-10-06 01:47:40 +02:00

5.5 KiB

How to enable UI interaction capture

Let /msd-ui-review's auditor capture what a page looks like after an interaction — a hover state, a focus ring, an open menu, a filled form's validation state — instead of only the first paint, without configuring an MCP server or widening the agent's tool surface.

Default-off, and deliberately so. The static capture path is unchanged in every configuration; this key adds captures on top of it. You opt in per project with one key. The gap it closes is #4223: the auditor is chartered to audit interaction, and npx playwright screenshot has no interaction verb.

What you need:

  • An installed Chrome or Chromium. The driver launches the system browser (Puppeteer channel: 'chrome'); it does not download one. Discovery tries google-chrome, google-chrome-stable, chromium, chromium-browser and chrome on PATH, then the standard macOS and Windows install paths. CHROME_BIN=/path/to/chrome overrides it.
  • npx able to fetch chrome-devtools-mcp (the package that ships the chrome-devtools CLI). It is resolved at the documented floor ^1.9.0 (the release that added --workspace, which confines the driver's file writes to the capture directory); CHROME_DEVTOOLS_MCP_VERSION overrides it.
  • Nothing else. Every driver call runs under a ceiling — CHROME_DEVTOOLS_START_TIMEOUT (default 180 s, for the npx fetch plus the Chrome launch) and CHROME_DEVTOOLS_STEP_TIMEOUT (default 60 s, per capture verb). At the ceiling the client's whole process group is killed (TERM, then KILL two seconds later) and the step is counted as failed, so a cold npm cache or a Chrome that never comes up costs a failed step rather than an open-ended wait. The one exception is a watchdog whose own clock (sleep) cannot launch: it stands down instead of killing a healthy call, and that call is then unbounded, as every call was before the ceilings existed.
  • A dev server the static capture already reaches — interaction capture runs against the same URL and skips itself when the static block reached nothing.

Step 1 — Turn the key on

msd-tools query config-set workflow.ui_interaction_capture true

Verify it took:

msd-tools query config-get workflow.ui_interaction_capture
# → true

/msd-ui-review reads the key and passes interaction_capture: true in the auditor's <config> block — the auditor itself never reads config.


Step 2 — Run a review and read the report

/msd-ui-review 3

The audit's static captures land where they always did. With the key on and a Chrome resolved, an interaction/ directory beside them holds baseline.png, focus-first.png (focus ring on the first focusable element), one capture per interaction the auditor drove from your UI-SPEC's interactive components, the accessibility snapshot it used for element ids, and the page's console output. The audit's .gitignore gate covers that interaction/ directory as a whole — the snapshot carries whatever was typed into forms and the console output can carry tokens — so a git add . never commits it, and a project whose .gitignore predates the directory is upgraded on the next audit. UI-REVIEW.md carries the outcome on its own line:

**Interaction captures:** captured (4 state(s), 0 failed) in .planning/ui-reviews/03-.../interaction

The other values it can hold are honest, not decorative:

**Interaction captures:** Meaning
off The key is false. Nothing else changed.
skipped (no dev server reached) The static capture found no dev server; there was nothing to interact with.
skipped (no Chrome binary resolved) The key is on but no browser resolved. Set CHROME_BIN.
not captured (driver or capture failure) The key was on and Chrome resolved, but no state landed on disk — npx could not fetch the driver, Chrome did not launch, the page never opened, or every capture failed. The audit output names the failing step.

An interaction state that does not appear in that directory is not reported as observed; the Experience Design pillar says when its findings are code-derived.


What this does not do

  • It does not replace the static captures. npx playwright screenshot stays the driver for the three viewport shots; it has --wait-for-selector, --device, --color-scheme and cross-engine -b firefox|webkit, none of which the CLI driver offers. Firefox and WebKit needs stay on Playwright.
  • It does not touch msd-dom-verifier or workflow.live_dom_uat. That capability drives a browser MCP server at execute time; this one drives a CLI from Bash at review time. They are independent keys.
  • It does not share the chrome-devtools-mcp browser profile. The auditor starts the daemon with --isolated, so it never contends for the profile lock a registered MCP server holds, and stops it when the capture ends — under an EXIT trap, so an aborted audit still stops it.
  • It does not let the driver write outside the capture directory. The daemon starts with --workspace set to the run's interaction/ directory; that is the only place its file-writing verbs may land.
  • It does not wait on selectors. wait_for is MCP-only; the auditor polls document.readyState through evaluate_script where a state needs settling.

Turning it back off

msd-tools query config-set workflow.ui_interaction_capture false

The next review runs the Playwright-only path and reports **Interaction captures:** off.