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

3.4 KiB
Raw Permalink Blame History

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
    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:
    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.