GENERATED VIEW · PRIVATE ARCHIVE · DO NOT SERVE
RobCo Industries · Archive Museum

MOBILE_AUDIT.md



RELEASE

planning/2.8.0/audits/MOBILE_AUDIT.md

sha256 0202e1ffb8d3c21b · 31103 bytes · original held in the private archive

# MOBILE_AUDIT.md — RobCo U.O.S. v2.6.0 > **FILED UNDER 2.8.0 — PRODUCED-BY, NOT MEASURED-VERSION.** This document *measures* the earlier v2.6.0/v2.7.0 > codebase, but it was *produced* as a stage of the PARKED-CHAIN audit sequence — the analysis phase that became > the 2.8.0 overhaul. Planning artifacts are filed by the release whose work produced them, never by the version > they discuss. The version in the title is the code it audited, not the folder it belongs in. > Stage A4 of the PARKED-CHAIN audit sequence. PLAN-ONLY. Nothing here is implemented or committed. > Companion docs: HOUSE_STANDARD.md (A0), DIEGETIC_AUDIT.md (A0), PERFORMANCE_AUDIT.md (A1), > ACCESSIBILITY_AUDIT.md (A2), CONTENT_AUDIT.md (A3). > Target: bc8c2eb (v2.6.0-r1, 1078 tests). Widths audited: 360px / 412px / ≥1000px. --- ## Architecture Reference ### Breakpoints (3 total) | Query | Range | Behaviour | |---|---|---| | `(min-width: 1000px)` | Desktop | Two-column fixed layout (`380px + 1fr`), `overflow: hidden` on body, `flex-grow` chat panel | | `(max-width: 999px)` | All mobile/tablet | Single-column `minmax(0, 1fr)` grid; tab bar becomes `position: sticky; top: 0`; col-left/col-right get `overflow-x: clip` | | `(max-width: 480px)` | Narrow phones | Auto-zoom fix (`font-size: 16px !important`); flex-wrap on `.input-group`; number input `max-width: 56px`; limb button shrink; faction grid 3→2 columns; header/boot text shrink | **Gap:** 481–999px range (tablets, landscape-mode phones) receives the single-column layout but does NOT receive the narrow-phone fixes (no 16px font guard, no input-group flex-wrap, no max-width cap on number inputs). ### "Stretch on Refresh" — Root Cause The specific bug class the owner has seen: iOS/Android zoom any `<input>` or `<select>` whose `font-size` is below 16px when focused, and **persist the zoom across reloads**. A fresh navigation resets to scale 1.0 so the first load looks fine; the zoom kicks in on first interaction and sticks. This was the r5 fix (v2.5.0-r5). **Current status:** Fixed at ≤480px by `font-size: 16px !important` on all form elements. Applies to all typical phone viewport widths (360–430px CSS). The gap is landscape orientation (see M-H1 below). ### Panel inventory (full list) | Panel | Tab | Opens by default | |---|---|---| | BIO-METRICS (SPECIAL + HP/LVL/KARMA/LOCATION/DATE/TIME) | stat | details closed | | BIO-SCAN & LIMB STATUS | stat | details closed | | SKILL MATRIX | stat | details closed | | SKILL BOOKS | stat | details closed | | SKILL MAGAZINES (FNV only) | stat | details closed | | PERKS (+ TRAITS sub-panel, FNV) | stat | details closed | | STATUS EFFECTS | stat | details closed | | FACTION STANDING | stat | details closed | | KARMA CENTER (FO3 only) | stat | details closed | | BACKPACK INVENTORY (+ AMMO RESERVES sub-panel) | inv | details closed | | COLLECTIBLES (+ LINCOLN MEMORABILIA sub-panel) | inv | details closed | | CRAFTING (+ SCRAP/BREAKDOWN sub-panel) | inv | details closed | | SQUAD STATUS | inv | details closed | | QUEST LOG | data | details closed | | CAMPAIGN NOTES | data | details closed | | SESSION STATISTICS | data | details closed | | WORLD MAP | data | details closed | | ACCOUNT | data | details closed | | CAMPAIGN CONFIGURATION (CAMPG) | campg | details OPEN | | SECURITY & CONFIGURATION | (always visible) | details closed | --- ## Coordinate references to other audit docs - **A2 §A-S2**: Tracker toggles are `<span onclick>` not `<button>` — keyboard inaccessibility. Overlaps with touch ergonomics here (non-button elements may have 0px declared height, relying on text line-height only for tap target). - **A2 §A-2**: Limb buttons: no `aria-pressed`; also below WCAG 44px tap target (35×35px). Cross-referenced in M-L3 below. - **A2 §A-5**: Faction F+/F−/I+/I− buttons use `title` not `aria-label`. Also referenced in M-L4 below. - **A1 §B-P1**: `renderWorldMap()` called unconditionally on every AI response. At mobile, the map rerenders a 6×6 CSS grid on every response. Performance concern separate from layout. - **A0 §CR-1**: Zero `prefers-reduced-motion` — all 22 `@keyframes` including `flicker`, `flicker-medium`, `rad-shift`, `weight-sag/buckle` run unconditionally. The shear animations (`weight-*`, `rad-*`) are neutralized at ≤480px (line 1873–1880) but not at 481–999px. Cross-referenced in M-H1. --- ## FINDINGS --- ### M-H1 · [LAYOUT / AUTO-ZOOM] Auto-zoom guard is absent for landscape browser at 481–999px **Widths affected:** 481–999px (all landscape phone orientations; ~780px CSS on a 360px portrait phone) **Root cause:** The `font-size: 16px !important` auto-zoom guard and the number-input `max-width: 56px` cap are inside `@media (max-width: 480px)`. When a phone rotates to landscape, its CSS viewport width crosses 480px (360px phone → 780px landscape). The guard no longer applies. Any input with font-size < 16px triggers iOS/Android focus-zoom and the zoom persists on reload ("stretch on refresh"). **Inputs affected in landscape:** `.special-grid input` (`font-size: 14px` → triggers zoom), `.skills-grid input` (`font-size: 12px` → triggers zoom), `.skills-grid label` is not an input. All number inputs remain at 65px width without the `max-width: 56px` cap → HP/LVL/CAPS number inputs may balloon to UA default (~170px) causing horizontal overflow in the BIO-METRICS input groups. **The weight/rad shear animations** (neutralized at ≤480px via `animation: none !important`) are NOT neutralized at 481–999px. In landscape, `body.weight-over .col-left { animation: weight-buckle 0.7s infinite }` would apply skewX/translateY to the entire left column. **Note:** The manifest locks the PWA to `"orientation": "portrait"`, so landscape is a browser-tab-only concern. But browser-tab use is documented as supported. **Fix:** Extend the auto-zoom font-size guard and the number-input overflow mitigations to `@media (max-width: 999px)` (the full mobile breakpoint), not just ≤480px. Retain the ≤480px-specific layout overrides (flex-wrap on `.input-group`, limb button sizing) at ≤480px. The font-size + max-width rules can safely widen to ≤999px. **360/412/desktop check:** - 360px: covered by existing ≤480px rule ✓ - 412px: covered ✓ - 780px landscape (360px phone): NOT covered ✗ — zoom-and-persist bug active - Desktop: unaffected (layout is fixed-column, inputs are not tapped on desktop) **Verdict:** BUILD-NOW · `[CACHE]` · **Severity: HIGH** **Files:** `css/terminal.css` --- ### M-H2 · [LAYOUT / WRAP] Export/Import/Restore button row has no flex-wrap — "RESTORE BACKUP" wraps at 360/412px **Location:** `index.html:1634` **Markup:** ```html <div style="display: flex; gap: 10px; margin-top: 10px"> <button ...>EXPORT SAVE</button> <button ...>IMPORT SAVE</button> <button ...>RESTORE BACKUP</button> <!-- 14 chars at 11px font --> </div> ``` **Analysis at 360px:** Panel effective content width ≈ 310px. Three `width: 100%` buttons in a no-wrap flex container shrink equally to ≈ (310 − 20px gaps) / 3 ≈ 97px each. Global `button { padding: 12px }` → content area ≈ 73px. "RESTORE BACKUP" at 11px monospace ≈ 84px > 73px → text wraps to two lines, making the third button taller than the other two. Inconsistent, visually broken row. **Also affected:** The three-button EXPORT .MD / EXPORT .HTML row (line 1549) uses `flex: 1` on each button and `flex-wrap` is not set. "EXPORT .HTML" at 11px monospace = ~70px content, fits marginally. "EXPORT .MD" fits fine. This row is borderline OK but still lacks `flex-wrap` as a safety net. **Fix:** Add `flex-wrap: wrap` to both button-row containers. With `flex-wrap: wrap` and `width: 100%` on each button, they become full-width stacked buttons on small screens (each button takes its own line). OR reduce "RESTORE BACKUP" to "RESTORE" (6 chars = 42px, fits at 73px). OR switch to `btn-sm` style so buttons take natural width and pack together. **360/412/desktop check:** - 360px: wraps ✗ (2-line third button) - 412px: wraps ✗ (marginally, ~110px per button; "RESTORE BACKUP" ≈ 84px content + 24px padding = 108px → wraps in 110px button) - Desktop: fine (380px fixed column, buttons at ~120px each) **Verdict:** BUILD-NOW · `[CACHE]` · **Severity: HIGH** **Files:** `index.html` --- ### M-M1 · [MODAL / OVERFLOW] `.modal-content` missing `overflow-wrap: anywhere` — long unbroken content overflows modal at 360px **Location:** `css/terminal.css:593` ```css .modal-content { white-space: pre-wrap; /* ← wraps at newlines and spaces */ overflow-y: auto; flex-grow: 1; } ``` **Problem:** `white-space: pre-wrap` respects whitespace but does NOT break unbroken tokens (a single word with no spaces or newlines). Unbroken tokens longer than the modal content width can cause horizontal overflow. Affected content scenarios: - Base64 save data in error messages - Long location names without spaces ("NV-OVERLORD-TERMINAL-STATUS-CHECK") - Fallout wiki item names with hyphens (not spaces) that may be pasted - Changelog viewer (the full changelog dump shown via `showFullChangelog()`) **At 360px:** Modal box is `min-width: 320px`, `padding: 20px` → content area = 280px. A single token wider than 280px overflows the `.modal-content` flex child. The `.modal-box` has no `overflow-x: hidden`, so the modal box itself can expand wider than 320px and potentially wider than the 360px viewport, forcing horizontal scroll. **Fix:** Add `overflow-wrap: anywhere` to `.modal-content`. Single-line: `overflow-wrap: anywhere;` **360/412/desktop check:** - 360px: unbroken-token overflow possible ✗ - 412px: unbroken-token overflow possible ✗ (smaller risk with 93px wider content area) - Desktop: `max-width: 600px` modal absorbs most tokens ✓ (low risk) **Verdict:** BUILD-NOW · `[CACHE]` · **Severity: MEDIUM** **Files:** `css/terminal.css` --- ### M-M2 · [LAYOUT / INPUT-GROUP] `.input-group select` flex override is missing at 481–999px **Location:** `css/terminal.css:1788` (in the ≤480px block) **Context:** CAMPG panel's `<select style="width: 100%">` elements (Game, Playstyle, Playthrough Type) are in `.input-group` containers. At ≤480px they get `flex: 1 1 auto; width: auto !important` → correctly fill available space. At 481–999px they don't — `width: 100%` on a flex item with `flex: 0 1 auto` (default) competes with the sibling label for space. **Observed behavior at 481–999px:** The GAME select collapses to its `min-content` width (roughly "Fallout 3" width ~68px) while the label "GAME:" takes its own space. `justify-content: space-between` separates them with a gap, but the select doesn't fill the remaining space. On a tablet at 768px, this shows a thin select floating to the right of the label with lots of empty space between. **Note:** Most phones (360–430px CSS) are below 480px and already covered. This gap primarily affects tablets and landscape-mode phones. **Fix:** Move the `.input-group select` width fix up to the `@media (max-width: 999px)` block so it applies at all mobile widths (or add a second rule at ≤999px). **360/412/desktop check:** - 360px: covered by ≤480px rule ✓ - 412px: covered ✓ - 481–999px (tablets): select visually narrow ✗ - Desktop: correct (fixed 380px column, selects have fixed `width: 100%` inline which works in block layout) **Verdict:** BUILD-NOW · `[CACHE]` · **Severity: MEDIUM** **Files:** `css/terminal.css` --- ### M-M3 · [DENSITY / PANEL-FORM] Status effects add-form select inherits narrow base width at ≤480px **Location:** `index.html:833` and `css/terminal.css` **Structure:** ```html <div class="panel-form"> <select id="newStatusName" style="flex: 2; ..."> <!-- full name list --> <input type="number" id="newStatusTicks" style="flex: 1; width: 50px" /> <select id="newStatusType" style="flex: 1; ..."> <!-- BUFF/DEBUFF/NEUTRAL --> <button style="width: auto; padding: 0 10px">ADD</button> </div> ``` The `.panel-form` is NOT `.input-group`. The ≤480px fix for `.input-group select` does NOT apply here. The `newStatusName` select gets `font-size: 16px !important` (from the universal rule), but its width is governed by `flex: 2` and the global `select { width: 95px }` as the flex-basis-equivalent min. At ≤480px with 16px font, `newStatusName` at `flex: 2` in a 310px panel with sibling items: available for the flex:2 item ≈ (310 - 50 - ~40(ADD) - ~12px gaps) × (2/4 flex ratio) ≈ 104px. At 16px, "Stealth Boy Active" (18 chars) overflows. The select clips at its bounds and shows a truncated value; the dropdown shows the full list. This is cosmetic-only. **Fix:** Add `min-width: 0` to `.panel-form select` so flex shrink works correctly, or add a `.panel-form select { flex: 1 1 auto; }` rule. `min-width: 0` alone prevents min-content flooring the size. **Verdict:** SPEC-FIRST (low visual impact; only affects the selected-value display width) · Severity: **LOW-MEDIUM** **Files:** `css/terminal.css` --- ### M-M4 · [PWA] `#updateBanner` CSS is dead code — no matching HTML element **Location:** `css/terminal.css:1883` ```css #updateBanner { position: fixed; top: 0; ... animation: update-banner-pulse 2s infinite; } ``` There is no `id="updateBanner"` element in `index.html`. The `_triggerUpdate()` flow now uses `#updateModal` (Suite 65 replacement). The banner CSS is live (served) dead code — it contributes ~35 lines to the parsed stylesheet with no effect. **Fix:** Remove the `#updateBanner` and `@keyframes update-banner-pulse` rules from `terminal.css`. **Risk:** Verify no JavaScript dynamically creates `id="updateBanner"`. Grepping all JS files returns zero hits. **Verdict:** BUILD-NOW (simple removal) · `[CACHE][TEST]` (Suite test guards the update modal — verify the removed keyframe doesn't break any guard) · **Severity: LOW** **Files:** `css/terminal.css` --- ### M-L1 · [TOUCH] D-pad buttons are 35×35px — below WCAG 44px, above Protocol 17's 28px **Location:** `css/terminal.css:673` ```css .d-pad button { width: 35px; height: 35px; } ``` WCAG 2.5.5 recommends 44×44px minimum touch target; Protocol 17 mandates ≥28px. The D-pad buttons (▲, ◄, ►, ▼) are at 35×35px — technically passing Protocol 17 but failing WCAG. They're small precision targets on a phone screen and are frequently used in gameplay sequences. **Cross-reference:** A2 §A-3 flags the "USE" and "X" delete buttons as anonymous labels; this is the touch-size sibling. Both are noted here for the combined responsive fix pass. **Fix:** Increase to `width: 44px; height: 44px` — brings them into WCAG compliance. **360/412/desktop check:** - 360px: 35px target — small but functional ✗ (WCAG) - 412px: same ✗ - Desktop: mouse use, click-target size less critical; no regression **Verdict:** BUILD-NOW · `[CACHE]` · **Severity: LOW** **Files:** `css/terminal.css` --- ### M-L2 · [TOUCH / DENSITY] Tracker toggle spans are `<span onclick>` — zero declared height, tap target is line-height only **Context from A2 §A-S2:** `renderCollectibles()`, `renderLincolnMemorabilia()`, `renderTraits()`, `renderSkillBooks()`, `renderMagazines()` — all render toggles as `<span onclick>` not `<button>`. The tap target height is exactly the text line-height (~16–18px), well below 28px. **Mobile specifics:** On a 360px phone, tracker rows are compact (see Suite 71 Phase 6 density changes). A `<span>` toggle in a compact row produces a tiny 16px-tall tap target in a dense list. Misfire rate is high on mobile touchscreens. **Fix (layout part):** Switch to `<button class="btn-sm">` so Protocol 17's `min-height: 28px` is applied by CSS. A2 covers the ARIA part (adding `role="checkbox"`, `aria-checked`). **Verdict:** BUILD-NOW (paired with A2 fix) · `[CACHE][TEST]` · **Severity: LOW** (layout) — see A2 for full severity **Files:** `js/ui-render.js` --- ### M-L3 · [TOUCH / DENSITY] Faction F+/F−/I+/I− buttons are 28×28px minimum — compliant but tight at mobile **Location:** `css/terminal.css:1355` ```css .faction-btn { min-width: 28px; min-height: 28px; font-size: 9px; } ``` At ≤480px, the faction grid drops to 2 columns and lone cards span full width (already handled). Each faction card at 2-column = ~(310−6)/2 = ~152px wide. With 4 buttons (F+, F−, I+, I−) at `flex-wrap: wrap; gap: 2px`, each pair fits in 152px. The buttons are exactly at Protocol 17's 28px minimum — functional but tight. **Cross-reference:** A2 §A-5 flags these buttons for using `title` instead of `aria-label`. **Fix (layout):** No layout change needed for 360/412px. For comfort, increase to `min-width: 32px; min-height: 32px` to add margin above the minimum without reaching 44px. **Verdict:** SKIP (Protocol 17 compliant; functional improvement is low-impact) · **Severity: LOW** --- ### M-L4 · [LABEL / WRAP] Karma slider label `width: 65px` with fixed `width: 100px` slider — entire row is 170px in input-group **Location:** `index.html:344`, `css/terminal.css` ```html <div style="display: flex; align-items: center; gap: 5px"> <input type="range" style="width: 100px" /> <span style="width: 65px; text-align: right">Neutral</span> </div> ``` The `input[type='range']` has no width override at ≤480px (the font-size rule applies to text/number/password/select/textarea, not range). The slider stays at 100px. The inner div (slider + label = 170px) is correctly constrained by `.input-group > div { max-width: 100%; flex-wrap: wrap; }` at ≤480px. The slider itself at 100px is narrow but functional on mobile. The range input on iOS renders with a full-width thumb and a reasonable drag target even at 100px width. **360/412/desktop check:** - 360px: slider 100px, accessible ✓ (tight but functional) - 412px: same ✓ - Desktop: fine ✓ **Verdict:** SKIP (functionally OK; cosmetically tight) · **Severity: LOW** --- ### M-L5 · [LABEL / WRAP] Autocomplete panel `left` is not viewport-clamped — future off-screen risk **Location:** `js/ui-saves.js:403` ```js function acPosition(inputEl) { var rect = inputEl.getBoundingClientRect(); panel.style.top = rect.bottom + 2 + 'px'; panel.style.left = rect.left + 'px'; // ← no right-edge clamp var panelW = Math.min(340, Math.max(220, rect.width)); panel.style.width = panelW + 'px'; } ``` The comment says "Clamp to viewport right edge" but only clamps panel width, not panel left. If `rect.left + panelW > window.innerWidth`, the panel extends off-screen to the right. **Current safe status:** Only three inputs are wired (`newQuestName`, `newItemName`, `newPerkName`), all in `.panel-form` containers that span the full panel width. At 360px: left ≈ 24px, panelW ≈ 220–280px → right edge ≈ 244–304px < 360px. Safe for current inputs. **Risk:** Any future `wireInput()` call on a narrow input (e.g., a compact right-side search field) could cause the panel to go off-screen. The safeguard is missing. **Fix:** After setting `panel.style.left`, add a right-edge clamp: ```js var vw = window.innerWidth || document.documentElement.clientWidth; var left = rect.left; if (left + panelW > vw - 4) left = Math.max(0, vw - panelW - 4); panel.style.left = left + 'px'; ``` **Verdict:** SPEC-FIRST (no current bug, defensive fix) · **Severity: LOW** **Files:** `js/ui-saves.js` --- ### M-L6 · [DENSITY] `#chatDisplay` fixed at 250px height on mobile — short for primary interface **Location:** `css/terminal.css:342` ```css #chatDisplay { height: 250px; /* mobile: fixed */ } /* desktop: flex-grow: 1; height: 0; */ ``` At 360px, the chat display is 250px tall. At ~18px per message (line-height + gap), 250px shows about 3–4 short messages. For longer AI responses with typewriter rendering, the panel fills quickly. The panel is scrollable. **This is a design tradeoff, not a layout bug.** There is no horizontal overflow from this. A taller fixed height is possible (e.g., `height: min(350px, 45vh)`) and would show more content on larger phones. **360/412/desktop check:** - 360px: 250px = ~3-4 messages visible ✓ (functional) - 412px: same ✓ - Desktop: grows to fill remaining viewport height ✓ **Verdict:** SPEC-FIRST (design decision; no overflow) · **Severity: LOW** --- ### M-L7 · [PWA] No `viewport-fit=cover` + no `safe-area-inset` — notch/home-bar interaction **Location:** `index.html:5` ```html <meta name="viewport" content="width=device-width, initial-scale=1.0" /> ``` No `viewport-fit=cover`. Without it, iOS doesn't offer `env(safe-area-inset-*)` values (they all return 0). The browser adds automatic chrome margins around the content area for the notch and home indicator. **PWA standalone mode:** iOS standalone (`display: standalone` from manifest) automatically adds padding for the status bar and home indicator. No `safe-area-inset` declarations needed. **Browser tab:** In a browser tab on iPhone X+ in portrait, the browser chrome handles the safe area. In landscape tab, the browser adds automatic side padding. No layout issue. **Risk:** If a future feature adds `position: fixed; bottom: 0` content (e.g., a bottom action bar), it would overlap the home indicator on iPhone X+. Current app has no fixed bottom content. **Verdict:** SKIP (no current risk; document for future bottom-fixed feature) · **Severity: LOW** --- ### M-L8 · [PWA / ICON] `icon.png` referenced at both 192×192 and 512×512 in manifest — both use 512px file **Location:** `manifest.json:12–24` ```json { "src": "icon.png", "sizes": "192x192", "purpose": "any maskable" }, { "src": "icon.png", "sizes": "512x512", "purpose": "any maskable" } ``` `icon.png` is physically 512×512px (verified). The 192×192 manifest entry references the same 512px file. The OS scales it down to 192px for app icons. Downscaling is lossless/high-quality. This is not a layout issue — app icons render correctly. **Best practice:** Provide an explicit 192×192 file to avoid the OS needing to do the downscale. Not required, and current behavior is fine on all major platforms. **Verdict:** SKIP (works correctly; minor best-practice deviation) · **Severity: LOW** --- ## "Already Nailed" Inventory These patterns are correctly implemented and protect the layout at 360/412px. Document them so the pattern is understood and preserved in future changes. | Pattern | Location | What it guards | |---|---|---| | `.main-grid { grid-template-columns: minmax(0, 1fr) }` | `terminal.css:130` | Prevents 1fr track expanding to hold wide open panels | | `.col-left, .col-right { min-width: 0 }` (global + desktop) | `terminal.css:393–407` | Standard grid/flex shrink guard; prevents min-content flooring | | `overflow-x: clip` on `.col-left, .col-right` at ≤999px | `terminal.css:435–438` | Clips inline overflow without creating a new scroll context (preserves `position: fixed` autocomplete) | | `.panel { overflow-wrap: anywhere }` | `terminal.css:137` | Prevents long panel content (item names, quest titles) from widening the panel | | `.msg-user, .msg-ai, .msg-sys { overflow-wrap: anywhere; word-break: break-word }` | `terminal.css:359–382` | Prevents long chat tokens (pasted JSON, URLs) from widening the chat track | | `.inventory-list li > span { min-width: 0; overflow-wrap: anywhere }` | `terminal.css:312–316` | Inventory item names wrap and don't push delete button off-screen | | `.list-row-content { flex: 1; min-width: 0; overflow-wrap: anywhere }` | `terminal.css:1994–1998` | Quest/perk/note rows grow and wrap without overflowing | | `.map-cell { min-width: 0; overflow: hidden; aspect-ratio: 1/1; min-height: 38px }` | `terminal.css:231` | 6×6 map grid stays within panel at any width; cells remain square tap targets ≥38px | | `#worldMapDisplay { max-width: 100%; overflow-x: auto }` | `terminal.css:411` | Fallback scroll container if map content somehow exceeds panel | | `input[type='number'] { max-width: 56px }` at ≤480px | `terminal.css:1802` | Prevents UA-default ~170px balloon on number inputs at mobile | | `font-size: 16px !important` on all form elements at ≤480px | `terminal.css:1853` | **The stretch-on-refresh fix.** Prevents iOS/Android auto-zoom on input focus | | `flex-wrap: wrap` on `.input-group` + limb button flex-grow at ≤480px | `terminal.css:1780` | BIO-METRICS rows wrap instead of clipping off the right edge | | `.faction-grid { 1fr 1fr }` at ≤480px + lone-card centering | `terminal.css:1827` | Faction cards legible in 2-column at 360px; odd count centered | | `.optics-label { white-space: nowrap }` | `terminal.css:151` | The documented S: wrap-fix pattern; prevents "OPTICS\n:" line break | | `.tab-bar { position: sticky; top: 0; z-index: 100 }` at ≤999px | `terminal.css:426` | Tabs stay visible while scrolling STAT/INV/DATA tabs | | `.tab-bar { flex-wrap: wrap }` at ≤480px | `terminal.css:1769` | Safety net if 4 tabs ever don't fit in one row | | CAMPG panel `<select style="width: 100%">` | `index.html:1003` | GAME/PLAYSTYLE/etc. selects fill available width (corrected by flex at ≤480px) | | `.faction-card { min-width: 0 }` | `terminal.css:1305` | Faction card name truncates with ellipsis rather than expanding card | | `.modal-box { min-width: 320px; max-width: 600px; max-height: 80vh }` | `terminal.css:576` | Modals fit 360px viewport (320px box + 20px margins each side) | | body `overflow-x: hidden` + html `overflow-x: hidden` | `terminal.css:12,22` | Belt-and-suspenders: prevents body-level horizontal scroll | | Craft selects rendered with `style="width:100%"` | `js/ui-render.js:1783,1806` | Recipe/scrap selects fill craft panel width on all sizes | --- ## "Stretch on Refresh" — Confirmed Mechanisms and Status | Mechanism | Status | Covered by | |---|---|---| | iOS/Android zoom focus-persist (main bug) | **Fixed at ≤480px** | `font-size: 16px !important` (r5) | | Same bug at landscape / 481–999px | **Not fixed** | See M-H1 — gap at 480–999px range | | Number input UA-default balloon at ≤480px | Fixed | `max-width: 56px` (r5) | | Number input balloon at landscape 481–999px | Not fixed | Paired with M-H1 | | `weight-over .col-left { transform }` at ≤480px | Fixed | `animation: none !important` at ≤480px | | `weight-over .col-left { transform }` at landscape | Not fixed | Paired with M-H1 | --- ## CAMPG panel density at 360px (no issues found) The CAMPG panel is `data-tab="campg"` and opens by default. Key elements: - `<select style="width: 100%">` × 3 (Game, Playstyle, Playthrough) — all full-width at ≤480px via override ✓ - COMPLETE RNG checkbox + label — flex row, wraps correctly ✓ - CAMPAIGN STATUS nested `<details class="panel">` — standard collapsible panel ✓ - WIPE TERMINAL button — `width: 100%; font-size: 11px` — full-width, accessible ✓ --- ## World Map at 360px (no issues found) The 6×6 world map grid: at 360px panel effective width ~310px. Each cell = (310 − 5×2px gaps) / 6 = ~50px. With `aspect-ratio: 1/1; min-height: 38px`, cells are 50×50px — above both the 44px WCAG and 28px Protocol 17 tap target. Cell names use `font-size: 8px; text-overflow: ellipsis; white-space: nowrap; max-width: 100%` — truncate correctly in 50px cells. No overflow. --- ## PWA standalone specifics | Property | Value | Status | |---|---|---| | `display` | `standalone` | App chrome hidden — content fills full screen below status bar | | `orientation` | `portrait` | Landscape disabled in standalone mode ✓ (no landscape gap in standalone) | | `theme_color` | `#010a07` | Matches app background — status bar blends ✓ | | `background_color` | `#010a07` | Splash screen background matches ✓ | | Shortcuts | 4 shortcuts (Comm-Link, Inventory, Stats, New Campaign) | All have maskable icons ✓ | | Icon | 512×512px `icon.png` | Downscales to 192px correctly; `any maskable` purpose ✓ | | Offline | Service worker + CACHE_NAME `robco-terminal-v2.6.0-r1` | All served assets precached ✓ | --- ## Prioritized Backlog ### BUILD-NOW — Scoped fixes, verified at all 3 widths | ID | Finding | Severity | Files | Width check | |---|---|---|---|---| | M-H1 | Font-size 16px + max-width guard missing at 481–999px (landscape auto-zoom) | **HIGH** | `css/terminal.css` · [CACHE] | Verified gap at landscape | | M-H2 | Export/Import/Restore no-wrap flex → "RESTORE BACKUP" wraps at 360/412px | **HIGH** | `index.html` · [CACHE] | 360 ✗, 412 ✗, desktop ✓ | | M-M1 | `.modal-content` missing `overflow-wrap: anywhere` | MEDIUM | `css/terminal.css` · [CACHE] | 360/412 risk, desktop low | | M-M2 | `.input-group select` flex override absent at 481–999px | MEDIUM | `css/terminal.css` · [CACHE] | 481–999px ✗ | | M-M4 | `#updateBanner` CSS dead code | LOW | `css/terminal.css` · [CACHE][TEST] | N/A | | M-L1 | D-pad buttons 35×35px → increase to 44px | LOW | `css/terminal.css` · [CACHE] | All widths improve | | M-L2 | Tracker toggle `<span onclick>` → switch to `<button class="btn-sm">` | LOW | `js/ui-render.js` · [CACHE][TEST] | All widths improve | ### SPEC-FIRST — Design decisions or multi-system changes | ID | Finding | Severity | Notes | |---|---|---|---| | M-M3 | Status effects select narrow in panel-form at ≤480px | LOW-MEDIUM | `min-width: 0` on panel-form selects | | M-L5 | Autocomplete panel left not viewport-clamped | LOW | Defensive fix; not currently a bug | | M-L6 | `#chatDisplay` 250px fixed height on mobile | LOW | Design tradeoff; use `min(350px, 45vh)` for more content | ### SKIP / INFO | ID | Finding | Notes | |---|---|---| | M-L3 | Faction buttons 28×28px | Protocol 17 compliant; minor comfort improvement only | | M-L4 | Karma slider fixed 100px | Functional; iOS range slider works at 100px | | M-L7 | No `viewport-fit=cover` | Current app has no fixed-bottom content; no risk | | M-L8 | Manifest: two entries, one 512px file | Works correctly; minor best-practice deviation | --- ## Counts by Severity | Severity | Count | |---|---| | HIGH | 2 | | MEDIUM | 3 | | LOW | 5 | | SPEC-FIRST | 3 | | SKIP/INFO | 4 | | **Total actionable** | **10** | --- ## Constraint Checklist - **[GATE]** 1078 tests must pass · never `--no-verify`. - **[CACHE]** All CSS/HTML changes are served files → `CACHE_NAME` bump required for every commit. - **[TEST]** Add Protocol 10 render-check at 360/412/desktop for any layout change. Both runners at parity per Protocol 15. - **[WIDTH TRIPLE-CHECK]** Every fix in this backlog must be reasoned at 360px, 412px, and ≥1000px before committing — per Protocol 10 and the "OPTICS lesson" (fixing one breakpoint must not break another). - **[A11Y PARITY]** M-L2 (tracker toggles) requires pairing with A2 §A-S2 for the ARIA changes (role/aria-checked/aria-pressed); never separate the visual fix from the semantic fix. - **[NEVER]** Never bump APP_VERSION for CSS/layout fixes. PATCH (x.y.Z) applies to bug fixes including layout. - **[NEVER]** Never use `--no-verify` to bypass the gate. --- *End of MOBILE_AUDIT.md*
STAMP · generated for RELEASE v2.8.5 commit 06e5180 (06e51801b38a) · archive input-tree hash c07fbfbdd2e1ddeb · 754 files · no wall-clock timestamp (regenerates identically when nothing changed).