Files
pluriwave/openspec/changes/auto-media-art-quality/apply-progress.md
T
FreeTLab c193650cc4 fix(auto): fall back to brand art and surface quality on dead/missing favicons
Android Auto no longer copies the launcher icon as placeholder art; it
rotates through the same 4 on-brand station_art assets the phone UI
already uses, keyed by the same per-station hash for visual parity.
Malformed or unusable favicon URLs (including a Dart Uri quirk where
'http://' reports hasAuthority=true with an empty host) now fail the
validity gate instead of being handed to the OS media browser as-is.
Browsable items also show codec/bitrate as a subtitle when known.
2026-07-19 13:06:18 +02:00

8.6 KiB

Apply Progress: auto-media-art-quality

Mode: Strict TDD Batch: 1 of 1 (single delivery, no chaining — forecast was Low risk, ~170-230 est. lines; actual ~347 text lines, still under 400-line budget)

Completed Tasks

All 22 tasks in tasks.md are complete and marked [x].

  • 1.1 [RED] faviconUsable test matrix
  • 1.2 [GREEN] Implement faviconUsable
  • 2.1 [RED] indiceArtePara formula/order test
  • 2.2 [GREEN] Implement indiceArtePara + _nombresArte
  • 3.1 [RED] artUriPara integration test (favicon gate + rotation)
  • 3.2 [GREEN] Implement artUriPara; drop _defaultArtUri and old _artUriPara
  • 3.3 [GREEN] Replace obsolete default-art test
  • 4.1 [RED] subtituloCalidad matrix test
  • 4.2 [GREEN] Implement subtituloCalidad
  • 5.1 [RED] itemEmisora sets displaySubtitle test
  • 5.2 [GREEN] Wire displaySubtitle into itemEmisora
  • 5.3 [REFACTOR] Cleanup pass, doc comments, dead-reference check
  • 6.1 [coverage] Emisora.fromMap unsanitized-favicon regression test
  • 6.2 [coverage] Phone/auto art-order parity guard
  • 7.1 Add 4 native drawable copies (static-review-only)
  • 7.2 Delete default_station_art.png (static-review-only)
  • 7.3 Verify applicationId/authority match (static-review-only)
  • 8.1 Targeted regression run (navegacion_auto_test.dart, full suite deviation documented)
  • 8.2 flutter analyze — DEVIATION, not run (hangs in this environment); manual static review done
  • 8.3 flutter build/flutter run — DEVIATION, not run (hangs in this environment)
  • 8.4 Manual DHU verification note — recorded below (non-blocking, not coded)

Files Changed

File Action What Was Done
lib/servicios/navegacion_auto.dart Modified Added faviconUsable, indiceArtePara, _nombresArte, artUriPara, subtituloCalidad; wired artUri/displaySubtitle into itemEmisora; dropped _defaultArtUri const and old private _artUriPara
test/servicios/navegacion_auto_test.dart Modified Added faviconUsable, indiceArtePara, artUriPara, subtituloCalidad, itemEmisora displaySubtitle extension, Emisora.fromMap unsanitized-favicon regression, and phone/auto art-order parity groups; replaced the obsolete _defaultArtUri-based default-art test; extended _emisora() test helper with a codec parameter
android/app/src/main/res/drawable/station_art_aurora.png Created Verbatim byte-for-byte copy of assets/images/station_art_aurora.png (size-verified match)
android/app/src/main/res/drawable/station_art_cosmic.png Created Verbatim byte-for-byte copy of assets/images/station_art_cosmic.png (size-verified match)
android/app/src/main/res/drawable/station_art_pulse.png Created Verbatim byte-for-byte copy of assets/images/station_art_pulse.png (size-verified match)
android/app/src/main/res/drawable/station_art_nova.png Created Verbatim byte-for-byte copy of assets/images/station_art_nova.png (size-verified match)
android/app/src/main/res/drawable/default_station_art.png Deleted Retired — rotation is total (uuid always present), launcher-icon-lookalike removed per Design Decision 3

TDD Cycle Evidence

