Files
pluriwave/openspec/changes/archive/2026-07-19-android-auto-eq-presets/verify-report.md
T
FreeTLab 9bfa9ac408 docs(openspec): archive android-auto-eq-presets
Merges its delta requirements into the android-auto-media base spec.
2026-07-19 14:19:11 +02:00

118 lines
8.3 KiB
Markdown

# Verification Report: android-auto-eq-presets
**Mode**: Strict TDD | **Verdict**: PASS
## Completeness
All 21 tasks in `tasks.md` (Phases 1-5) are checked complete and match the live code. No task marked complete lacks a corresponding code/test change.
## Test Execution Evidence (independently re-run, not trusted from apply-progress)
| Command | Result |
|---|---|
| `flutter test test/servicios/navegacion_auto_test.dart --concurrency=1 --timeout=60s` (compact reporter) | 46/46 passed |
| Same file, `-r json`, counted `testDone` events | 46 (independently confirmed via JSON reporter, not just trusting the compact-reporter tally) |
| `flutter test test/estado/estado_radio_test.dart --concurrency=1 --timeout=60s` (favorite-groups regression suite, shared `playFromMediaId`/dispatch surface) | 22/22 passed (JSON `testDone` count also 22) |
Apply-progress's claimed 46/46 for `navegacion_auto_test.dart` is accurate. The extra regression pass on `estado_radio_test.dart` (not part of apply-progress's own claims) also passes clean - no regression on the favorite-groups feature that shares the touched dispatch surface.
## Load-Bearing Invariant: EQ selection never disturbs playback
Traced `playFromMediaId` (`lib/servicios/servicio_audio.dart:781-820`) directly in the live file:
- The `esPresetMediaId(mediaId)` branch is the first statement inside the (now single) outer `try`, before `_fuenteNavegacionGlobal` is even read.
- It ends with an unconditional `return;` - genuinely cannot fall through to `reproducirPorMediaId`/`playMediaItem`.
- No `mediaItem` mutation, no `play()`/`setUrl()`/reconnect call anywhere in that branch - only `ServicioEcualizador()` construction and `aplicarPresetPorMediaId(...)`.
Traced `aplicarPresetPorMediaId`'s actual signature (`lib/servicios/navegacion_auto.dart:364-371`, read directly, not from docstring):
```
Future<void> aplicarPresetPorMediaId(
String id, {
required List<PresetEcualizador> presets,
required String? uuidActual,
required Future<Set<String>> Function() clavesPorEmisora,
required Future<void> Function(PresetEcualizador) persistirPrincipal,
required Future<void> Function(PresetEcualizador) aplicar,
})
```
Confirmed: no `playMediaItem`, `mediaItem`, or `playbackState` parameter exists - structurally impossible to inject a playback seam. This matches the docstring claim and the dedicated structural test (`navegacion_auto_test.dart:755-784`).
## Root Folder Count
- `ConstructorArbolAuto.raiz()` (`lib/servicios/navegacion_auto.dart:171-176`) returns 4 folders, `Ecualizador` last, `playable: false`.
- `test/servicios/navegacion_auto_test.dart:226` - `expect(raiz, hasLength(4))` - confirmed updated (line number shifted from the apply-progress's original estimate of 225 to 226 due to file growth, as anticipated by the reviewer's warning; content matches).
- Line 241-242 additionally assert `raiz.last.id == idEcualizador` and `raiz.last.playable == false` (ADR-2 last-position + non-playable, beyond the base spec's minimum).
- Grepped the full `test/` tree for other `raiz()`/root-count assertions: only `navegacion_auto_test.dart` matches - confirms task 4.2's claim.
## Persistence Seam
Grepped both call sites in the diff:
- `servicio_audio.dart:800` - `persistirPrincipal: servicio.guardarPrincipal` -> `ServicioEcualizador.guardarPrincipal` (`servicio_ecualizador.dart:129-132`, writes SharedPreferences key `eq_preset_principal_v1`).
- No call to `EstadoEcualizador.cambiarPresetPrincipal` anywhere in the diff or the new code paths (confirmed via grep across `navegacion_auto.dart`/`servicio_audio.dart`).
- Matches ADR-4 exactly: handler + service seam, not the phone's `ChangeNotifier`.
## debeAplicarPrincipalAhora Gate Parity
Read both gates directly:
- `estado_ecualizador.dart:302-304`: `uuid == null || !_presetsEmisoraMap.containsKey(uuid)`
- `navegacion_auto.dart:348-351`: `uuidActual == null || !clavesPorEmisora.contains(uuidActual)`
Also traced that `clavesPorEmisora` is populated from `servicio.cargar().porEmisora.keys`, and `servicio_ecualizador.dart`'s `cargar()` reads `porEmisora` from the same persisted per-station map that populates `EstadoEcualizador._presetsEmisoraMap` (`estado_ecualizador.dart:161-163`, `..addAll(config.porEmisora)`). The two gates are genuinely operating over the same underlying data, not just superficially similar in shape.
## Unknown/Stale Preset ID
`resolverPresetEq` returns `null` for unknown name / empty-after-prefix / non-`eq_preset:` id (`navegacion_auto.dart:332-340`). `aplicarPresetPorMediaId` returns immediately on `null` without invoking either seam. Test (`navegacion_auto_test.dart:731-753`) asserts `persistirLlamadas == 0` and `aplicarLlamadas == 0` for `'eq_preset:Inexistente'` - the assertion is on call counts, not just a test name; genuinely proves the no-op.
## Active Preset Indication (Optional requirement)
Confirmed OUT OF SCOPE as designed (ADR-6). Grepped `navegacion_auto.dart` for marker/selection-related terms - no half-built UI, no dead marker code, no leftover scaffolding. `itemPresetEq` emits only the plain preset name as `title`.
## Deviations Self-Reported by apply-progress.md - Verified
1. servicio_ecualizador.dart import added to servicio_audio.dart, not listed in task 3.3's enumerated file list. Confirmed real: `git diff` shows `+import 'servicio_ecualizador.dart';` at the top of the file; task 3.3's text only mentions verifying the `preset_ecualizador.dart` import. This is a genuine (harmless) task-description gap, not a code defect.
2. playFromMediaId restructured to a single outer try/catch. Confirmed real via `git diff`: previously `final fuente = _fuenteNavegacionGlobal; if (fuente == null) return;` sat OUTSIDE the try block; now both lines are inside the try, after the new `eq_preset:` branch. Behaviorally inert for the pre-existing `emisora:`/`grupo:` path - the null-check itself cannot throw, so moving it inside `try` changes nothing observable. Verified via the full `estado_radio_test.dart` (22/22) and `navegacion_auto_test.dart` (46/46, includes `reproducirPorMediaId` tests) suites, both green.
## Diff Size Cross-Check
`git diff --stat` on the three touched files:
```
lib/servicios/navegacion_auto.dart | 94 ++++++++++++-
lib/servicios/servicio_audio.dart | 26 +++-
test/servicios/navegacion_auto_test.dart | 226 ++++++++++++++++++++++++++++++-
3 files changed, 339 insertions(+), 7 deletions(-)
```
339 + 7 = 346 changed lines - matches apply-progress's self-reported figure exactly. Within the 300-420 forecast, under the 400-line budget.
## Cleanliness Checks
- No AI attribution strings found in the diff (`Co-Authored-By`, `Generated with`, `Claude`, `ChatGPT`, etc. - none matched).
- No stray `print`/`debugPrint` calls introduced.
- No `TODO`/`FIXME` markers introduced.
- No dead code found in the reviewed hunks.
- Working tree: `navegacion_auto.dart`, `servicio_audio.dart`, `test/servicios/navegacion_auto_test.dart` are modified-but-unstaged; `openspec/changes/android-auto-eq-presets/` is untracked. Nothing has been committed - confirmed via `git log` (HEAD is still `066fedb`, predating this change) and `git status`.
## Spec Compliance Matrix
| Spec Requirement | Status | Evidence |
|---|---|---|
| Browsable Media Tree (4 folders, Ecualizador included) | PASS | raiz() + test at line 226 |
| EQ Preset Browsable Folder (6 items, eq_preset:<nombre>) | PASS | presetsEq/itemPresetEq + tests |
| EQ Preset Selection Applies Without Disturbing Playback | PASS | Structural signature proof + first-branch/unconditional-return trace |
| EQ Preset Persistence and Phone/Car Parity | PASS | guardarPrincipal call site confirmed, SP key shared with phone |
| Active Preset Indication (Optional) | PASS (scoped out) | ADR-6, no half-built code |
## Design Coherence
All 6 ADRs (ADR-1 through ADR-6) traced against live code - implementation matches design as documented. No undocumented deviation found beyond the two apply-progress already self-reported (both verified genuine and low-risk).
## Issues Found
None CRITICAL. None WARNING. None SUGGESTION beyond a purely cosmetic note: apply-progress's line-number references (e.g. "225" vs actual "226") drift slightly as the file grows - informational only, does not affect correctness or task completeness.
## Final Verdict
PASS - 0 CRITICAL, 0 WARNING, 0 SUGGESTION (one informational-only line-number drift note, not a defect).