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.
This commit is contained in:
@@ -54,7 +54,16 @@ Start here, don't re-derive from scratch:
|
|||||||
live (mobile viewport CSS collapse, a dialog's status message landing
|
live (mobile viewport CSS collapse, a dialog's status message landing
|
||||||
behind its own backdrop, a JSON/form-urlencoded body mismatch). Spin up
|
behind its own backdrop, a JSON/form-urlencoded body mismatch). Spin up
|
||||||
`uvicorn app.main:app` against a scratch `DATABASE_URL`/`CONFIG_PATH`
|
`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`
|
- **Deploy**: Gitea Actions at `git.thumeit.com/tfaour/espresso_frame`
|
||||||
(`.gitea/workflows/server-docker-build.yml`: `test` -> `build-and-push`
|
(`.gitea/workflows/server-docker-build.yml`: `test` -> `build-and-push`
|
||||||
-> `deploy` on any push to `main` touching `server/**`; `deploy` SSHes
|
-> `deploy` on any push to `main` touching `server/**`; `deploy` SSHes
|
||||||
|
|||||||
@@ -89,6 +89,20 @@ Screenshots land in `/tmp/run-server-shots/` (override:
|
|||||||
`SCREENSHOT_DIR`). **Actually look at them** -- a blank or error-page
|
`SCREENSHOT_DIR`). **Actually look at them** -- a blank or error-page
|
||||||
screenshot is a failure to launch, not success.
|
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 `<dialog>` 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
|
### Driver commands
|
||||||
|
|
||||||
| command | what it does |
|
| command | what it does |
|
||||||
@@ -101,6 +115,7 @@ screenshot is a failure to launch, not success.
|
|||||||
| `screenshot [name]` | → `/tmp/run-server-shots/<name>.png` |
|
| `screenshot [name]` | → `/tmp/run-server-shots/<name>.png` |
|
||||||
| `eval <js>` | evaluate JS in the page, prints JSON |
|
| `eval <js>` | evaluate JS in the page, prints JSON |
|
||||||
| `console-errors` | prints all captured console/page errors as a JSON array |
|
| `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 <dialog-selector>` | prints `true`/`false` for a `<dialog>` element's `.open` -- use this instead of `wait-for sel[open]` |
|
| `is-open <dialog-selector>` | prints `true`/`false` for a `<dialog>` 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 |
|
| `bootstrap-admin [user] [pass]` | completes first-run `/setup` (defaults `admin`/`testpassword123`); links frame #1 and logs in |
|
||||||
| `quit` | closes the browser, exits the driver |
|
| `quit` | closes the browser, exits the driver |
|
||||||
@@ -138,6 +153,15 @@ needed beyond the venv.
|
|||||||
|
|
||||||
## Gotchas
|
## 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 `<dialog>` 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)
|
- **`chrome-headless-shell` (Playwright's default headless target)
|
||||||
crashes on basic calls in this container**, e.g. `page.set_content()`
|
crashes on basic calls in this container**, e.g. `page.set_content()`
|
||||||
returns `TargetClosedError`, even after every `ldd`-reported missing
|
returns `TargetClosedError`, even after every `ldd`-reported missing
|
||||||
|
|||||||
@@ -95,6 +95,19 @@ def cmd_console_errors(_arg):
|
|||||||
print(json.dumps(console_errors))
|
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):
|
def cmd_is_open(arg):
|
||||||
"""App-specific: print whether a <dialog> element is open (true/false)."""
|
"""App-specific: print whether a <dialog> element is open (true/false)."""
|
||||||
print(json.dumps(page.eval_on_selector(arg, "el => el.open")))
|
print(json.dumps(page.eval_on_selector(arg, "el => el.open")))
|
||||||
@@ -132,6 +145,7 @@ COMMANDS = {
|
|||||||
"screenshot": cmd_screenshot,
|
"screenshot": cmd_screenshot,
|
||||||
"eval": cmd_eval,
|
"eval": cmd_eval,
|
||||||
"console-errors": cmd_console_errors,
|
"console-errors": cmd_console_errors,
|
||||||
|
"viewport": cmd_viewport,
|
||||||
"is-open": cmd_is_open,
|
"is-open": cmd_is_open,
|
||||||
"bootstrap-admin": cmd_bootstrap_admin,
|
"bootstrap-admin": cmd_bootstrap_admin,
|
||||||
"quit": cmd_quit,
|
"quit": cmd_quit,
|
||||||
|
|||||||
Reference in New Issue
Block a user