---
name: mobile-ui-validation
description: Reviews and fixes web app and PWA interfaces on phone, tablet and desktop — responsiveness and breakpoints, pages that scroll sideways, 1px seams between bars, navbar/bottom tab/sticky/safe area/notch, scroll bounce (overscroll), on-screen keyboard covering inputs, small touch targets, enter and exit animations, and unbalanced proportion or visual hierarchy. Discovers the project's real screens and breakpoints, helps choose the scope and spreads broad reviews across subagents. Use whenever the user asks to validate, test, review or adjust how screens look or behave on mobile or at different widths, or describes symptoms such as "it broke on mobile", "there's a line between the bars", "the screen wobbles", "it looks out of proportion", "the bar hides and doesn't come back" — in any language, even without naming the skill. Not a general frontend audit (performance, SEO, architecture).
---

# Interface and responsive validation

Diagnose before compensating visually. A 1px seam can be a layout error or a browser paint artifact; a single screenshot cannot tell them apart. Respect the scope: if the request is only to validate, deliver findings; if it includes fixing, implement and verify.

## Workflow at a glance

1. **Discover** (read-only): project instructions, routes, navigation, local URL and real breakpoints → `scripts/discover_breakpoints.py`.
2. **Choose the scope** with the user, asking only for what's missing (screens, ranges, review or fix).
3. **Distribute** when there are two or more independent screens and subagents are available → `references/parallel-review.md`.
4. **Measure and reproduce** at each viewport → `scripts/ui-probe.js` + visual inspection.
5. **Diagnose by symptom** using the triage table and the matching reference.
6. **Fix** (if authorized) the cause, not the symptom; compare before/after in the same scenario.
7. **Deliver** in the format at the end of this file, stating what was and wasn't covered.

## Bundled tools

**`scripts/discover_breakpoints.py <root> [--json]`** — inventory of `@media`, `@container`, `matchMedia`, Tailwind screens (v3 and v4) and how often each responsive prefix is used. It reports configured limits with no real usage (they change no layout), the same limit written as both `max-width: 767px` and `min-width: 768px`, and Tailwind prefixes used but missing from the config (classes with no effect). It's a starting point: hooks such as `useMediaQuery` and library themes (MUI, Chakra, Bootstrap via Sass) may need manual reading.

**`scripts/ui-probe.js`** — inject it into the page (Playwright `page.addScriptTag({ path })`, `page.evaluate`, or paste it into the console) and use `window.uiProbe`:

| Call | What for |
|---|---|
| `report()` | everything below at once; a good first step per viewport |
| `env()` | viewport, DPR, safe areas, `display-mode` (PWA), reduced motion, whether the layout viewport expanded |
| `scrollContainers()` | what actually scrolls, and the effective `overscroll-behavior-y` of each |
| `horizontalOverflow()` | elements that make the page scroll sideways (deepest only) |
| `seams()` | gaps/overlaps between adjacent fixed/sticky bars and the viewport edges |
| `await scrollSample({ container, steps })` | `seams()` at the top, intermediate positions, the end and back to the middle |
| `smallTargets(24)` / `smallInputs()` | small touch targets; inputs with font < 16px (zoom on iOS) |

The probes measure geometry; they don't prove what was painted, nor reproduce momentum or rubber-banding. If `layoutViewportExpanded` is `true`, something overflows sideways and the mobile browser has "zoomed out" the page: fix that before trusting any other measurement. Without a controllable browser, use the same functions as a manual inspection checklist.

## Guided start and choosing the scope

An invocation without context (for example, just the skill name) starts with discovery, then settles the scope. It never ends on discovery plus questions alone: the turn must either ask through an interactive question tool or proceed with the defaults below. Reuse context: don't ask again for a screen, URL, widths or authorization already given. A follow-up request such as "fix it" keeps the scope of the previous review.

Before asking, do a brief read-only inspection: project instructions, routes, navigation and responsive styles. Find the local URL when available; don't assume a port. Run the breakpoint inventory and separate configured limits from those that actually change the layout; don't assume Tailwind defaults or treat 375 and 390 px as universal choices.

Present a short summary of the screens and ranges found, with understandable names and real values. Without access to the code, describe what you observed and ask for the project or URL; don't invent an inventory.

