|
| 1 | +--- |
| 2 | +name: verify-mosaic |
| 3 | +description: Drive @clerk/mosaic components and flows in swingset, the Mosaic component explorer (Next.js dev server in packages/swingset), through an isolated agent-browser session, and capture ARIA snapshots, screenshots, and DOM attribute dumps as evidence. Use it to prove a change to packages/mosaic or packages/swingset works in a real browser before calling it done, to reproduce a Mosaic UI bug, or to check knobs, overlays, RTL, chaos data, and dark mode on a component page. |
| 4 | +--- |
| 5 | + |
| 6 | +# verify-mosaic |
| 7 | + |
| 8 | +`bin/control-swingset` (beside this file) starts swingset for this worktree, checks it, gives you an `agent-browser` session that no other worktree shares, makes evidence folders, and tears it all down. Swingset reads Mosaic from `packages/mosaic/src`, so a Mosaic edit shows up on reload without rebuilding `@clerk/mosaic`. |
| 9 | + |
| 10 | +A change to Mosaic UI is not done until a browser shows the changed behaviour on the page that documents it. Unit and browser-mode tests in `packages/mosaic` prove the code. This skill proves what a user sees. |
| 11 | + |
| 12 | +Run every command from the repo root. Below, `CS` means `packages/mosaic/.claude/skills/verify-mosaic/bin/control-swingset`. |
| 13 | + |
| 14 | +## Launch |
| 15 | + |
| 16 | +Once per machine: install `agent-browser` 0.27 or newer (`brew install agent-browser`). Read `agent-browser skills get core` once if you have not used it. |
| 17 | + |
| 18 | +Once per worktree, after `pnpm install`: |
| 19 | + |
| 20 | +```console |
| 21 | +$ pnpm turbo build --filter='@clerk/swingset^...' |
| 22 | +$ CS up |
| 23 | +start pid 3722 log <repo>/packages/mosaic/.verify/swingset.log |
| 24 | +listen http://localhost:6008 |
| 25 | +ready http://localhost:6008/components/button session verify-mosaic-057349b4 |
| 26 | +``` |
| 27 | + |
| 28 | +- `up` runs `packages/swingset/scripts/dev.mjs`, which takes port 6006 or the next free one. Other worktrees often hold 6006 and 6007, so always use the port `up` prints, or `CS url`. |
| 29 | +- Ready means `/components/button` returned 200, which forces the first compile (about 20s cold). |
| 30 | +- `up` reuses an instance this worktree already started. It refuses when a swingset that it did not start is running in this worktree, because the two would share `packages/swingset/.next`. |
| 31 | +- `up` refuses when `@clerk/nextjs` or `@clerk/shared` has no `dist/`, and prints the build command. |
| 32 | + |
| 33 | +## Doctor |
| 34 | + |
| 35 | +Run it first, and again whenever anything looks off. It only reads. |
| 36 | + |
| 37 | +```console |
| 38 | +$ CS doctor |
| 39 | +ok agent-browser 0.27 or newer (found 0.27.0) |
| 40 | +ok state file <repo>/packages/mosaic/.verify/instance.env |
| 41 | +ok process 3722 alive |
| 42 | +ok port 6008 served from this worktree (<repo>/packages/swingset) |
| 43 | +ok http://localhost:6008/components/button returns 200 |
| 44 | +info started at HEAD 9f579d368f (now 9f579d368f) |
| 45 | +info session verify-mosaic-057349b4 |
| 46 | +``` |
| 47 | + |
| 48 | +Each `FAIL` line has a `fix`. The exit code is 1 when any check fails. Swingset reloads source edits, so a different `HEAD` is only information. After a dependency rebuild, run `down` and `up`. |
| 49 | + |
| 50 | +## Drive |
| 51 | + |
| 52 | +`CS ab <args>` is `agent-browser --session verify-mosaic-<hash>` for this worktree. Never call bare `agent-browser`: its `default` session can belong to another agent. |
| 53 | + |
| 54 | +```console |
| 55 | +$ CS ab open "$(CS url /components/dialog)" |
| 56 | +$ CS ab snapshot -i # interactive elements with @refs |
| 57 | +$ CS ab find role button click --name "Open dialog" |
| 58 | +$ CS ab wait '[role=dialog]' |
| 59 | +$ CS ab eval '(() => document.querySelector("[role=dialog]").dataset.variant)()' |
| 60 | +``` |
| 61 | + |
| 62 | +Conventions: |
| 63 | + |
| 64 | +- Routes are `/<group-slug>/<component-slug>`: `/components/button`, `/components/dialog`, `/user-button/user-button`, `/misc/organization-switcher`. The slug is the story's `meta.group` and `meta.title` run through `packages/swingset/src/lib/slug.ts`. A wrong path still returns 200 with `No docs found`, so check the level-1 heading. Read the paths of the expanded sidebar groups with `CS ab eval '(() => [...document.querySelectorAll("[data-sidebar] a")].map(a => a.getAttribute("href")))()'`, or click the link with `CS ab click 'a[href="/user-button/user-button"]'`. |
| 65 | +- Prefer role and accessible name (`find role button --name …`). When `find` cannot resolve a name, as with the header switches, take `snapshot -i` and use the `@ref`. Refs change after every navigation and every re-render, so take a new snapshot first. |
| 66 | +- Mosaic parts carry their identity in the DOM: a `.cl-<slot>` class plus `data-<axis>` attributes (`data-variant`, `data-size`, `data-color`, `data-open`). Assert on those, not on computed colours or class hashes. `data-style-src` lists the StyleX source lines that styled the element. |
| 67 | +- Wrap every `eval` body in an IIFE. The page keeps `const`s between `eval` calls, and a second `const b` throws. |
| 68 | +- Wait on an element (`CS ab wait '<selector>'`), never on `wait --url`. Swingset navigates on the client, and `wait --url` hangs until its timeout. |
| 69 | +- `agent-browser` has no per-command timeout. Give the Bash call its own timeout. |
| 70 | +- If a command hangs or prints `CDP response channel closed`, run `CS ab close`, then `CS doctor`, then start the recipe again from `open`. Doctor checks the server, not the browser, so a hung browser still shows all `ok`. |
| 71 | +- Set the viewport with `CS ab set viewport <w> <h>` before `open`. Changing it on a loaded page can drop the browser connection. |
| 72 | +- When you pull a ref out of a snapshot with `grep`, match `e[0-9]\+`. A bare `e[0-9]*` also matches the `e` in `ref`. |
| 73 | + |
| 74 | +Read [`notes.md`](notes.md) before driving. It says what to prove for a primitive, a component, a feature, or a Live Sandbox change. When a note turns out to be wrong, fix it in the same PR. |
| 75 | + |
| 76 | +## Evidence |
| 77 | + |
| 78 | +```console |
| 79 | +$ E=$(CS evidence dialog-open) # packages/mosaic/.verify/evidence/<utc>-dialog-open |
| 80 | +$ CS ab snapshot > "$E/open.aria.txt" |
| 81 | +$ CS ab screenshot "$E/open.png" |
| 82 | +$ CS ab eval '(() => Object.fromEntries([...document.querySelector("[role=dialog]").attributes].map(a => [a.name, a.value])))()' > "$E/open.attrs.json" |
| 83 | +``` |
| 84 | + |
| 85 | +Proof standards: |
| 86 | + |
| 87 | +- Drive the user path: click, type, and press keys on the page. Do not set React state or call story functions from `eval`. Use `eval` only to read. |
| 88 | +- Capture the action and the state it produced: before and after for a toggle, open and closed for an overlay, the knob value and the preview attribute for a prop. |
| 89 | +- Prove the check can fail. Save your diff to a file, undo the change, confirm the undo took effect, run the same commands into a `CS evidence <feature-id>-control` folder, and show the result differs. Then apply the diff again and run the check once more. A check that passes with and without the change proves nothing. |
| 90 | +- A screenshot alone proves nothing about state. Pair it with the ARIA snapshot or the attribute dump that shows the claimed value. |
| 91 | +- For a styling change, check both themes (`Dark mode` switch) and RTL when the change touches layout. |
| 92 | +- Name the feature file and entry point you used. Report every entry point you could not reach, and why, instead of claiming it through another one. |
| 93 | +- Evidence lives in `packages/mosaic/.verify/evidence/`, which git ignores. `CS down` keeps it. Each folder's `source.env` records the commit it was made at, whether the tree had uncommitted changes, and whether swingset had an env file loaded. |
| 94 | + |
| 95 | +## Attach |
| 96 | + |
| 97 | +Once the branch is pushed and has a pull request, put the screenshots in its description: |
| 98 | + |
| 99 | +```console |
| 100 | +$ CS attach "$E" --pr <n> --before "$CONTROL" |
| 101 | +attached 3 screenshots with befores to https://github.com/clerk/javascript/pull/<n> |
| 102 | +``` |
| 103 | + |
| 104 | +- It writes a block between `<!-- verify-evidence:mosaic -->` and `<!-- /verify-evidence:mosaic -->`. A line names each evidence folder with the commit it was captured at, and the pull request's head commit when it was attached. With `--before`, a table shows each control screenshot beside the screenshot of the same name. Without it, the screenshots sit two across under their names. |
| 105 | +- Run it again to replace the block. Nothing outside the markers changes. |
| 106 | +- The upload is public, so it takes only fixture data. It refuses a folder that `CS evidence` did not make in this worktree's evidence folder, and a folder captured while swingset had an env file loaded, such as `.env.local` for the Live Sandbox. It also refuses while the running swingset has loaded one. |
| 107 | +- It refuses when a marker appears twice or alone, when the text between the markers does not start with its `**Verified in swingset**` line, or when the description ends in an unclosed code fence. Markers inside a code fence do not count. |
| 108 | +- It edits the description twice: once to upload the screenshots as a list, then to lay them out. If it stops after it began uploading, even when only some uploads failed, it says so. Check the description, then run it again. |
| 109 | +- `--before` keeps only the screenshots both folders have, and names each one it skips. Save the control screenshots under the same names. |
| 110 | +- It needs `gh` 2.99 or newer for `gh pr edit --attach`. `--dry-run` prints the command without changing the pull request. |
| 111 | +- Only `.png` files go up. ARIA snapshots and attribute dumps stay local; quote the line that proves the claim in the description instead. |
| 112 | + |
| 113 | +## Cleanup |
| 114 | + |
| 115 | +```console |
| 116 | +$ CS down |
| 117 | +closed browser session verify-mosaic-057349b4 |
| 118 | +stopped pid 3722 |
| 119 | +kept evidence in <repo>/packages/mosaic/.verify/evidence |
| 120 | +``` |
| 121 | + |
| 122 | +`down` closes only this worktree's browser session and stops only the process `up` started, after checking that the process runs from `packages/swingset`. It never kills by process name. Run it after a failed attempt too. A failed `up` runs it for you. |
| 123 | + |
| 124 | +## Changing the script |
| 125 | + |
| 126 | +`bin/test-control-swingset` tests `bin/control-swingset` without starting swingset. Run it after every change to the script, and add a case that fails without your change. |
| 127 | + |
| 128 | +## Not covered |
| 129 | + |
| 130 | +- The Live Sandbox (`/live`, `/sign-in`, `/sign-up`) needs `packages/swingset/.env.local` with a publishable key. Without it, report Live Sandbox paths as skipped. See [`notes.md`](notes.md). |
| 131 | +- The Playwright suite for Mosaic in a real Next.js app (`pnpm test:integration:mosaic`, tests in `integration/tests/mosaic/`) needs the team's integration secrets. This skill does not run it. |
0 commit comments