koala73 / koala73/worldmonitor
ux(mobile): mobile experience overhaul — feed-first IA, tab bar, auth entry, back-gesture, pro merchandising (full review 2026-07-11)
- Dominant language
- TypeScript
- Stars
- 86.6k
- Forks
- 13.1k
- Avg merge
- 8h 4m
- Merged PRs (30d)
- 825
Description
## Summary
Full mobile UX review (2026-07-11): live walkthrough of production at Pixel-8 viewport (390×844×3, touch UA, portrait+landscape, anon + returning profiles) + parallel code audit of the mobile architecture and CSS. 60% of visitors are mobile; the current experience is the desktop IA squeezed into one column. Verdict: the building blocks are good (gestures, bottom sheets, chip nav, PWA plumbing) but the architecture is desktop-first and a layer of fit-and-finish is missing.
**Key measurements (production, 2026-07-11):**
- First viewport = 100% chrome + map, **zero content items**; first panel at ~961px (1.15 screens down)
- **253px of every screen is permanent chrome** (116 top + **137px always-visible footer** — `` is a sibling of the `.main-content` scroll container, `panel-layout.ts:902`)
- 43 panels in a single 12,679px column; eager-mount budget 3 → skeleton pop-in
- **99/177 countries render <24px** at default zoom; tap at the center of the largest visible country lands on a military marker (655 overlay nodes intercept)
- 150 sub-44px tap targets in one screenful (burger 28px, chips 32px, follow 22px, share 24px, posture-refresh 8×16px)
- Every text input <16px → iOS zoom-on-focus (search 14/15px, auth 14px, settings 13px, watchlist 11px)
- INP p75 799ms / 30% good (tracked: #5080, #5165, #5158 — not duplicated here)
## Critical findings
1. **No sign-in entry point on mobile** — `#authWidgetMount` lives in `.header-right` (`display:none !important` ≤768px, main.css:13252) and the burger menu has no auth row (panel-layout.ts:792-849). Only path to auth = locked-panel CTA. Direct conversion leak.
2. **No back-button/history integration for any overlay** — zero pushState/popstate for menu/sheets/settings/deep-dive; Android back exits the site from any depth. Overlays stack physically (menu over deep-dive over feed).
3. **"Map: Iran" search command is invisible on mobile** — map flies off-screen above the scroll position; command path never calls `revealMobileMap()` (only caller is the critical banner, panel-layout.ts:1248).
4. **Landscape phone = desktop layout** — all mobile accommodation is `max-width:768px`; JS mobile state (renderer/budgets) frozen at boot (utils/index.ts:157, MapContainer.ts:217). No orientation queries repo-wide.
5. **Pro merchandising empty** — ⚡PRO chip filters to blank lock boxes (no preview/value prop, dismissible ✕); deep-dive premium blur runs ~2 screens with the CTA below the fold; "Sign In to Unlock" vs "Upgrade to Pro" copy never distinguishes free account from paid.
6. **Content bugs in flagship surfaces**: raw i18n keys `POPUPS.MILITARYFLIGHT.MANUFACTURER`/`.OWNER` rendered in map popup (keys missing from en.json; MapPopup.ts:2706-2707); literal "Fallback" token in deep-dive brief (CountryDeepDivePanel.ts:2355); feed items render title twice with literal ` ` (decodeEntities exists at CountryDeepDivePanel.ts:3072 but isn't shared); `U:0 C:0 S:55 I:0` initialisms don't match their own detail labels (S = "Mil. Activity"); `CACHED · 22/35 sources` plumbing badges as primary UI.
## Proposed direction (full review artifact has mocks + 13 annotated screenshots)
Mobile ≠ shrunken desktop wall. Reframe as: **"what changed since I last looked?"** — feed-first, map as a destination.
### P0 — structural
- [ ] **Bottom tab bar** (Today · Map · Search · Alerts · More): replaces permanent footer + FAB + half the burger; map becomes a full-screen tab (reuse `live-news-fullscreen` mode); footer links move into More
- [ ] **"Today" brief as mobile home**: 3-5 ranked items (breaking, posture changes, movers, followed countries) from the existing brief pipeline; interim: default map collapsed for first-time mobile visitors
- [ ] **History-integrated sheet manager**: pushState on overlay open, popstate closes topmost — one shared utility for menu/search/region/popup/settings/deep-dive
- [ ] **Sign-in/account row in mobile menu** (shippable this week; reuse `ctx.authModal`)
- [ ] Land tracked perf: #5080 (containment, post-soak), #5165 (chunk wave), #5158 (search sheet)
### P1 — high-impact fixes in current IA
- [ ] Map tap disambiguation (candidate-radius chooser sheet) + zoom-aware marker density caps; country hit-priority when no direct marker hit
- [ ] Pro merchandising: blurred real content under locks, CTA overlaid at top of gate, ANONYMOUS vs FREE_TIER copy split, PRO-chip showcase card
- [ ] Search mobile-native: input top-anchored, recent/followed/trending suggestions (not alphabetical), plain-language rows, `revealMobileMap()` on nav commands, verify touch-tap execution
- [ ] Content hygiene batch: add missing popup locale keys + guard test; remove "Fallback" token; dedupe title≡description + decode entities in feeds; humanize CACHED badges; drop U/C/S/I on mobile rows
- [ ] Ergonomics sweep: 16px inputs on mobile; 44px floor (burger, closes across ~9 modal families, chips, follow/share, time-btns); dvh on deep-dive/settings (country-deep-dive.css:6); settings modal → full-screen sheet; real scroll-lock (body is already overflow:hidden — lock `.main-content`); unify 767→768 breakpoint (country-deep-dive.css:1196); consistent tap-highlight
- [ ] Landscape/boot coherence: short-viewport media queries; re-evaluate width-derived state on orientationchange (SearchModal/MapPopup already re-read per open — extend pattern)
- [ ] Delete dead mobile code: `MobileWarningModal` (never instantiated) + `#mobileSearchBtn` (display:none in every rule)
### P2 — growth & polish
- [ ] First-run onboarding (mission-preset picker is desktop-only today, event-handlers.ts:793) + deferred A2HS install prompt (`beforeinstallprompt` — currently unhandled)
- [ ] Offline last-good data (workbox: `/api/*` is NetworkOnly today) — coordinate with #5191
- [ ] Tablet 769-1024 split view · pull-to-refresh on feed · feed virtualization · mobile 3D globe camera framing (opens pointed at empty space)
## Method / provenance
Two Explore agents audited src mobile architecture + CSS (all file:line cites verified against worktree at review time); live probes measured DOM (scroll containers, rects, elementsFromPoint). Perf numbers from #5080/#5165/#5158/#4487 + CrUX, not re-measured. Full narrative review with 13 annotated screenshots + before/after IA mocks: internal artifact (ask Elie).
Related: #5080 #5165 #5158 #4487 #5191 · supersedes scattered TODO-065/067 mobile items in docs/Docs_To_Review/todo.md
Contributor guide
Research direction
Treat this as a broad review rather than a single first contribution, and choose one P0/P1 slice before starting. For the auth-entry slice, read panel-layout.ts:792-849 and main.css:13252, then verify the mobile menu at the stated viewport; done means a mobile sign-in row opens the existing auth flow without relying on a locked-panel CTA.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- design, frontend, mobile
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100