Files
Tudor Sitaru 1ca957499a
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 12s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 47s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
feat(mobile): promote filter toggle when active + document 360px baseline
MOB-08: Search list pagination is already implemented (page_size 50,
"Load more schools" button + count) — no change needed; ticket closed.

MOB-10: Rather than build a full bottom-sheet filter modal (large
change, modal/focus-trap/scroll-lock infra), promote the existing
"Advanced" toggle to a coral pill labeled "Filters (n)" whenever
dropdown filters are applied. Users now see at a glance that the list
is being narrowed; the inline accordion remains the disclosure
mechanism. Adds aria-expanded for screen readers.

MOB-23: Add MOBILE.md at the repo root with the 360 px design baseline,
acceptance checks for any UI PR (no horizontal overflow, ≥44px tap
targets, no <11px visible text, iOS Chrome parity, safe-area-inset,
dvh), and the established component patterns. Playwright regression
test deferred — adding the dep for one test is heavier than the
current value warrants; documented as a future option.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 09:52:17 +01:00

72 lines
3.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Mobile design baseline
Mobile (≥55% of traffic) is the primary target for this app. Any new
screen or component must be designed at the **360 px** viewport first
and verified at three reference widths before merge.
## Reference viewports
| Width | Device class | Purpose |
| --- | --- | --- |
| 360 px | Low-end Android (Samsung A-series, older Pixels) | Hard floor — if it doesn't fit here it isn't shipping |
| 390 px | iPhone 14 / 15 / 16 (38% of mobile traffic) | Primary iOS target |
| 430 px | iPhone 16 Pro Max, large Android | Upper mobile bound |
## Acceptance checks for any screen change
Before raising a PR that touches user-visible UI, confirm at each
reference width:
1. **No horizontal overflow.** `document.documentElement.scrollWidth ===
window.innerWidth`. The most reliable check: in DevTools console run
```js
document.documentElement.scrollWidth - innerWidth
```
It must read `0`. Any positive number means something is bleeding
past the right edge — usually a fixed-width element, an inline-block
that didn't wrap, or a flex row missing `flex-wrap: wrap`.
2. **Tap targets ≥ 44 × 44 px** on every interactive element (iOS Human
Interface Guidelines minimum). Probe with:
```js
Array.from(document.querySelectorAll('a, button, [role=button], input, select'))
.filter(el => el.offsetParent)
.map(el => ({ t: el.innerText?.trim().slice(0,30), r: el.getBoundingClientRect() }))
.filter(o => o.r.width < 44 || o.r.height < 44)
```
3. **No text below 11 px** in any visible-by-default block. Decorative
demo content (illustrations, mocked previews) should either scale up
or be hidden under the `640 px` breakpoint — see `MOB-04` for the
pattern used on the home page's "What you'll see" section.
4. **iOS Chrome bottom-bar parity.** The fixed `Navigation` bottom tab
bar already compensates for the auto-hiding URL bar via the Visual
Viewport API (`Navigation.tsx`). New fixed-bottom elements must
either use the same offset (read `var(--mobile-bar-offset)`) or sit
inside the existing tab-bar container.
5. **Safe-area insets** on any new sticky/fixed chrome:
`padding-bottom: env(safe-area-inset-bottom)` for bottom-pinned UI,
`padding-inline: env(safe-area-inset-left/right)` for header-class
chrome that runs full bleed.
6. **`dvh`, not `vh`.** iOS Safari's collapsing toolbar makes raw `vh`
units jump. Prefer `100dvh` (with a `100vh` fallback if you support
older engines) for any height that needs to track the visible
viewport.
## Component patterns
- **Hide-on-mobile decoration:** wrap with `@media (max-width: 640px) {
.x { display: none; } }` — examples in `HomeView.module.css`
(`.hiwVisual`), `MetricTooltip.module.css` (`.wrapper`).
- **Right-edge scroll-fade for horizontal scrollers:**
`mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);`
Drop the fade when scrolled to the end with a JS-toggled class — see
`SchoolDetailView.tsx`'s `sectionNavAtEnd` state for the pattern.
## Automation (future)
A Playwright regression test that asserts `docW === vw` at the three
reference widths on `/`, `/rankings`, `/admissions`, `/compare`, and a
representative `/school/:urn` page would catch overflow regressions
immediately. Not added yet — Playwright isn't currently in the project
dependency set, and the existing Jest setup doesn't compute layout.
Worth adding if mobile overflow regressions recur.