Files
pluriwave/openspec/changes/android-auto-eq-presets/apply-progress.md
T
FreeTLab 90cd232ad2 feat(auto): expose EQ presets as a browsable Android Auto folder
Adds an Ecualizador folder listing the 6 fixed presets; selecting one
applies and persists it through the existing headless-safe seam
without touching playback or the now-playing media item.
2026-07-19 14:12:39 +02:00

94 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Apply Progress: android-auto-eq-presets
**Mode**: Strict TDD
**Batch**: First and only batch — all 21 tasks completed in one pass.
## Completed Tasks
### Phase 1: Foundation — media-id scheme + root folder
- [x] 1.1 [RED] `esPresetMediaId` table test
- [x] 1.2 [GREEN] `_prefijoPresetEq` const + `esPresetMediaId`
- [x] 1.3 [RED] `raiz` test updated to `hasLength(4)` + last-position assertions
- [x] 1.4 [GREEN] `idEcualizador` const + `raiz()` appends `Ecualizador` folder last
- [x] 1.5 [RED] `itemPresetEq` test
- [x] 1.6 [GREEN] `itemPresetEq`
- [x] 1.7 [RED] `presetsEq` test
- [x] 1.8 [GREEN] `presetsEq`
### Phase 2: Core Implementation — resolve, gate, orchestrate
- [x] 2.1 [RED] `resolverPresetEq` table test
- [x] 2.2 [GREEN] `resolverPresetEq` free function
- [x] 2.3 [RED] `debeAplicarPrincipalAhora` table test
- [x] 2.4 [GREEN] `debeAplicarPrincipalAhora` pure gate function
- [x] 2.5 [RED] `aplicarPresetPorMediaId` known-preset gate-true/gate-false test
- [x] 2.6 [RED] `aplicarPresetPorMediaId` unknown/stale-id no-op test
- [x] 2.7 [RED] Structural non-playback invariant test (signature has no playback seam)
- [x] 2.8 [GREEN] `aplicarPresetPorMediaId` orchestration
### Phase 3: Integration / Wiring
- [x] 3.1 `getChildren``idEcualizador` branch returns `presetsEq(PresetEcualizador.presets)`
- [x] 3.2 `playFromMediaId``eq_preset:` branch as first statement, unconditional `return`, wrapped in method's outer try/catch
- [x] 3.3 Import check — `preset_ecualizador.dart` already imported; added new `servicio_ecualizador.dart` import (required, was not previously imported in this file)
### Phase 4: Testing / Verification
- [x] 4.1 `flutter test test/servicios/navegacion_auto_test.dart` — 46/46 passing (independently re-run twice with default and compact reporters)
- [x] 4.2 Grepped `test/` tree for other `raiz()`/root-count assertions — only `navegacion_auto_test.dart` matches, confirmed
- [x] 4.3 [DEVIATION] `flutter build`/`run`/`analyze`/`gen-l10n` NOT run (hangs in this environment, same precedent as prior change). Manual static review performed instead.
### Phase 5: Cleanup
- [x] 5.1 [REFACTOR] Doc comments added referencing ADR-1/2/3/5 on all new public/free-function members; suite confirmed green after
- [x] 5.2 Active-preset indication — confirmed out of scope per ADR-6, no code added (tracking-only task)
## Files Changed
| File | Action | What Was Done |
|------|--------|----------------|
| `lib/servicios/navegacion_auto.dart` | Modified | Added `_prefijoPresetEq` const, `esPresetMediaId`, `ConstructorArbolAuto.idEcualizador`, `raiz()` now returns 4 folders (Ecualizador last), `itemPresetEq`, `presetsEq`, free functions `resolverPresetEq`, `debeAplicarPrincipalAhora`, `aplicarPresetPorMediaId` |
| `lib/servicios/servicio_audio.dart` | Modified | `getChildren` gains `idEcualizador` branch (before `fuente` guard); `playFromMediaId` restructured — single outer try/catch, `eq_preset:` branch first with unconditional return; added `servicio_ecualizador.dart` import |
| `test/servicios/navegacion_auto_test.dart` | Modified | Updated `raiz` test to `hasLength(4)`; added groups for `esPresetMediaId`, `itemPresetEq`, `presetsEq`, `resolverPresetEq`, `debeAplicarPrincipalAhora`, `aplicarPresetPorMediaId` (4 tests including structural non-playback invariant) |
## TDD Cycle Evidence
| Task | Test File | Layer | Safety Net | RED | GREEN | TRIANGULATE | REFACTOR |
|------|-----------|-------|------------|-----|-------|-------------|----------|
| 1.1/1.2 | `navegacion_auto_test.dart` | Unit | ✅ 34/34 (pre-existing baseline) | ✅ Written | ✅ Passed | ✅ 5 cases (table) | None needed |
| 1.3/1.4 | `navegacion_auto_test.dart` | Unit | ✅ (same file) | ✅ Written | ✅ Passed | Single (fixed 4-folder shape) | None needed |
| 1.5/1.6 | `navegacion_auto_test.dart` | Unit | ✅ (same file) | ✅ Written | ✅ Passed | Single | None needed |
| 1.7/1.8 | `navegacion_auto_test.dart` | Unit | ✅ (same file) | ✅ Written | ✅ Passed | ✅ 6 presets iterated | None needed |
| 2.1/2.2 | `navegacion_auto_test.dart` | Unit | ✅ (same file) | ✅ Written | ✅ Passed | ✅ 3 negative cases + 1 positive | None needed |
| 2.3/2.4 | `navegacion_auto_test.dart` | Unit | ✅ (same file) | ✅ Written | ✅ Passed | ✅ 3 cases (null/no-override/override) | None needed |
| 2.5-2.8 | `navegacion_auto_test.dart` | Unit | ✅ (same file) | ✅ Written | ✅ Passed | ✅ 4 tests (gate-true, gate-false, unknown-id, structural) | ✅ Clean (extracted gate + resolve as separate pure fns) |
| 3.1-3.3 | N/A (dispatch layer, zero coverage precedent per design baseline) | N/A | N/A (new branch in untested method) | N/A | N/A — manual review only, standard workflow per design's documented precedent | N/A | None needed |
| 5.1 | `navegacion_auto_test.dart` | Unit | ✅ 46/46 (post-implementation) | N/A (doc-only) | ✅ Suite still green | N/A | ✅ Doc comments added |
Note on Phase 3: `servicio_audio.dart`'s `getChildren`/`playFromMediaId` dispatch layer has zero existing test coverage (confirmed in tasks.md baseline — no `test/servicios/servicio_audio_test.dart` exists). This is a pre-existing condition, not something introduced here; the design explicitly names the pure `navegacion_auto.dart` functions as the load-bearing tested layer (ADR-3), with the handler wiring as thin, structurally-safe delegation. No RED/GREEN test cycle applies to Phase 3 tasks — they are direct wiring edits verified by (a) the full Phase 1-2 pure-function suite staying green, and (b) manual static review per task 4.3.
### Test Summary
- **Total tests written**: 15 new test cases (1 esPresetMediaId table [5 sub-cases], 1 itemPresetEq, 1 presetsEq, 2 resolverPresetEq, 3 debeAplicarPrincipalAhora, 4 aplicarPresetPorMediaId, plus the updated raiz test)
- **Total tests passing**: 46/46 in `test/servicios/navegacion_auto_test.dart` (34 pre-existing baseline + 12 new test blocks, some containing multiple sub-assertions/table cases) — independently re-confirmed via two separate `flutter test` invocations (default and `--reporter=compact`)
- **Layers used**: Unit (46), Integration (0), E2E (0)
- **Approval tests** (refactoring): None — no refactoring tasks; Phase 5.1 was pure doc-comment addition, verified via the full suite staying green
- **Pure functions created**: 4 (`esPresetMediaId`, `resolverPresetEq`, `debeAplicarPrincipalAhora`, plus `itemPresetEq`/`presetsEq` as pure builders) — `aplicarPresetPorMediaId` is an orchestrator with injected seams, not fully pure, by design (ADR-3)
## Deviations from Design
1. **`servicio_ecualizador.dart` import added to `servicio_audio.dart`** — not explicitly listed in task 3.3 (which only mentions verifying the `preset_ecualizador.dart` import), but required to instantiate `ServicioEcualizador()` per the design's data-flow pseudocode. This is a direct, unavoidable consequence of implementing 3.2 as specified — not a deviation from the design's intent, just an omission in the task's explicit file-diff enumeration.
2. **`playFromMediaId` restructured to a single outer try/catch** (was: `fuente`-null guard outside try/catch, `reproducirPorMediaId` call inside try/catch). This was required because the design mandates the `eq_preset:` branch run BEFORE the `fuente`-null guard while still being covered by exception safety (Spec "Unknown or stale preset id": no unhandled exception may propagate). Functionally equivalent for the pre-existing `emisora:`/`grupo:` path — behavior for those ids is unchanged (the `fuente == null` check remains, just now inside the try block, and its `return` behavior on null is unaffected).
No other deviations — implementation matches design (ADR-1 through ADR-6 all satisfied as specified).
## Issues Found
None.
## Workload / PR Boundary
- Mode: single PR (no chaining needed — actual diff came in at 339 insertions + 7 deletions = 346 changed lines, within the 300-420 forecast and under the 400-line budget)
- Current work unit: N/A — single delivery, all tasks in one pass
- Boundary: Starts from a clean baseline (34/34 pre-existing tests green) and ends with the full `Ecualizador` folder + preset-selection feature wired end-to-end, 46/46 tests green
- Estimated review budget impact: Medium-low — under the 400-line reviewer budget; changes are additive (new branches alongside existing `emisora:`/`grupo:` dispatch, no restructuring of unrelated logic)
## Status
21/21 tasks complete. Ready for sdd-verify.