Ask only for what's missing, in one short round, **only when an interactive question tool is available** (e.g. `AskUserQuestion`, `request_user_input`) and it can be used in the current mode. Don't ask in plain chat and stop: that leaves the user with a dead end. Without such a tool, skip to the defaults below.

- **Screens:** the whole app or specific screens? Offer the discovered screens by name, without requiring the user to know routes. An explicit URL already defines the screen, unless a broader request was made.
- **Ranges:** all those found, or some? Show the real options (for example mobile < 768, tablet 768–1023, desktop ≥ 1024) only when that split matches the project. Accept a specific width or device.
- **Action:** only review and list issues (recommended without context), or also fix and verify? Don't ask again if the intent is already clear.

While an interactive question is pending, continue only with independent discovery. Don't read silence as "whole app".

**Defaults (no question tool, non-interactive run, or the person says "you decide"):** don't wait. Adopt review-only mode, one screen per distinct layout (home plus one sample of each other route type), and one viewport per discovered range plus the widths on both sides of each limit that changes the reviewed components. State these choices in the first lines of the delivery and offer "fix it" as the follow-up. Never fix files under the defaults unless the request said so.

After the answers, summarize the scope in one sentence and start without a second confirmation. Turn ranges into concrete viewports: one representative width per range, plus widths immediately below and above each limit that changes the reviewed components (e.g. 767 and 768). Record the height too — 360×640 and 390×844 behave differently for bars and the keyboard. Apply safe areas and touch only to relevant environments; include keyboard (navigation) in desktop ranges.

"Whole app" covers the distinct screens and layouts, with relevant states (empty, filled, loading, error, overlays). Parameterized routes sharing a layout get samples with short and long content; declare the sampling. Don't reduce "all responsive sizes" to two mobile widths, and don't claim full coverage with inaccessible screens or environments.

## Distributing by screen and subagents

With two or more independent screens and subagents available and allowed, delegate by screen; review a simple screen yourself. Size by the number of distinct screens/layouts — viewports and states don't multiply agents. Before delegating, read `references/parallel-review.md` (capacity, queue, task contract, shared files, browser isolation). Without subagents, run the same coverage sequentially.

## Evidence and reproduction

- Identify the container that actually scrolls (`scrollContainers()`), the bars involved, their ancestors and visibility states.
- Record route, browser and version, viewport (width × height), zoom, DPR and browser/PWA mode. Distinguish a physical device, a simulator and viewport emulation — emulated Chromium doesn't validate iOS Safari.
- Reproduce before editing. Compare before/after in the same scenario with a screenshot and a measurement. DOM tests, TypeScript or a static screenshot don't prove an animation, nor the absence of a seam while scrolling.
- Without access to the affected environment, proceed with inspection and whatever checks are possible, but present the cause as a hypothesis and the visual validation as pending. Don't invent measurements.

## Triage by symptom

