Files
yellowjacket/.pi/skills/yellowjacket-dev/references/ui-tier.md
T
logan 5ca6cad45a
Build & publish Arch package / arch-package (push) Successful in 2m8s
CI / check (push) Failing after 1m56s
CI / e2e (push) Skipped
Search index maintenance / maintain-index (push) Successful in 13s
feat(harness): agent-drivable dev harness and CI that gates
A coding agent could develop this repo's Go packages and could not
develop the application: every path to running YellowJacket ended in a
blocking GTK window, so 265 bound methods, 46 events, 33 component
directories and 13 stores had exactly one form of verification
available — `tsc --noEmit`.

The unlock is that `wails dev`'s dev server on :34115 serves the real
frontend with the real generated bindings against the same Go backend a
desktop window attaches to, so a plain Chromium under Xvfb gets a fully
functional app. Four test tiers now exist, cheapest first:

- `make ui-test` — 313 Vitest tests in a real browser in ~2 s, no app,
  no backend, no display. Works because `frontend/wailsjs/` is a pure
  passthrough to `window.go`/`window.runtime`, so faking just those two
  globals runs the real bindings and the real store code.
- `make test` — services in-process, asserting on the payload the
  frontend would receive, via a new `events.Emit` wrapper.
- `make dev-headless` + `playwright-cli` — the real app, driven
  interactively, with an event bridge on `window.__yjEvents` and a
  dev-only control surface at `/__test/`.
- `make e2e` — 19 of those flows frozen as Playwright specs.

`events.Emit(ctx, …)` replaces all 35 direct `runtime.EventsEmit` call
sites: wails' `getEvents` `log.Fatalf`s on any context without its
runtime, so those paths could not run under test and a background
worker could take the app down. Four packages had each hand-rolled the
same guard; nine more guarded on `ctx != nil`, which does not help.
`TestNoDirectRuntimeEmits` fails the build on a new one.

Fixtures are generated, not committed (`make testdata`), and seeds are
built by *running the app* — never by hand-writing config and DB rows,
which would be a second description of a valid YJ_HOME.

`.gitea/workflows/ci.yml` is the first workflow here that tests
anything; the other three only package, so `gitea_ci` reported only
packaging jobs and misled anyone asking whether a push was healthy.
Both jobs were prototyped to green in a bare ubuntu:24.04 container
before the YAML was written, which immediately caught `make lint`
linting three configurations that nothing builds: all three passes
omitted `webkit2_41`, so wails resolved webkit2gtk-4.0 — which Arch
still ships and Ubuntu 24.04 dropped.

Operational instructions live in `.pi/skills/yellowjacket-dev/`,
measured discoveries in `.planning/NOTES.md`, and architecture in
`CLAUDE.md` — split by tense, not by topic, because a topical split
gives every new fact two plausible homes. `make skill-check` fails a
commit if the skill cites a make target that does not exist.
2026-08-10 23:20:42 -04:00

75 lines
3.1 KiB
Markdown

# The component and store tier (`make ui-test`)
313 tests in a real Chromium in ~2 s with no Wails, no backend, no
seeded library and no virtual display. This is the cheapest coverage
available and where the bulk of UI regression belongs.
```bash
make ui-setup # once: the Vitest provider's own Chromium
make ui-test # behaviour only
make ui-watch
make ui-visual # + toMatchScreenshot baselines (YJ_VISUAL=1)
make ui-visual-update # re-record them
make ui-test UI_ARGS='store/queue' # filter
```
## How it works
`frontend/wailsjs/` is a pure passthrough — every binding is
`window.go[svc][Type][Method](args)`, every runtime call is
`window.runtime.X(...)`. So `frontend/test/support/wails-fake.ts`
replaces **those two globals and nothing else**, and the tests then
exercise the *real* generated bindings and the *real* store code. No
module mocking, and no second description of the Wails layer.
```ts
emit(Events.QueueChanged, payload); // push a backend event
stub('queue.Queue.GetState', state); // a value, or a function of the args
stubFailure('queue.Queue.SetQueue'); // reject, as a Go error does
calls('queue.Queue.SetQueue'); // what the frontend called back with
lastArgs('queue.Queue.SetQueue');
const el = await fixture('now-playing'); // mount; shadow()/text() query it
```
The dispatcher mirrors wails' own `desktop/events.js`, including
`maxCallbacks` expiry and the fact that a frontend `EventsEmit`
notifies local listeners *before* Go.
## Four things that will cost you time
- **Store singletons are constructed at module import**, before any test
can stub. `test/setup.ts` therefore carries import-time defaults for
the stores that read config in their constructor. Without one, a store
caches `undefined` where Go would have sent `[]`, and components crash
on `.length` — which reads exactly like a component bug and is not.
Adding a store that reads config on construction means adding its
default there.
- **`vitest.config.mts`, not `.ts`** — it `mergeConfig`s the repo's
`vite.config.mts` to reuse the `@go`/`@store`/`@components` aliases,
and a `.ts` sibling cannot import it.
- **Screenshots need the theme.** The setup file imports
`@store/theme-store` for its side effect (it applies the `--yj-*`
ramp to `:root`); without it a component renders white-on-white and
the baseline is blank.
- **`@lit-labs/virtualizer` never produces two identical frames**, so
`toMatchScreenshot` on `<queue-panel>` fails with "could not capture a
stable screenshot" rather than a diff. Assert on its rows instead.
Visual baselines are font-hinting and compositing sensitive, which is
why they are opt-in: they only mean anything on the machine that
recorded them.
## Bindings
`frontend/wailsjs/` is generated by `wails`, **not** by `go generate`,
so the pre-commit codegen check does not cover it — a renamed Go bound
method first shows up at runtime, as a call that never settles.
```bash
make bindings-check # ~1.5 s, also a pre-commit hook
make bindings # regenerate for real
```
The generator rewrites `wailsjs/runtime/*` as mode 755 every run; that
is churn, not drift, and the check ignores it.