Repository navigation
[Feature]: Layout and accessibility checks in done and preview, and end-to-end coverage of the design loop #493
Copy link
Copy link
Open
Description
Problem / pain point
doneis the agent's last check before it stops, and it only sees syntax and runtime errors. The static lint inpackages/core/src/tools/done.tscovers unclosed tags, duplicate ids, missingalt, broken hash links and JSX structure;makeRuntimeVerifier(apps/desktop/src/main/done-verify.ts) loads the artifact in system Chrome at 1280×800 and collects console, page and request errors. A design that loads cleanly but scrolls sideways, clips its text, sets grey text on a grey background or has tiny tap targets gets "ok — no syntactic or runtime issues detected", followed by an instruction to stop calling tools.previewdoes not show these either: it reportsnodes,width,height,loadMs, the console and asset errors, and a screenshot (vision models) or a depth-4 tag outline (text-only models). A vision model has to notice a layout defect in the picture; a text-only model has no way to.- Claude Design's help center says Claude can "review your design for accessibility, contrast ratios, information hierarchy, and general usability" and "checks its own output against your design system, and makes corrections before you see them" (Get started with Claude Design). In Open CoDesign the first is left to the model's eyes and the second does not exist.
- The repo has no end-to-end coverage.
CLAUDE.mdandAGENTS.mdname Playwright for E2E,CLAUDE.mdlistspnpm test:e2e,website/roadmap.mdlists "Add broader Playwright coverage for New Design → ask → scaffold → write → preview → tweaks → done" under v0.2.x, and.github/dependabot.ymlgroups Playwright packages, but there is no Playwright dependency, no config and notest:e2escript. The one browser-level test,apps/desktop/src/main/preview-interactions.browser.test.ts, drives system Chrome withpuppeteer-coreand skips itself when Chrome is missing.agent.native-lifecycle.test.tsruns the real piAgentwith a scripted stream for lifecycle cases, but no test runs the tool chain (editor,preview,done) as one flow.
I searched open and closed issues and PRs for Playwright, contrast, accessibility, overflow, layout checks and e2e. Nothing open covers this. The closed #392 bundled accessibility and code-quality verifiers with 15 skills and a multi-agent orchestrator and was not merged.
Proposed solution
Two parts. Part 1 needs no new dependency and no decision beyond this issue. Part 2 asks the maintainers a few questions before any code.
Part 1: deterministic layout and accessibility checks in preview and done
- One probe function that runs inside the page Chrome already has open: plain DOM,
getComputedStyleandgetBoundingClientRect, no library.preview-runtime.tsalready runs apage.evaluatefor metrics, anddone-verify.tshas a settle window after load; both call the same probe from a shared module, so the two reports cannot disagree. It returns at mostMAX_LAYOUT_ISSUESentries (20), each with the check name, a selector and the measured numbers, and it visits at mostMAX_PROBE_ELEMENTSelements (5,000). If it reaches that cap, throws or times out, the report says so as a warning; a failed probe is never dropped silently. - Checks, each with named thresholds:
- horizontal overflow: the document is wider than the viewport, at 1280 px and again at a 390 px phone width (the verifier resizes the viewport, no reload);
- clipped text: an element with
overflow: hiddenwhose content is larger than its box; - contrast: WCAG 2.x ratio of the text colour over the effective background, 4.5:1 and 3:1 for large text. The background comes from walking ancestors and compositing alpha; colours are resolved through a 1×1 canvas so
oklch()orcolor-mix()arrive as sRGB. A background image or gradient is reported as "not measured" instead of guessed; - tap targets: links, buttons and inputs under 24×24 CSS px (WCAG 2.2 SC 2.5.8);
- controls with no accessible name: no text,
aria-label,aria-labelledby,<label>ortitle.
- Findings are warnings, not errors.
donestill decidesok/has_errorsfrom errors alone, so a false positive cannot start a repair loop or use up the three repair rounds. They appear in the existing non-blocking warnings block of thedoneresult (its heading changes from "design metadata warnings" to "warnings"), andpreviewgets alayoutIssuesfield next tometrics.DoneRuntimeVerifierreturns{ errors, warnings }instead ofDoneError[]. It has one producer (makeRuntimeVerifier) and two consumers:makeDoneTool, and the standalonedone:verify:v1IPC, which keeps returningerrorsand gainswarnings. - Later, once feat(core): map design tokens to DESIGN.md fields #490 lands: a drift check that compares the colours and font families the page uses with the tokens in
DESIGN.md, reusing the colour parser from that PR. It is the closest equivalent of "checks its own output against your design system".
Part 2: regression coverage for the design loop
- 2a. Vitest tests with the real pi
Agentand a scripted model stream, in the style ofagent.native-lifecycle.test.ts: scaffold, write through the editor tool,preview,donereturnshas_errors, fix,donereturnsok; and a layout warning that does not blockdone. No new dependency, part ofpnpm test. - 2b. An Electron smoke test: start the built app against a local fake model endpoint, create a design, see the preview render. This needs answers to three questions before I write it:
- Driver: Playwright, which the docs and roadmap name (Apache-2.0, dev-only, never shipped), or the
puppeteer-corealready in the tree attached to Electron's remote-debugging port. If you prefer the second, I will correct the wording inCLAUDE.md,AGENTS.mdand the roadmap to match. - Model: a local HTTP server that speaks the OpenAI-compatible stream, selected through the normal custom-provider setting, so the app needs no test-only code path. Is that acceptable?
- CI:
checkruns on ubuntu only to control Actions cost and already installs the Electron binary. Should the smoke test join that job (Linux needs a virtual display), run in its own workflow, or stay a localpnpm test:e2e?
- Driver: Playwright, which the docs and roadmap name (Apache-2.0, dev-only, never shipped), or the
Plan. About 3,000 lines in total, about half of it tests, in PRs of 350 to 700 lines. Each PR stands alone and keeps pnpm lint && pnpm typecheck && pnpm test green.
- Probe, overflow and clipped-text checks, the
{ errors, warnings }contract, and thepreviewanddonewiring (about 550). - Contrast (about 450).
- Tap targets and accessible names (about 350).
- 2a agent-loop tests (about 600).
- Drift check, after feat(core): map design tokens to DESIGN.md fields #490 (about 400).
- 2b Electron smoke test, after your answers (about 700).
Alternatives considered
- axe-core: MPL-2.0, which conflicts with hard constraint 4 once shipped, and a large dependency for five checks that are mostly arithmetic on computed styles.
- Block
doneon layout findings: these heuristics have false positives (decorative text, intentional overflow in a carousel), and a blocking false positive costs a repair round or ends in a run that keeps a flawed artifact with a warning. Warnings give the model the numbers and let it judge. - Put the checks in the system prompt only ("check contrast yourself"): that is the status quo, and a text-only model cannot do it.
- Leave end-to-end coverage as it is: the roadmap and
CLAUDE.mdpromise it, and the agent loop keeps changing ([Feature]: Stop a run that keeps repeating the same tool call #487, feat(core): stop a run that keeps repeating the same tool call #489, [Feature]: Context control that follows the model's real context window #475).
Scope
Generation capability
Hard-constraints check
- I have read the hard constraints in CLAUDE.md and this proposal does not conflict with any of them. Part 1 adds no dependency and runs only inside the Chrome page that
previewanddonealready open; the probe sits in modules those tools already load. Part 2b's driver is a dev-only tool and is a question for you.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
github-actions commentedon Oct 14, 2026
Thanks for the thorough proposal — separating the no-new-dependency work in Part 1 from the questions in Part 2b makes it easy to evaluate, and the body points at the right modules.
What I can confirm from the public tree
CLAUDE.mdstates "Tests: Vitest (unit) + Playwright (E2E)" and listspnpm test:e2e, but the rootpackage.jsonscripts includetest,test:scripts,lint,lint:fix,typecheck,format,build,dev,docs:*,smoke,changeset,version-packages,release— there is notest:e2e.pnpm-lock.yamllistspuppeteer-coreunder theapps/desktopandpackages/exportersimporters, which is consistent withpreview-interactions.browser.test.tsdriving system Chrome and skipping when Chrome is absent. The documented Playwright story is ahead of the actual dependency tree.getComputedStyleandgetBoundingClientRect, running inside the Chrome page the tools already open, adds no shipped/bundled dependency and no app-start cost.On the Part 1 design
donedecidesok/has_errorsfrom errors alone is the right call — a heuristic false positive should not consume a repair round. Please make sure the heading change from "design metadata warnings" to "warnings" does not change how the model reads that block.{ errors, warnings }change touches two consumers (makeDoneTooland thedone:verify:v1IPC).CLAUDE.mdasks that IPC payloads carry aschemaVersion; the standalone IPC should gainwarningsadditively rather than reshaping the existingerrors.MAX_LAYOUT_ISSUES,MAX_PROBE_ELEMENTS) plus a surfaced warning on cap hit, timeout or thrown probe is the right failure mode — and keeping "not measured" for background images/gradients instead of guessing a ratio is the correct default.DESIGN.mddrift check until feat(core): map design tokens to DESIGN.md fields #490 lands is reasonable; that is the only piece that depends on new colour parsing.pnpm typecheck(Turborepo), sopnpm -r typecheckis not required. New features are expected to ship at least one Vitest test, so the thresholds and cap behaviour should be unit-testable without Chrome.Part 2b questions (maintainer calls)
puppeteer-core. A dev-only, never-shipped Playwright dependency is allowed under constraint 4 (and Apache-2.0 is permissive regardless), but it would be a second browser-automation dependency alongsidepuppeteer-core. Whichever is chosen, the wording inCLAUDE.md,AGENTS.mdand the roadmap should be corrected to match.checkneeding a virtual display for Electron (e.g. xvfb), a separate workflow, or a local-onlypnpm test:e2e.Thanks also for linking #392 as prior art.
Need more info:
Open-CoDesign Bot
wudilyy999 commentedon Oct 15, 2026
Thanks for the review. Answers in order.
Platform. macOS. I would develop and run the Electron smoke test locally on macOS, where no display setup is needed. The Linux virtual display would only be a CI concern, and I would try that job on a fork before proposing it.
IPC.
done:verify:v1keeps itserrorsfield as it is and gainswarningsas an additional field, so the channel name and existing callers stay the same. The current payload has noschemaVersion; I will addschemaVersion: 1next towarningsin the same PR, again as an additional field.Heading. Each warning entry already carries a
source. The block heading becomes "Non-blocking warnings" and the entries keep thesource: messageform, so layout and DESIGN.md warnings read the same way: advice that does not changeokorhas_errors.Commands. You are right about
pnpm typecheck; I have corrected the plan in the description, and the PR descriptions will usepnpm lint && pnpm typecheck && pnpm test.Testing without Chrome. The in-page function only collects raw samples (resolved sRGB colours, rectangles, overflow flags, element counts). The decisions live in pure functions on the Node side: the WCAG ratio, alpha compositing, the thresholds,
MAX_LAYOUT_ISSUESand the cap, timeout and thrown-probe warnings. Those get plain Vitest tests. A Chrome-backed test followspreview-interactions.browser.test.tsand skips itself when Chrome is missing.github-actions commentedon Oct 15, 2026
Thanks for the detailed follow-up — the answers line up with what's in the public tree, so here's a point-by-point closure on the open questions and what's still a maintainer call.
Platform. macOS for local development is a fine choice. Nothing in the repo requires display setup on macOS, and keeping the Linux virtual-display work as a fork/CI experiment is the right way to de-risk it before proposing a job.
IPC. Keeping the
errorsfield as-is and addingwarningsplusschemaVersion: 1as additional fields is the correct shape.CLAUDE.mdrequires aschemaVersionon IPC payloads ("Schema-version everything that lives on disk. Config files, SQLite tables, IPC payloads, exported bundle formats — all carry aschemaVersionfield"), so this closes the gap the earlier review flagged. Adding it alongside the existingdone:verify:v1channel name is slightly redundant but harmless, and it keeps existing callers working as long as unknown fields are ignored.Heading. "Non-blocking warnings" with
source: messageentries keeps layout andDESIGN.mdwarnings reading the same way and, importantly, leavesok/has_errorsdriven by errors only. That preserves the repair-round semantics we discussed.Commands. Confirmed against the root
package.json: it exposeslint,typecheck,test(andtest:scripts), and there is still notest:e2escript.pnpm lint && pnpm typecheck && pnpm testis the right trio; note that roottestrunspnpm test:scripts && turbo run test.Testing without Chrome. Splitting the in-page sampler (raw sRGB colours, rects, overflow flags, counts) from the pure decision functions on the Node side (WCAG ratio, alpha compositing, thresholds,
MAX_LAYOUT_ISSUES, cap/timeout/thrown-probe warnings) is exactly the testable boundary this repo wants, and a Chrome-backed test that skips when Chrome is absent matches the pattern already used by the existing browser test. Good call.Verified from the public tree:
package.json—lint,typecheck,test,test:scriptsexist; notest:e2e.pnpm-lock.yaml—puppeteer-coreappears under theapps/desktopandpackages/exportersimporters, consistent with the existing Chrome-driven test.CLAUDE.md— schema-version requirement covers IPC payloads; "New features require at least one Vitest test."AGENTS.md— constraint 7 ("Reuse pi primitives first") and constraint 5 (permissive shipped deps, workflow-only tools may differ) are the ones Part 2 touches.Still maintainer calls (unchanged):
puppeteer-corealready in the tree. Whichever wins, the docs/roadmap wording should be corrected to match.checkjob (needs a virtual display), a separate workflow, or local-onlypnpm test:e2e.None of those block Part 1 or Part 2a, so those PRs can land first while the driver/CI questions are decided.
Open-CoDesign Bot