From 0e35735a2ac6c721c8e29f66abf38a6241051b6f Mon Sep 17 00:00:00 2001 From: Thomas Faour Date: Sat, 25 Jul 2026 01:19:44 +0000 Subject: [PATCH] Add mobile-viewport testing to run-server + require it in CLAUDE.md Confirmed the theatre-mode preview dialog actually renders correctly at phone widths (390x844) -- centers properly, backdrop and close button both fine, nothing overflows. Added a `viewport` command to the driver (defaults to mobile, since that's the step easy to skip) and a CLAUDE.md rule to screenshot both breakpoints for future UI changes, since the 860px sidebar/mobile-bar fork is a real, previously-hit source of bugs here. --- CLAUDE.md | 11 +++++++++- server/.claude/skills/run-server/SKILL.md | 24 ++++++++++++++++++++++ server/.claude/skills/run-server/driver.py | 14 +++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 650d664..1b5a83e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -54,7 +54,16 @@ Start here, don't re-derive from scratch: live (mobile viewport CSS collapse, a dialog's status message landing behind its own backdrop, a JSON/form-urlencoded body mismatch). Spin up `uvicorn app.main:app` against a scratch `DATABASE_URL`/`CONFIG_PATH` - sqlite file, don't touch the real deployment's data. + sqlite file, don't touch the real deployment's data. `server/.claude/skills/run-server/` + (`/run-server`) has a driver for exactly this. +- **New/changed UI must work at both desktop and mobile widths -- + screenshot both, don't assume one implies the other.** The layout + genuinely forks at the 860px breakpoint (`theme.css`): the sidebar + goes off-canvas behind a hamburger below it. A dialog, header + control, or new widget that looks right at a wide viewport can + overflow, overlap the mobile bar, or mis-center at phone widths. + `run-server`'s driver has a `viewport` command for exactly this + (defaults to a phone size; switch to `1280 900` for desktop). - **Deploy**: Gitea Actions at `git.thumeit.com/tfaour/espresso_frame` (`.gitea/workflows/server-docker-build.yml`: `test` -> `build-and-push` -> `deploy` on any push to `main` touching `server/**`; `deploy` SSHes diff --git a/server/.claude/skills/run-server/SKILL.md b/server/.claude/skills/run-server/SKILL.md index e4edf27..cb3ea85 100644 --- a/server/.claude/skills/run-server/SKILL.md +++ b/server/.claude/skills/run-server/SKILL.md @@ -89,6 +89,20 @@ Screenshots land in `/tmp/run-server-shots/` (override: `SCREENSHOT_DIR`). **Actually look at them** -- a blank or error-page screenshot is a failure to launch, not success. +**Test every UI change at both a desktop and a mobile viewport.** The +driver defaults to a desktop size (1280x900); switch with `viewport`. +The app's mobile breakpoint is 860px (`theme.css`) -- below that the +sidebar goes off-canvas behind a hamburger (`.mobile-bar`). A page that +looks right at 1280px can still overflow, overlap the mobile bar, or +mis-center a `` at phone widths -- screenshot both: + +```bash +tmux send-keys -t runserver 'viewport 1280 900' Enter # desktop (also the default) +tmux send-keys -t runserver 'screenshot desktop-x' Enter +tmux send-keys -t runserver 'viewport 390 844' Enter # iPhone-ish mobile width +tmux send-keys -t runserver 'screenshot mobile-x' Enter +``` + ### Driver commands | command | what it does | @@ -101,6 +115,7 @@ screenshot is a failure to launch, not success. | `screenshot [name]` | → `/tmp/run-server-shots/.png` | | `eval ` | evaluate JS in the page, prints JSON | | `console-errors` | prints all captured console/page errors as a JSON array | +| `viewport [w] [h]` | resize the viewport, default `390 844` -- use `1280 900` for desktop (see Gotchas re: real touch input) | | `is-open ` | prints `true`/`false` for a `` element's `.open` -- use this instead of `wait-for sel[open]` | | `bootstrap-admin [user] [pass]` | completes first-run `/setup` (defaults `admin`/`testpassword123`); links frame #1 and logs in | | `quit` | closes the browser, exits the driver | @@ -138,6 +153,15 @@ needed beyond the venv. ## Gotchas +- **`viewport` only resizes the window -- it does not emulate touch + input.** `click` still dispatches a mouse click, not a tap; there's + no touch-delay, no `:hover`-stickiness-after-tap, no `hasTouch` + context. It catches real bugs (layout overflow, off-canvas sidebar, + a `` mis-centering at phone widths) but won't catch anything + that's specifically a touch-vs-mouse event difference. Good enough + for CSS/layout verification; not a substitute for testing on an + actual phone if a change touches touch-specific interaction. + - **`chrome-headless-shell` (Playwright's default headless target) crashes on basic calls in this container**, e.g. `page.set_content()` returns `TargetClosedError`, even after every `ldd`-reported missing diff --git a/server/.claude/skills/run-server/driver.py b/server/.claude/skills/run-server/driver.py index 988dcf3..b45ba9d 100644 --- a/server/.claude/skills/run-server/driver.py +++ b/server/.claude/skills/run-server/driver.py @@ -95,6 +95,19 @@ def cmd_console_errors(_arg): print(json.dumps(console_errors)) +def cmd_viewport(arg): + """Resize the viewport. No args -> 390x844 (iPhone-ish mobile + width); the page itself starts at 1280x900 (desktop) on launch, so + `viewport 1280 900` gets back to that. The app's mobile breakpoint + is 860px (see theme.css) -- anything under that exercises the + off-canvas sidebar/mobile-bar layout.""" + parts = arg.split() + width = int(parts[0]) if len(parts) > 0 else 390 + height = int(parts[1]) if len(parts) > 1 else 844 + page.set_viewport_size({"width": width, "height": height}) + print("viewport:", width, "x", height) + + def cmd_is_open(arg): """App-specific: print whether a element is open (true/false).""" print(json.dumps(page.eval_on_selector(arg, "el => el.open"))) @@ -132,6 +145,7 @@ COMMANDS = { "screenshot": cmd_screenshot, "eval": cmd_eval, "console-errors": cmd_console_errors, + "viewport": cmd_viewport, "is-open": cmd_is_open, "bootstrap-admin": cmd_bootstrap_admin, "quit": cmd_quit,