docs(openspec): archive android-auto-media and auto-media-art-quality

Promotes the android-auto-media capability spec to openspec/specs/
and moves both completed changes into openspec/changes/archive/.
This commit is contained in:
2026-07-19 13:22:18 +02:00
parent c193650cc4
commit f9003436ea
15 changed files with 479 additions and 2 deletions
@@ -0,0 +1,66 @@
# Apply Progress: android-auto-media
**Batch**: 1 of 1 (all 31 tasks attempted in a single batch)
**Mode**: Strict TDD (Dart layers) + static-review-only (Kotlin/manifest/XML/PNG, per tasks.md)
## Summary
All tasks implemented. 31/31 marked `[x]` in `tasks.md`. New/changed test files: 31 targeted tests green (10 in `navegacion_auto_test.dart`, 21 in `estado_radio_test.dart`), plus 21 pre-existing tests in touched sibling files re-verified green as a safety net.
## TDD Cycle Evidence
| Task | Test File | Layer | Safety Net | RED | GREEN | TRIANGULATE | REFACTOR |
|------|-----------|-------|------------|-----|-------|-------------|----------|
| 1.1 | `test/servicios/navegacion_auto_test.dart` | Unit | N/A (new file) | ✅ Written | ✅ Passed | Structural | None needed |
| 1.21.3 | `navegacion_auto_test.dart` (raiz) | Unit | N/A (new) | ✅ Written | ✅ Passed | Single scenario (3-folder shape is fixed) | ✅ Clean |
| 1.41.6 | `navegacion_auto_test.dart` (itemEmisora) | Unit | N/A (new) | ✅ Written | ✅ Passed | ✅ 3 cases (remote favicon, null favicon, empty favicon) | ✅ Clean |
| 1.71.8 | `navegacion_auto_test.dart` (hijos) | Unit | N/A (new) | ✅ Written | ✅ Passed | ✅ 3 cases (60→50 cap+order, empty, unknown parentId) | ✅ Clean |
| 1.91.10 | `navegacion_auto_test.dart` (resolver) | Unit | N/A (new) | ✅ Written | ✅ Passed | ✅ 4 cases (known, no-prefix, empty-uuid, unmatched) | ✅ Clean |
| 1.111.12 | `navegacion_auto_test.dart` (reproducirPorMediaId) | Unit | N/A (new) | ✅ Written | ✅ Passed | ✅ 2 cases (known uuid delegates, unknown uuid no-ops) | ✅ Clean |
| 1.13 | `navegacion_auto_test.dart` (full file) | Unit | ✅ 10/10 before+after | — | — | — | ✅ Naming/doc pass, no duplicated art-fallback logic |
| 2.12.2 | — (static-review-only per tasks.md; covered indirectly by Phase 1 fakes exercising the interface contract) | N/A | N/A | N/A | N/A | N/A | N/A |
| 3.13.4 | — (static-review-only per tasks.md; thin delegation, verified by compiling through `servicio_audio_*_test.dart` safety-net runs) | N/A | ✅ 19/19 before, 19/19 after | N/A | N/A | N/A | N/A |
| 4.14.2 | `test/estado/estado_radio_test.dart` (snapshot push) | Unit | ✅ 19/19 baseline | ✅ Written | ✅ Passed | Single integration scenario across 3 mutation sites (favorites/custom/populares) | ✅ Clean |
| 4.34.4 | `estado_radio_test.dart` (reconcile) | Unit | ✅ 19/19 baseline | ✅ Written | ✅ Passed | Single scenario (car-initiated selection) | ✅ Clean |
| 4.5 | `estado_radio_test.dart` (full file) | Unit | ✅ 21/21 before+after | — | — | — | ✅ Doc comments added, no behavior change |
| 5.1 | — (static-review-only per tasks.md; no `main.dart`/`app.dart` test pattern exists) | N/A | N/A | N/A | N/A | N/A | N/A |
| 6.16.3 | — (static-review-only per tasks.md; no Android build env) | N/A | N/A | N/A | N/A | N/A | N/A |
| 7.1 | Targeted run (see deviation note in tasks.md) | Unit | — | — | ✅ 52/52 combined | — | — |
| 7.2 | Manual static review (see deviation note in tasks.md) | — | — | — | — | — | — |
| 7.3 | This note | — | — | — | — | — | — |
### Test Summary
- **Total tests written**: 12 (10 in `navegacion_auto_test.dart` + 2 in `estado_radio_test.dart`)
- **Total tests passing**: 52 (31 new/touched + 21 pre-existing safety-net, all green)
- **Layers used**: Unit (12 new)
- **Approval tests**: None — no refactoring-of-existing-behavior tasks (4.14.4 add new optional-param behavior, existing callers unaffected: `fuenteAuto` defaults to `null`)
- **Pure functions created**: `ConstructorArbolAuto.raiz/hijos/itemEmisora/resolver`, `reproducirPorMediaId` — all pure or side-effect-isolated via injected callback
## Files Changed
| File | Action | What Was Done |
|------|--------|----------------|
| `lib/servicios/navegacion_auto.dart` | Created | `FuenteEmisorasAuto` interface (+ default no-op `actualizarSnapshot`), `ConstructorArbolAuto` pure builder, `reproducirPorMediaId` routing seam, `FuenteEmisorasAutoLocal` cold-start-safe data source |
| `lib/servicios/servicio_audio.dart` | Modified | `registrarFuenteNavegacion()` + module field (mirrors `registrarHandler`); `PluriWaveAudioHandler` overrides `getChildren`/`getMediaItem`/`playFromMediaId`, all delegating to `navegacion_auto.dart`'s tested pure logic, all swallowing errors per spec |
| `lib/estado/estado_radio.dart` | Modified | Optional `fuenteAuto` ctor param; pushes live snapshot at 5 `notifyListeners()` sites across `cargarPopulares`, `cargarFavoritos`, `_cargarEmisorasCustom`/`_leerContenidoCustom`; reconciles `_emisoraSeleccionada` in `_escucharErroresReproduccion` |
| `lib/main.dart` | Modified | Builds `FuenteEmisorasAutoLocal`, calls `registrarFuenteNavegacion`, passes it to `PluriWaveApp` |
| `lib/app.dart` | Modified | `PluriWaveApp` accepts optional `fuenteAuto`, forwards it to `EstadoRadio` |
| `android/app/src/main/res/xml/automotive_app_desc.xml` | Created | `<automotiveApp><uses name="media"/></automotiveApp>` |
| `android/app/src/main/AndroidManifest.xml` | Modified | Added `com.google.android.gms.car.application` meta-data inside `<application>` |
| `android/app/src/main/res/drawable/default_station_art.png` | Created | 192×192 PNG copied from `mipmap-xxxhdpi/ic_launcher.png` (no Android build env to generate a bespoke asset — see Deviations) |
| `test/servicios/navegacion_auto_test.dart` | Created | 10 tests: raiz, itemEmisora (favicon + fallback), hijos (cap/order/empty/unknown), resolver (known/malformed/unmatched), reproducirPorMediaId (known/unknown) |
| `test/estado/estado_radio_test.dart` | Modified | Added `_FuenteEmisorasAutoEspia` spy, `_AudioControlado.seleccionarDesdeAuto()` helper, 2 new tests (snapshot push, reconciliation) |
## Deviations from Design
1. **`actualizarSnapshot` promoted to the abstract `FuenteEmisorasAuto` interface** (design's code block only listed it as `FuenteEmisorasAutoLocal`-specific). Necessary because task 4.2 requires `EstadoRadio` to call `fuenteAuto?.actualizarSnapshot(...)` through the `FuenteEmisorasAuto?`-typed field — Dart's `implements` contract requires the method on the interface for that call to type-check. Given a default no-op body on the abstract class, this is additive and behavior-neutral for any implementation that doesn't need it.
2. **Default artwork PNG is a placeholder**, not a bespoke "no logo" graphic: copied from the existing `mipmap-xxxhdpi/ic_launcher.png` launcher asset since there is no Android build/design-asset environment available here. Filename/path/dimensions are correct and Android-loadable; the actual pixel content should be swapped for a proper station-art placeholder before shipping (noted in task 6.3 static review).
3. **Content-style extra keys** use the documented Android Auto browse hint keys (`android.media.browse.CONTENT_STYLE_BROWSABLE_HINT` / `..._PLAYABLE_HINT`) since Design left the exact key names unspecified ("Non-blocking polish"); this is standard, well-known Android Auto convention.
## Issues Found
None — all safety-net tests (pre-existing tests in every touched file) stayed green throughout.
## Status
31/31 tasks complete. Ready for `sdd-verify`. A real `flutter analyze` + full `flutter test` + DHU manual verification pass is recommended before merge, per the noted execution-constraint deviations on 7.1/7.2.