7.5 KiB
7.5 KiB
Tasks: Android Auto Favorite Groups
Strict TDD active for Dart layers. Behavioral task = RED (failing test) -> GREEN
(minimal impl) -> REFACTOR. flutter build/flutter run/flutter analyze/
flutter gen-l10n MUST NOT be executed in this environment (hang) — marked
[DEVIATION], same precedent as openspec/changes/archive/2026-07-19-auto-media-art-quality/tasks.md.
Review Workload Forecast
| Field | Value |
|---|---|
| Estimated changed lines | 280-370 |
| 400-line budget risk | Medium |
| Chained PRs recommended | No |
| Suggested split | Single PR; fallback 2-way split if apply exceeds budget: PR 1 = Phase 1-2 (builders+tests), PR 2 = Phase 3-5 (wiring+cleanup) |
| Delivery strategy | ask-on-risk (default, not overridden by caller) |
| Chain strategy | pending |
Decision needed before apply: No Chained PRs recommended: No Chain strategy: pending 400-line budget risk: Medium
Suggested Work Units
| Unit | Goal | Likely PR | Notes |
|---|---|---|---|
| 1 | Snapshot seam + pure builders (Phase 1-2) + tests | PR 1 | Independent of dispatch wiring; fully unit-testable |
| 2 | Handler/state wiring (Phase 3-5) | PR 2 (fallback only) | Depends on Unit 1; only split out if diff runs over 400 |
Baseline (verified against live code, not spec/design prose)
lib/servicios/navegacion_auto.dart:86-107FuenteEmisorasAuto— nogrupos();actualizarSnapshotlacks agruposparam.lib/servicios/navegacion_auto.dart:112-185ConstructorArbolAuto— no group consts/builders; noGrupoFavoritosimport.lib/servicios/navegacion_auto.dart:229-323FuenteEmisorasAutoLocal— no grupos snapshot/read;favoritos()try/catch pattern at 257-268 is the template to mirror.lib/servicios/servicio_audio.dart:730-796getChildren/_listaParaCarpeta—favoritosid routes through flathijos(); nogrupo:branch.lib/estado/estado_radio.dart:346-349cargarGruposFavoritos()— no_fuenteAuto?.actualizarSnapshot(grupos: ...)push (siblingcargarFavoritos()at line 342 does push).lib/servicios/servicio_favoritos.dart:166-173obtenerGrupos()confirmedorden ASC, nombre ASC;sin_asignarauto-inserted + protected (lines 83, 142-153).lib/pantallas/pantalla_favoritos.dart:58-99confirms the UI iteratesgruposin query order with no re-sort — design's "phone order" claim holds.- Contract gap found in design.md:
carpetasFavoritosMUST routesin_asignarleaves through the existinghijos(idFavoritos, emisoras: ...)path (identical sort+cap+map) to guarantee byte-identical output when no custom groups exist. design.md's Interfaces section doesn't spell this out explicitly, but it's required by the regression scenario and Testing Strategy row 1 — tasks below make it explicit. - No existing unit tests exercise
FuenteEmisorasAutoLocaldirectly (only the hand-written_FakeFuenteEmisorasAuto);grupos()follows the same untested-Local-impl precedent as siblingfavoritos()/misEmisoras()— no new SQLite-mocking test added for it.
Phase 1: Foundation — snapshot seam + media-id scheme
- 1.1 [RED]
navegacion_auto_test.dart:esCarpetaGrupotest table —'grupo:g1'→true,'favoritos'/'emisora:x'/''→false. (Design "media-id scheme") - 1.2 [GREEN]
navegacion_auto.dart: add_prefijoGrupo='grupo:',_maxGruposPorFavoritos=50consts;esCarpetaGrupo(id) => id.startsWith(_prefijoGrupo). - 1.3
navegacion_auto.dart: addimport '../modelos/grupo_favoritos.dart'; extendFuenteEmisorasAuto.grupos()(abstract) +actualizarSnapshot({..., List<GrupoFavoritos>? grupos}). - 1.4
navegacion_auto.dart:FuenteEmisorasAutoLocal— add_snapshotGrupos,grupos()override (snapshot-or-_favoritosServicio.obtenerGrupos(), try/catch→[], mirrors lines 257-268), wiregruposparam inactualizarSnapshot. - 1.5
navegacion_auto_test.dart: update_FakeFuenteEmisorasAuto— implementgrupos()(defaultconst []) +actualizarSnapshotgrupos param, so existing tests keep compiling.
Phase 2: Core Implementation — pure tree builders
- 2.1 [RED]
itemGrupotest — id'grupo:<id>',playable:false,title == g.nombre. - 2.2 [GREEN]
navegacion_auto.dart:itemGrupo(g) => _carpeta('$_prefijoGrupo${g.id}', g.nombre)(reuses existing_carpetahelper, line 141). - 2.3 [RED]
carpetasFavoritosregression:grupos=[sin_asignar], no custom groups → output equals currenthijos(idFavoritos, emisoras: favoritos)(same items, same order). Most important test in this change — Spec "Ungrouped station appears exactly as before". - 2.4 [RED]
carpetasFavoritos: 1+ non-empty custom group →grupo:<id>folders (playable:false, source order) +sin_asignarleaves, folders first. - 2.5 [RED]
carpetasFavoritos: empty custom group omitted from output;sin_asignarnever becomes its own folder even when present ingrupos. - 2.6 [RED]
carpetasFavoritos: >50 eligible non-empty groups truncated to 50. - 2.7 [GREEN] implement
carpetasFavoritos({grupos, favoritos}): filtergruposto non-esSinAsignarwith ≥1 member (viafavoritos.any),.take(_maxGruposPorFavoritos).map(itemGrupo); appendhijos(idFavoritos, emisoras: favoritos.where(sin_asignar)). Satisfies 2.3-2.6. - 2.8 [RED]
hijosGrupo: filters bygrupoFavoritosId, reusesordenarEmisoras+50 cap (mirror existing cap test ~line 334); unknown/stale/malformed id →[]. - 2.9 [GREEN] implement
hijosGrupo(grupoMediaId, {favoritos}): strip_prefijoGrupodefensively (mirrorresolver), filter, sort viaordenarEmisoras,.take(_maxItemsPorCarpeta), mapitemEmisora. - 2.10 [RED] assert
resolver('grupo:g1', universo) == null— collision-free vsemisora:scheme. (Spec "Media Item Resolution")
Phase 3: Integration / Wiring
- 3.1
servicio_audio.dart:730-748getChildren: branchparentMediaId == idFavoritos→carpetasFavoritos(grupos: await fuente.grupos(), favoritos: await fuente.favoritos()); branchconstructor.esCarpetaGrupo(parentMediaId)→hijosGrupo(parentMediaId, favoritos: await fuente.favoritos()); keep existingtodas/mis_emisoraspath and outer try/catch unchanged. - 3.2
servicio_audio.dart:791-795_listaParaCarpeta: remove the now-unreachableidFavoritosswitch case (handled by 3.1). - 3.3
estado_radio.dart:346-349cargarGruposFavoritos(): add_fuenteAuto?.actualizarSnapshot(grupos: _gruposFavoritos);(mirrors line 342'sfavoritos:push).
Phase 4: Testing / Verification
- 4.1 Run
flutter test test/servicios/navegacion_auto_test.dart— full green. - 4.2 Run any
estado_radio/servicio_audiotest files if they exist; else note as manual follow-up (no existing suite found for these two files as of this writing). (Updated:test/estado/estado_radio_test.dart's_FuenteEmisorasAutoEspianeededgrupos()wiring to keep compiling; added an assertion proving the newactualizarSnapshot(grupos:)push — full file green, 21/21.) - 4.3 [DEVIATION]
flutter build/flutter run/flutter analyze/flutter gen-l10n— DO NOT RUN in this environment (hangs). Manual static review instead: no unused symbols,grupo:/emisora:prefixes non-colliding by inspection, no leftover references to the removed_listaParaCarpetacase. Real run recommended before merge/CI.
Phase 5: Cleanup
- 5.1 [REFACTOR]
navegacion_auto.dart: doc-comment new public members per the file's existing Design-decision-linking style; confirm full test suite still green.