| Reported or measured symptom | Frequent causes (verify, don't assume) | Read |
|---|---|---|
| Page scrolls sideways, layout "zoomed out" on mobile | element with `width: 100vw` + padding/scrollbar, fixed width, image/table/code without `max-width`, long word/URL that doesn't wrap, `translate`/negative margin, grid with a fixed minimum column | `references/responsive.md` |
| 1px line between bars or at the bottom | fractional or duplicated offset, safe area counted twice, border/`box-sizing`, `top` with a differently rounded height, paint rounding at fractional DPR | `references/bars-and-scrolling.md` |
| Sticky doesn't stick / sticks in the wrong place | ancestor with `overflow` other than `visible`, short containing block, missing `top`, ancestor with `transform` (for fixed) | `references/bars-and-scrolling.md` |
| Page bounces, shows background outside the app, sheet drags the page behind | `overscroll-behavior` on the wrong element, missing `contain` on the inner container, scrolling on `body` instead of the expected container | `references/bars-and-scrolling.md` |
| Bar hides and doesn't come back, or flickers | direction logic without tolerance, overscroll flipping the state, end of page not handled | `references/bars-and-scrolling.md` |
| Keyboard covers the input, bottom bar rises with it, height jumps | `100vh` instead of `dvh`/`svh`, fixed bar anchored to the layout viewport, `interactive-widget` | `references/responsive.md` |
| Zoom when tapping an input (iOS) | input `font-size` < 16px | `references/responsive.md` |
| Element disappears without animation, or appears and disappears again | immediate unmount, `display: none` cutting the exit, delayed callback after reopening | `references/animations.md` |
| Screen looks empty, out of proportion, bar dominates the content | exaggerated minimum heights, duplicated spacing, flex area pushing groups apart, inconsistent type scale | `references/composition.md` |

Read only the references relevant to the request; a broad review ("whole app, all ranges") usually touches all of them.

## Default preferences for this workflow

They apply unless the project or the user says otherwise; state when you apply them.

- Avoid vertical bounce and exposing content outside the app: `overscroll-behavior-y: none` on the effective scroll container (details and exceptions in `bars-and-scrolling.md`).
- A bottom tab bar that hides when scrolling down must reappear when scrolling up, at the top and on reaching the end, tolerating fractional positions and without flickering.
- Transient elements (compact titles, bars, menus, sheets, overlays) enter and leave with a subtle animation, respecting reduced motion.

## Fixing without creating new problems

- Fix the responsible rule or container, using the existing tokens and visual language, instead of scattering per-component compensations.
- Don't reflexively apply `top: -1px`, `translateZ(0)`, `will-change`, `overflow-x: hidden` on the body, `touch-action: none` or `user-scalable=no`. Each hides symptoms and creates others (clipped content, broken sticky, zoom blocked for people who need it).
- A localized workaround is acceptable when the geometry is correct and the evidence points to a paint failure; call it a workaround, not a root-cause fix, and document the symptom, evidence and environment.
- After touching a shared component (shell, navigation, tokens), revalidate the other screens and limits that use it.

## Verification

Cover the scenarios relevant to the defect, without an indiscriminate giant matrix: top, middle and end; slow and fast scrolling; short and long content; show, hide and revert; widths on both sides of the limit; safe area; affected zoom and DPR; reduced motion. Include the on-screen keyboard, orientation, dark theme and route changes with preserved scroll when they affect the changed components.

Use behavior tests for relevant logic (reappearing at the end, cancelling an exit on reopen). Run the project checks appropriate to the change (lint, types, tests). Confirm the visuals in motion in the available environment and record what was left uncovered.

## Delivery

Use this structure, sizing it to the scope (one screen with one defect fits in a few lines):

```markdown
## Scope and environment
Screens, viewports (W×H), browser/version, emulation or device, browser/PWA mode. Declared sampling.

## Findings
For each, in priority order:
**[High|Medium|Low] Short title** — screen(s), viewport(s)
- Symptom and how to reproduce
- Cause: confirmed (evidence: measurement, screenshot, CSS rule at file:line) | hypothesis (what's missing to confirm)
- Fix applied or proposed, and why it addresses the cause

## Verification
What was checked after the change, where, and which project checks ran.

## Coverage and open items
Screen × range × state matrix with: verified | code inspection only | blocked (reason).
What's missing (environment, access, device) to finish.
```

Priority: **High** prevents use or hides content/actions (sideways overflow, input covered by the keyboard, bar that doesn't come back); **Medium** is a visible defect that doesn't block (seam, bounce, exit without animation); **Low** is a composition refinement or aesthetic preference — flag it as such. Don't turn a hypothesis into a diagnosis.

## Technical sources

Consult them when unsure about behavior or compatibility; recheck the current status of bugs before blaming the browser.

- [CSS Positioned Layout](https://www.w3.org/TR/css-position-3/): sticky and fixed.
- [WebKit: safe areas](https://webkit.org/blog/7929/designing-websites-for-iphone-x/).
- [CSS Overscroll Behavior](https://drafts.csswg.org/css-overscroll/) and [MDN: overscroll-behavior-y](https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior-y).
- [MDN: viewport units](https://developer.mozilla.org/en-US/docs/Web/CSS/length#relative_length_units_based_on_viewport) (`dvh`, `svh`, `lvh`) and [MDN: VirtualKeyboard / interactive-widget](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name/viewport).
- [MDN: prefers-reduced-motion](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion).
- [WCAG 2.5.8 Target Size (Minimum)](https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html).
- [Chromium 436536717](https://issues.chromium.org/issues/436536717): a documented case of a rounding seam; it doesn't prove the cause of a new case nor prescribe a 1px overlap.
