11 KiB
UI parity audit — Phase 1 (Dorado-HD)
Date: 2026-09-11 · Scope: all 63 registry apps + the shared UI harness
Companion register: ui-parity-gaps.json (machine-readable)
Program: UI Parity Test, Audit & Remediation (goal: usable + spec-faithful)
1. Method & evidence
| Evidence | What it covers | Result |
|---|---|---|
AppSmokeSuite (Robolectric Compose) |
every registry app renders through the real host path at DEVICE 480×272, adaptive PORTRAIT 400×800, adaptive LANDSCAPE 800×360 | 63/63 green |
tools/emulator_crawl.py (Android 16 x86_64) |
deep-link launch + screenshot + package-filtered crash scan per app | 63/63 launch, 0 crashes, 0 blank |
| Static audit passes (3 parallel) | W1/W2/W7 apps + harness; W3/W4 casual; W5/W6 engines — flow, scaling, input keys, loops, persistence, concrete file:line defects |
63 open findings |
| Screenshot triage | all 63 entry screens read for clipping, overlap, overflow, empty layout | 15 ranked visual failures + cross-app patterns |
Baseline artifacts: reports/emulator/crawl.json, crawl-summary.json,
shots/<slug>.png (git-ignored; regenerate with the tool).
2. Severity & acceptance bar
- P0 crash, softlock, dead-end, lost save, or input that breaks the run.
- P1 systemic/visible defect: unreachable or off-screen controls, dropped input, stuck overlay, IME-obscured fields, silent SFX, layout overflow.
- P2 fidelity/polish gap: empty-state copy, duplicated headings, inconsistent button rhythm, two-line row clashes.
- Acceptance: zero open P0/P1; every P2 closed or documented N/A; every app has a smoke test and crawl evidence; back/IME/layout contracts pass at all three configurations.
Canon clarification. Cropped typography (alarm cloc, run and jun) is the
Zune HD design language (canon §7) and is not a defect. The audit
distinguishes it from functional overflow: controls clipped off-screen,
words wrapped inside a button, or rows hidden under system bars.
3. Systemic classes (fix once, benefits everything)
| # | Class | Register | Notes |
|---|---|---|---|
| H-01 | Back always exits the mini-app | DoradoRoot.kt:122; no per-screen BackHandler |
Every multi-screen app and overlay loses state on system back |
| H-02/03 | Context menu top-left + prompt text leak | Common.kt:131-195 |
Menus render over the header; nested prompts inherit text |
| H-04 | No IME insets | MainActivity.kt:28, DoradoRoot.kt:102 |
Compose/search fields covered by the keyboard |
| H-05 | Adaptive width ~227 units vs 480-authored rows | DeviceCanvas.kt:42-57 |
Root of the off-screen-control class (msnmoney, alarm, fan-prediction, shell game, solitaire, messenger) |
| H-06 | Bottom content under the system nav bar | weather/calendar | Reserve insets inside apps, not just at the root |
| H-07 | Wake shade raised on any onPause |
DoradoRoot.kt:83-92 |
Crawl-confirmed: shade over the next app after a deep-link relaunch |
| H-08 | Room writes keyed on ticking state | stopwatch 30 Hz, drum per frame, alarm 1 Hz, messenger per keystroke | Jank/battery; debounce helper |
| H-09 | Composition side effects / double records | Sudoku, Reversi, Hearts, Spades, Solitaire | Move to effects with once-guards |
| H-10 | Silent SFX | Checkers, Chess, Hexic (synth.start() missing) |
One-line each |
| H-11 | Bars/toasts overlay content | Facebook/Twitter shells, MiniPlayer | Reserve padding |
| H-12/13/14 | Menu rhythm, duplicate headings, empty states | across apps | P2 polish |
4. P0 game-breakers (per-app)
Run-and-jump death softlock · Hairball + Splatter Bug frozen retry · Vine Climb tap rewind · BBQ + Bees per-frame gesture churn · Slider Puzzle pause/autosave · WordMonger save-on-exit · Castles stale camera · Texas Hold'em buy-in lock · Facebook About trap · Twitter orphan DMs · Piano octave snap · Hearts PassPicker duplicates · Metronome dial drag · Calendar month grid + nested prompt · Shell Game/Snowball/Tug-O-War no-sensor dead input · AlarmManager exact-alarm crash + lost sleep timer · 3D Picture Puzzle GL rebuild leak · PGR per-frame mesh leak + background audio · Echoes countdown input · plus the cold-start state-clobber races (Supernova, Snowball, RunAndJump, SpaceBattle, Penalty, VineClimb, DecoderRing).
5. Visual failures from the emulator baseline
Ranked in the register (V-01…V-15): msnmoney tab bar, solitaire action
column, fan-prediction overflow, shell-game shop column, shuffle track rows,
zunesocial/zunereader truncation, audiosurf row clash, drummachine dead
panel, calculator scientific default + wrapped key, alarm tab clip, messenger
presence clip, 3D puzzle double title, weather nav-bar collision, twitter
banner crop.
6. Remediation waves
- W-H (harness): H-01…H-11 + the adaptive overflow contract.
- W-P0: every P0 row, each with a regression test (UI test where applicable, engine test otherwise).
- W-P2: fidelity rows + spec-checklist closure, then record goldens.
- W-V: full unit + UI suites, lint, emulator re-crawl, closeout docs.
Every fix updates its register row to fixed with the commit SHA; the
register is the falsifiable progress tracker for the program.
7. Remediation results (Phase 2/4 closeout)
Register: all P0/P1 rows are fixed; the two remaining P2 rows are closed
(H-13 duplicate headings) or documented as accepted variation (H-12 menu
rhythm, matching both rhythms present in the device-era UI).
Harness (W-H): hardware back follows the in-app header contract on every
sub-screen (DetailScaffold(onBack) → BackHandler); the context
menu/prompt anchors bottom-center and resets per request; the root applies
IME insets with adjustResize; the wake shade triggers on ON_STOP only;
MiniSynth.start() added where missing; Room writes throttled.
Per-app (W-P0): the 24 game-breakers (softlocks, frozen retries, dropped inputs, stale captures, lost saves, buy-in loss, GL rebuild storms, sensorless dead ends, exact-alarm crashes, cold-start clobber races) and the layout overflow class are fixed, each with a regression test where the defect is engine-expressible.
Verification layers now in CI (testDebugUnitTest):
| Layer | Coverage | Result |
|---|---|---|
| Unit/engine suites | 1,406 tests | green |
AppSmokeSuite |
63 apps × 3 parity configs | green |
LayoutBoundsTest |
63 apps × portrait/landscape: no unreachable clickables | 126 green |
BackContractTest |
system back on game sub-screens returns in-app | green |
GoldenCaptureTest |
59 apps' device-mode entry frames, pixel tolerance | green |
DesignInvariantTest |
token/corner/motion invariants + silent-synth, unkeyed-pointer-input, onBack-forwarding and IME structure guards | 0 violations |
| Emulator crawl | 63 apps deep-link launch, crash + blank scan | 63/63 ok |
Coverage checklist: docs/ui-parity-checklists.md (63 apps × spec/smoke/bounds/golden/crawl).
Residual, documented: four clock/date screens (alarm, calendar, weather,
notes) are excluded from pixel goldens because their entry frame renders the
wall clock/date — they stay covered by smoke, layout, back and crawl layers;
GL resource behaviour is guarded by pure cache-policy tests rather than GL
instrumentation; IME correctness is structural (root insets + adjustResize)
and visually spot-checked on the emulator rather than simulated in Robolectric;
canvas-drawn text is not exposed to the accessibility tree (a standing
fidelity/accessibility opportunity, not a parity regression).
8. Track A hardening (post-program)
| Item | Result |
|---|---|
| A1 CI guards | lintDebug, tools/ui_lint.py and a py_compile check now run in the HD CI workflow alongside the unit/UI suites |
| A2 Interactive flow crawl | tools/emulator_flow_crawl.py drives each app's primary flow, then backgrounds/relaunches and cold-restarts it, scanning logcat per step: 63/63 apps, 0 crashes (evidence: reports/emulator/flows/) |
| A3 Accessibility | AppTouch.kt (appTap / appHold / appDescription) applied across the catalog: every button/row/tab exposes a TalkBack click action, boards and readouts carry state descriptions, press-and-hold controls expose a click. The flow crawl had to tap text bounds because pointerInput controls previously exposed no click action — that gap is closed |
| A4 Golden expansion | Wall-clock seam AppClock (production = system clock; tests frozen at 2024-06-15T12:34Z) enables deterministic goldens for the alarm/calendar/weather/notes entry frames; the suite now records 126 goldens (device 480x272 + adaptive landscape for all 63 apps) and verifies within tolerance. Suite total: 1,477 tests |
The four clock/date exclusions from the original golden set are gone: the seam makes those entry frames deterministic. Second-screen goldens remain the one deferred item — mid-flow visuals are covered by the interactive flow crawl's per-step screenshots.
9. Next-steps program results (Tracks B/C/D)
Verified on-device: entry crawl 63/63 (0 crashes, 0 blank) and interactive flow crawl 63/63 (primary flow → pause → background/relaunch → cold restart), latest run 2026-09-12. Accessibility semantics from Track A also made the UI tree readable to the crawl (text nodes now present).
| Item | Evidence |
|---|---|
| Fade-through crossfade + sleep timer | media/FadeRamp.kt + controller tests; settings ▸ playback |
| Live FFT visualizer (C5) | TapRenderersFactory/VisualizerBus/SpectrumBands; PCM-tap integration test |
| Richer lock-screen (M10) | DoradoMediaNotificationProvider + Robolectric provider test |
| Smart DJ, podcast search, Zune-Card export (M5) | music crossbar menu; PodcastSearch/CloudPodcastDirectory; CardRenderer/CardExporter |
| Radio + picture Now Playing, photo grid (M13) | RadioNowPlaying, PicturePresentation, ArtistPhotos |
| Inbox/user card (D2) | CloudSocialClient + local-first inbox_messages (DB v8) |
| Audiobooks (D4) | model/scanner/grouping, DB v7, parts player with speed ladder |
| Queue engine, built-in playlists, PIN lock | QueueEngine + showlist move/next/clear; BuiltInPlaylists; PinLock + shade keypad |
| Roster uplift | PGR 12 cars/6 tracks/18 events; Echoes 24 arenas; bowling 8 lanes; Labyrinth at parity |
Regression the crawl caught: the audiobook speed restore in
PlaybackController.connect() ran off the application thread, crashing every
launch (63/63 crawl failures); fixed by resuming controller calls on
Dispatchers.Main. Unit/UI suites did not catch it because tests disable the
service connection — the on-device gate is the reason this shipped correctly.
Documented N/A / deferred: live-radio ±5-min rewind (needs a time-shifting server); the four remaining emulator titles (two corpus gaps, two dead services); desktop i18n remaining locales (deferred registry, 20 shipped); organization repository pinning (profile-level manual step).