tools/ui/build.sh and serve.sh still cd'd into the app/ directory the flattening removed, so make test-e2e / ui-dev / ui-smoke died at the first line. The staged Gitea workflow had the same stale cd in every job, plus a coverage gate with no coverage run before it — it now goes through the make targets (tooling discipline: the make layer owns env setup) with make test-coverage feeding make coverage-gate. Fixing the paths exposed the real break: flutter build web --wasm cannot compile the tree since the dart:ffi pivot (tree-sitter, native PTY) — dart:ffi does not exist on the wasm target. Fence vs park vs drop is filed as Q-50; the workflow's e2e job is withheld with a pointer there, and T-384 sits in review until Q-50 resolves. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
clide UI harness
A Playwright-based driver that drives the Flutter WASM build of clide through its semantic DOM tree (the same tree screen readers use). Used by Claude Code to "actually use the app" without screenshot round-trips, and by CI to catch web-regression bugs.
One-time setup
cd tools/ui
npm install
npx playwright install chromium
Dev loop
# From repo root:
make ui-dev # build web + start local server :4280
cd tools/ui && npx playwright test
# When done:
make ui-stop # kill the local server
Or the one-shot smoke:
make ui-smoke # build + serve + run smoke + stop
Driver surface
import { ClideDriver } from '../driver';
test('...', async ({ page }) => {
const clide = new ClideDriver(page);
await clide.goto('/');
await clide.click('Open project');
await clide.type('Name', 'My project');
const text = await clide.readText('disconnected');
await clide.screenshot('out/my-state.png');
const tree = await clide.dumpSemanticsTree();
});
All lookups go through Flutter's semantics tree (flt-semantics[aria-label]).
This only works because every interactive widget in clide emits a
Semantics(label:, hint:, button:) wrapper — a requirement that's baked
in for screen-reader support and gets enforced by
test/a11y/semantic_coverage_test.dart.
CI
make ui-smoke is the CI entry — builds the WASM bundle, runs the
harness, cleans up. Enabling Gitea Actions will start running this on
every push.