ui-verification · git:20261007.7c593ed · 2026-10-07 · sha256 cace58ffc748c0e0
ui-verification git:20261007.7c593edA
Immutable. This exact content never changes and is served at /api/v1/blob/cace58ffc748c0e0.
--- description: UI layout checks before claiming work is done globs: external/ui/**/* alwaysApply: false --- # UI verification (Playwright MCP) Before you tell the user that a UI change is complete or merge-ready: 1. Choose one free port (for example **`PORT=5201`**) and start **`npx vite --host localhost --port "$PORT"`** in **`external/ui`**. 2. Use **Playwright MCP** (or the repo browser tools) to open **`http://localhost:$PORT/layout-scroll-check.html`** and **`http://localhost:$PORT/`** with at least two viewports: **narrow** (for example **390px**) and **wide** (for example **1280px**). 3. Run **`browser_evaluate`** to compare **`getBoundingClientRect()`** edges for **`.chat-header`**, **`.messages-inner`** first child, and **`.composer-card`** (left and right should match within about **1px** after shared column padding). 4. If anything is off, fix CSS and re-check before reporting done. This is required for changes to **`external/ui/src/styles.css`**, **`ChatScreen`**, **`Composer`**, or nav layout. ## Safari and other WebKit reports A Safari-only report is reproducible without a Mac: Playwright's WebKit is the engine Safari is cut from and its version tracks Safari's. When the change touches a capped, scrollable or **`position: fixed`** surface, run **`external/ui/scripts/webkit-scroll-check.mjs`** against a live **`coddy serve`** (setup and env vars in **`docs/surfaces/web-ui.md`**, *Reproducing a Safari report without a Mac*), and run it again with **`CODDY_ENGINE=chromium`** to tell a WebKit-only regression from a layout bug every engine shares. ## The transcript at every width of the grid Nothing in the transcript may be wider than the transcript: on the stacked shell its column is the page, and one row that cannot wrap makes a phone scroll sideways (**`DESIGN.md`**, *Layout grid*, *Tool card UI*, *Markdown*). When the change touches the transcript's rows, the Markdown styles or a width query, run **`external/ui/scripts/phone-overflow-check.mjs`** against a **`vite`** dev server (setup in **`docs/surfaces/web-ui.md`**, *Checking the transcript at every width of the grid*): it mounts the rows that used to overflow and fails when the page scrolls sideways or anything sticks out of **`.messages-inner`** at 360 to 1280px. A new width query goes into the grid first; the vitest **`layoutGridCss.test.ts`** fails on one the grid does not name. ## The fold chevron The chevron is an SVG whose ink is centred in its viewBox, never a text glyph: a glyph's ink moves with the platform's font, which is why the reports of a chevron riding above its label kept coming back (**`DESIGN.md`**, *Chevron*). jsdom has no layout, so vitest cannot see where it lands. When the change touches the chevron, the rows it sits on or the type around them, run **`external/ui/scripts/chevron-align-check.mjs`** against a **`vite`** dev server (setup in **`docs/surfaces/web-ui.md`**, *Checking the fold chevron against its label*): it measures the chevron's ink centre against the label's on a transcript row and on the Tasks panel toggle, and fails past **1px**. ## A long transcript Only a bounded slice of a transcript is in the DOM, and a long session holds only the end of its history until the reader scrolls up (**`DESIGN.md`**, *Transcript window*; **`docs/surfaces/web-ui.md`**, *Long sessions*). jsdom has no layout, so vitest renders every row and cannot see the window at work. When the change touches the transcript window, the rows, the messages or tool-calls routes, or what a row renders, run **`external/ui/scripts/transcript-window-check.mjs`** against a binary built with **`TAGS="http ui swarm"`** (setup in **`docs/surfaces/web-ui.md`**, *Checking a long transcript*): it opens a 3306-message session under CPU throttling, scrolls it without a jump, edits a prompt of an older page, runs a turn in a second browser, reads through a swarm relay, and fails past a budget. Run it once more with **`CODDY_ENGINE=webkit`** when the change touches scroll anchoring: WebKit does not anchor scrolling, so there the window's own correction is all that keeps the reader's row still. ## The views of a chat The view buttons of the chat header, the dock beside the chat and the Files window are placement, focus and media through a relay, none of which jsdom can see. When the change touches **`chat/ChatHeader.tsx`**, the dock, **`files/`**, the files routes or the swarm mount, run **`external/ui/scripts/files-check.mjs`** against a binary built with **`TAGS="http ui swarm"`** (setup in **`docs/surfaces/web-ui.md`**, *Checking the views of a chat through a swarm relay*): it drives the menu, the dock and the window through an authenticated relay, plays a sound from its signed address through the relay, and measures the buttons and the window at every tier of the grid in English and in Russian. Run it once more with **`CODDY_ENGINE=webkit`** when the change touches the window's layout. ## The shared file cards The card of a shared file is `overflow: hidden`, so its actions menu is portalled to the document and placed from the trigger, and the trigger is placed from the extension badge's own numbers (**`DESIGN.md`**, *Shared file cards*). Whether a menu can be seen and reached is a layout fact jsdom cannot answer: while the menu sat inside the card, a click on the trigger drew nothing and every vitest still passed. When the change touches **`messages/ToolArtifactCards.tsx`** or the card's styles, run **`external/ui/scripts/artifact-card-check.mjs`** (**`npm run check:artifacts`**) against a **`vite`** dev server (setup in **`docs/surfaces/web-ui.md`**, *Checking the shared file cards*): it measures the cards, the trigger against the badge and the open menu on desktops and on emulated phones driven by taps, down to 320px, and fails past **1px**. ## Capture isolation and redaction - Captures use a temporary browser-context page, never the operator's main tab, and close it afterward. - Navigate the capture page through localhost to avoid host-specific zoom. Set the viewport explicitly and blur focus unless focus is what the screenshot documents. - Screenshot stands use isolated `HOME` and `CODDY_HOME` with neutral fixtures. - Before pushing, inspect every PNG for skill names, hosts, tokens, usernames, and personal paths. - Screenshots for English pages and pull requests are taken with `?lang=en`, so the browser's locale does not decide the language of the capture.