--- name: yellowjacket-dev description: Operating YellowJacket's development harness — which of the four test tiers to use for a given change, how to run the app headless and drive it with playwright-cli, seed and sandbox lifecycle, the three build-tag passes, and the failure modes that waste a cycle if you meet them cold. Use whenever building, running, testing or debugging this repo. --- # Working on YellowJacket `CLAUDE.md` says what this system **is**. This skill says what to **run**. `.planning/NOTES.md` records what we **measured** and when. Keep them in those three tenses: if something here is wrong, fix it here and add the discovery to `NOTES.md` — do not add a corrective paragraph to `CLAUDE.md`. Every command below is a `make` target on purpose. The Makefile is the source of truth for *how* to invoke something; this file only decides *which* and *in what order*. `make skill-check` fails if a target named here has disappeared. ## Read this part before you fail Fifteen things cost a cycle each the first time. They are here, not in a reference, because you need them *before* the failure, not after. - **Call a binding through the bridge.** `window.go` does not exist under Wails v3 — the bindings are bundled modules, not a global — so use `window.__yjEvents.call(path, args, ms)` (browser) or `callBinding` (specs). Both post to the runtime's own endpoint by method name, so they work on any page, including one with no init script. A bad call now *rejects*, and says why: a wrong type comes back as a TypeError naming the argument, a wrong count as `expects 4 arguments, got 3`, an unknown method as a ReferenceError. Under v2 the backend logged `error parsing arguments` and never fired the callback, so `.dev/app.log` was the only place the reason appeared and the timeout was the only thing that made the mistake visible. The timeout is still there, but now it means a genuinely hung request. - **Nothing is clickable on a fresh `YJ_HOME`.** `` intercepts all pointer events until a library exists, and the click fails with a Playwright interception error that reads like a selector bug. Use a seed unless you are *testing* the wizard, in which case `make dev-headless-fresh`. - **Never `pkill -f`.** The pattern matches the invoking shell's own command line, killing it and silently dropping the rest of your compound command. `make dev-stop` kills by saved PID. - **Seeds are produced by running the app**, never by hand-writing a `config.toml` and DB rows — a hand-built `YJ_HOME` is a second description of a valid one and will drift. `make sandbox-seed` drives the real `AddLibrary` binding and waits for the real scan. - **…and a seed freezes every default it has already persisted.** Changing a default in `backend/config` (or `backend/tracklist`) is invisible against an existing seed, whose `config.toml` holds the old value — while CI builds its seed by running the app and therefore tests the *new* one. Re-seed before believing either. - **A `wa-dialog` is awkward to locate, in three ways.** The host is `display: contents`, so the element carrying your testid always reports hidden; the visible thing is the native `` in its shadow root. The slotted content is in the *host's* shadow root, not in that dialog's subtree, so `toContainText` on the dialog sees only its chrome. And it has an accessible name **only because `utils/name-dialog.ts` gives it one** — Web Awesome does not wire `label` to `aria-labelledby` — so a new dialog that forgets to call the helper from `updated()` is invisible to `getByRole('dialog', {name})`. - **A name is computed on the element carrying the *role*, and Web Awesome puts the role in its own shadow root.** `aria-label` on a `` or a `` host never reaches the tree. Use the component's own `label` (plus `styles/wa-slider-label.css.ts`, since a slider's is visible) or `utils/name-dialog.ts`. And in the light DOM, a `