| name | dev-app-verify |
| description | Drive and screenshot the running Rocket.Chat Desktop dev app (yarn start) through the main-process inspector on port 9339 — trigger menu items (Simulate Download/Update), evaluate in the renderer DOM, capture titlebar screenshots. Use whenever a UI change needs runtime/visual verification that component tests can't see (paint, clipping, colors, animation, layout). |
Verify UI in the running dev app
yarn start launches Electron with --inspect=9339 (main-process Node
inspector; there is no renderer CDP port). Everything below drives the
app through that socket: real menus, real Redux, real paint.
This skill drives the local macOS dev machine — commands (pkill, /tmp
paths) are macOS-specific by design. No Windows variants.
When to use
- A UI change needs visual proof (component tests can't see paint — a
clipped SVG passes every DOM assertion).
- You need the simulate flows (
Simulate Download / Simulate Update Flow)
run and screenshotted at specific progress points.
- You need computed styles, bounding boxes, or DOM structure from the live
renderer.
Before connecting — the three pitfalls
- Watcher restarts kill everything. The rollup watcher restarts the
whole app when ANY bundle rebuilds (including after a subagent's last
file save). Confirm the
yarn start log shows no bundles src/ /
Restarting main process lines for 12–15s before any timing-sensitive
run. A builder's "finished" report can arrive before its final saves hit
the watcher.
- Occluded windows lie. macOS stops painting occluded windows and
capturePage returns the last painted frame — screenshots freeze while
the DOM moves. Always win.show(); win.focus() before captures.
- Singleton wedges. If the inspector port refuses connections while an
Electron process exists, two instances raced the SingletonLock. Recovery:
pkill -9 -f "<worktree-name>", wait, single fresh yarn start
(cold boot ≈ 30s).
- Background
gitnexus analyze interferes. While it runs it mutates
worktree git state and touches watched files — it can restart the app
mid-verification (phantom bundles src/ rebuilds) and silently drop
freshly staged files from the git index. Don't reindex during a
verification run; when you do reindex, use
node .gitnexus/run.cjs analyze --index-only.
The script
Run with the context-mode sandbox (Bun has a global WebSocket) or any Bun
runtime. Adapt the marked sections.
const targets = await fetch('http://127.0.0.1:9339/json', {
signal: AbortSignal.timeout(3000),
}).then((r) => r.json());
const ws = new WebSocket(targets[0].webSocketDebuggerUrl);
let id = 0;
const pending = new Map();
const REQUEST_TIMEOUT_MS = 5000;
const failAllPending = (reason) => {
for (const [i, { reject }] of pending) {
reject(reason);
pending.delete(i);
}
};
ws.onmessage = (e) => {
const m = JSON.parse(e.data);
if (m.id && pending.has(m.id)) {
pending.get(m.id).resolve(m);
pending.delete(m.id);
}
};
ws.onerror = (e) => ( ());
ws. = ( ());
= () =>
( {
i = ++id;
timer = ( {
pending.(i);
(
(
)
);
}, );
pending.(i, {
: {
(timer);
(m);
},
reject,
});
ws.(.({ : i, method, params }));
});
( {
ws. = res;
(rej, );
});
();
= () => {
r = (, {
expression,
: ,
: ,
});
(r.)
();
(r.?.)
(.(r..).(, ));
r.?.?.;
};
= ;
= ;
();
();
.(
()
);
();
ws.();
Gotchas
- In the main-process inspector sandbox bare
require may be missing — use
process.mainModule.require('electron').
- Developer-menu toggles can be driven with
Menu.getApplicationMenu().getMenuItemById('<id>').click() (ids:
developerMode, simulateUpdate, simulateDownload,
simulateDisconnected).
- The tray
Tray instance is module-scoped and not reachable from CDP;
opening the tray menu needs a real click — osascript/System Events clicks
require Accessibility permission for the terminal app (error -25211
otherwise). screencapture -x + sips -c crops work without permission
for verifying the icon itself.
yarn start relaunches Electron once per bundle for ~60 s; wait for
waiting for changes before screenshots.
Useful recipes
- Real download with known size (slow mirror, good for watching the
ring): grab a cancel handle first, then
wc.downloadURL('https://proof.ovh.net/files/1Gb.dat') on a webview's
webContents (wc.session.once('will-download', (e, item) => { globalThis.__t = item; })),
cancel with globalThis.__t.cancel() when done.
- DOM-truth beats screenshots for diagnosis:
getBoundingClientRect of
svg children vs their svg viewport catches clipping that looks like
"missing artwork"; getComputedStyle(...).stroke/opacity/transition
catches token and animation regressions.
- The dev instance uses the
Rocket.Chat (development) userData profile —
its persisted settings (theme, navigationLayout: 'tabs' | 'sidebar' | 'hidden') live in that profile's config.json; edit + restart to switch
the layout under test (TopBar layouts only render with sidebar/hidden).