Task RED GREEN REFACTOR
1.1/1.2 faviconUsable Confirmed fail: Method not found: 'faviconUsable' (compile error across all call sites) Implemented; all faviconUsable cases pass. Bug found during GREEN: Uri.tryParse('http://').hasAuthority returns true even with an empty host — initial implementation used uri.hasAuthority, which wrongly accepted 'http://'. Fixed to check uri.host.isNotEmpty explicitly; re-ran, all pass. Doc comments added explaining the hasAuthority gotcha inline
2.1/2.2 indiceArtePara Confirmed fail: Method not found: 'indiceArtePara' Implemented verbatim-ported formula; parity assertions pass _nombresArte kept private per design; cross-link comment to tarjeta_emisora.dart added
3.1/3.2/3.3 artUriPara Confirmed fail: Method not found: 'artUriPara' (multiple sites) Implemented; replaced old _artUriPara/_defaultArtUri; obsolete default-art test replaced Grep-confirmed zero remaining _defaultArtUri/old _artUriPara references anywhere in lib//android/
4.1/4.2 subtituloCalidad Confirmed fail: Method not found: 'subtituloCalidad' Implemented per design's quality-format table incl. bitrate<=0 and whitespace-codec edge cases; all pass
5.1/5.2 itemEmisora wiring Confirmed fail: assertion targeted not-yet-set displaySubtitle Wired displaySubtitle: subtituloCalidad(e) into MediaItem(...); full group green 5.3: re-read top to bottom, doc comments added, dead-reference grep clean, full suite (this file) green
6.1 Emisora.fromMap gap regression Coverage-only, single-pass assertion (design already characterizes current behavior) Passed on first run — confirms artUriPara's validity gate catches the fromMap unsanitized-favicon gap as designed
6.2 Phone/auto parity guard Coverage-only, single-pass assertion Passed — verified via artUriPara/indiceArtePara over 4 seeds covering all rotation indices (no public accessor added, keeping _nombresArte private per design)

Deviations from Design

  • Test verifies canonical art-name order behaviorally, not via a public constant. Design specifies _nombresArte as a private const (task 2.2 says "Private const _nombresArte"). Task 2.1/6.2 ask to assert the canonical order directly. Rather than making the list public just for test access (which would contradict the design's explicit privacy choice), the order is verified indirectly through indiceArtePara + artUriPara behavior using single-character seeds ('d'→index 0, 'a'→1, 'b'→2, 'c'→3) that deterministically cover all 4 rotation indices. This satisfies the same intent (pin the order, catch reorder drift) without leaking an implementation-private symbol.
  • No other deviations — implementation matches design (faviconUsable, indiceArtePara, artUriPara, subtituloCalidad signatures match the Design "Interfaces / Contracts" section exactly).

Issues Found

  • Real bug caught by Strict TDD RED→GREEN cycle: Uri('http://').hasAuthority returns true in Dart even when host is empty — a naive uri.hasAuthority check would have let 'http://' (scheme, no host) pass as a "usable" favicon, contradicting the spec's malformed-URL rejection requirement. Fixed by checking uri.host.isNotEmpty explicitly. Documented inline with a code comment to prevent regression.
  • No other issues.

Static Review Notes (Phase 7 — no Android build env available)

  • 4 new drawables (station_art_aurora/cosmic/pulse/nova.png) copied byte-for-byte from assets/images/ — file sizes verified identical to source (1,910,538 / 2,053,431 / 1,879,963 / 1,742,275 bytes respectively).
  • default_station_art.png deleted; grep -rn default_station_art lib android returns zero matches (excluding SDD artifact prose in openspec/changes/auto-media-art-quality/).
  • android/app/build.gradle.kts:34 confirmed unchanged: applicationId = "es.freetimelab.pluriwave", matching the android.resource://es.freetimelab.pluriwave/drawable/station_art_<name> authority built in artUriPara. No manifest/build.gradle change required.
  • A real Android build or DHU session is still required before shipping to confirm the OS art loader resolves the 4 new station_art_* drawables via android.resource:// (Design's open question — unresolved, carried forward).

Manual DHU Verification Note (task 8.4, non-blocking)

Recommended before merge/ship: a Desktop Head Unit (or real car) session to visually confirm (a) all 4 rotating arts render correctly per station, (b) displaySubtitle quality text renders as expected on the browse row, (c) no stale default_station_art reference lingers anywhere in the built APK's resources.

Remaining Tasks

None — all 22 tasks complete.

Workload / PR Boundary

  • Mode: single PR (no chaining needed)
  • Current work unit: entire change (auto-media-art-quality), single deliverable
  • Boundary: starts from the existing _artUriPara/_defaultArtUri baseline, ends with the full validity-gate + rotation + quality-subtitle feature landed and tested
  • Estimated review budget impact: ~347 changed text lines (2 files) + 4 binary adds + 1 binary delete — comfortably under the 400-line budget (forecast: Low risk, confirmed)

Status

22/22 tasks complete. Ready for sdd-verify.

Working tree left uncommitted (staged-ready) per instructions — no git commit run by this agent.