Files
FreeTLab 066fedb7bc docs(openspec): archive android-auto-favorite-groups
Merges its delta requirements into the android-auto-media base spec.
2026-07-19 13:47:39 +02:00

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-107 FuenteEmisorasAuto — no grupos(); actualizarSnapshot lacks a grupos param.
  • lib/servicios/navegacion_auto.dart:112-185 ConstructorArbolAuto — no group consts/builders; no GrupoFavoritos import.
  • lib/servicios/navegacion_auto.dart:229-323 FuenteEmisorasAutoLocal — no grupos snapshot/read; favoritos() try/catch pattern at 257-268 is the template to mirror.
  • lib/servicios/servicio_audio.dart:730-796 getChildren/_listaParaCarpetafavoritos id routes through flat hijos(); no grupo: branch.
  • lib/estado/estado_radio.dart:346-349 cargarGruposFavoritos() — no _fuenteAuto?.actualizarSnapshot(grupos: ...) push (sibling cargarFavoritos() at line 342 does push).
  • lib/servicios/servicio_favoritos.dart:166-173 obtenerGrupos() confirmed orden ASC, nombre ASC; sin_asignar auto-inserted + protected (lines 83, 142-153).
  • lib/pantallas/pantalla_favoritos.dart:58-99 confirms the UI iterates grupos in query order with no re-sort — design's "phone order" claim holds.
  • Contract gap found in design.md: carpetasFavoritos MUST route sin_asignar leaves through the existing hijos(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 FuenteEmisorasAutoLocal directly (only the hand-written _FakeFuenteEmisorasAuto); grupos() follows the same untested-Local-impl precedent as sibling favoritos()/misEmisoras() — no new SQLite-mocking test added for it.

Phase 1: Foundation — snapshot seam + media-id scheme

  • 1.1 [RED] navegacion_auto_test.dart: esCarpetaGrupo test table — 'grupo:g1'→true, 'favoritos'/'emisora:x'/''→false. (Design "media-id scheme")
  • 1.2 [GREEN] navegacion_auto.dart: add _prefijoGrupo='grupo:', _maxGruposPorFavoritos=50 consts; esCarpetaGrupo(id) => id.startsWith(_prefijoGrupo).
  • 1.3 navegacion_auto.dart: add import '../modelos/grupo_favoritos.dart'; extend FuenteEmisorasAuto.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), wire grupos param in actualizarSnapshot.
  • 1.5 navegacion_auto_test.dart: update _FakeFuenteEmisorasAuto — implement grupos() (default const []) + actualizarSnapshot grupos param, so existing tests keep compiling.

Phase 2: Core Implementation — pure tree builders

  • 2.1 [RED] itemGrupo test — id 'grupo:<id>', playable:false, title == g.nombre.
  • 2.2 [GREEN] navegacion_auto.dart: itemGrupo(g) => _carpeta('$_prefijoGrupo${g.id}', g.nombre) (reuses existing _carpeta helper, line 141).
  • 2.3 [RED] carpetasFavoritos regression: grupos=[sin_asignar], no custom groups → output equals current hijos(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_asignar leaves, folders first.
  • 2.5 [RED] carpetasFavoritos: empty custom group omitted from output; sin_asignar never becomes its own folder even when present in grupos.
  • 2.6 [RED] carpetasFavoritos: >50 eligible non-empty groups truncated to 50.
  • 2.7 [GREEN] implement carpetasFavoritos({grupos, favoritos}): filter grupos to non-esSinAsignar with ≥1 member (via favoritos.any), .take(_maxGruposPorFavoritos).map(itemGrupo); append hijos(idFavoritos, emisoras: favoritos.where(sin_asignar)). Satisfies 2.3-2.6.
  • 2.8 [RED] hijosGrupo: filters by grupoFavoritosId, reuses ordenarEmisoras+50 cap (mirror existing cap test ~line 334); unknown/stale/malformed id → [].
  • 2.9 [GREEN] implement hijosGrupo(grupoMediaId, {favoritos}): strip _prefijoGrupo defensively (mirror resolver), filter, sort via ordenarEmisoras, .take(_maxItemsPorCarpeta), map itemEmisora.
  • 2.10 [RED] assert resolver('grupo:g1', universo) == null — collision-free vs emisora: scheme. (Spec "Media Item Resolution")

Phase 3: Integration / Wiring

  • 3.1 servicio_audio.dart:730-748 getChildren: branch parentMediaId == idFavoritoscarpetasFavoritos(grupos: await fuente.grupos(), favoritos: await fuente.favoritos()); branch constructor.esCarpetaGrupo(parentMediaId)hijosGrupo(parentMediaId, favoritos: await fuente.favoritos()); keep existing todas/mis_emisoras path and outer try/catch unchanged.
  • 3.2 servicio_audio.dart:791-795 _listaParaCarpeta: remove the now-unreachable idFavoritos switch case (handled by 3.1).
  • 3.3 estado_radio.dart:346-349 cargarGruposFavoritos(): add _fuenteAuto?.actualizarSnapshot(grupos: _gruposFavoritos); (mirrors line 342's favoritos: 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_audio test 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 _FuenteEmisorasAutoEspia needed grupos() wiring to keep compiling; added an assertion proving the new actualizarSnapshot(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 _listaParaCarpeta case. 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.