diff --git a/openspec/changes/android-auto-favorite-groups/apply-progress.md b/openspec/changes/archive/2026-07-19-android-auto-favorite-groups/apply-progress.md similarity index 100% rename from openspec/changes/android-auto-favorite-groups/apply-progress.md rename to openspec/changes/archive/2026-07-19-android-auto-favorite-groups/apply-progress.md diff --git a/openspec/changes/archive/2026-07-19-android-auto-favorite-groups/archive-report.md b/openspec/changes/archive/2026-07-19-android-auto-favorite-groups/archive-report.md new file mode 100644 index 0000000..fe3f1a1 --- /dev/null +++ b/openspec/changes/archive/2026-07-19-android-auto-favorite-groups/archive-report.md @@ -0,0 +1,134 @@ +# Archive Report: android-auto-favorite-groups + +**Date**: 2026-07-19 +**Status**: PASS WITH WARNINGS → ARCHIVED +**Artifact Store**: hybrid (openspec + engram) + +## Change Summary + +**Change Name**: android-auto-favorite-groups +**Proposal**: Expose favorite groups (`GrupoFavoritos`) as browsable, non-playable sub-folders in the Android Auto browse tree under the existing `Favoritos` folder, leveraging the existing group data model without introducing new schema or phone-UI changes. + +**Verification Verdict**: PASS WITH WARNINGS +- **CRITICAL Issues**: 0 +- **WARNING Issues**: 1 (test-count report accuracy in apply-progress.md, corrected to 54/54 before archive) +- **SUGGESTION Issues**: 2 (non-blocking, low-priority) + +**Implementation Status**: Complete (git commit f368bcc on main, 291 changed lines across 5 files) + +## Artifacts Archived + +| Artifact | Location | Status | +|----------|----------|--------| +| proposal.md | openspec/changes/archive/2026-07-19-android-auto-favorite-groups/proposal.md | Archived | +| design.md | openspec/changes/archive/2026-07-19-android-auto-favorite-groups/design.md | Archived | +| tasks.md | openspec/changes/archive/2026-07-19-android-auto-favorite-groups/tasks.md | Archived | +| apply-progress.md | openspec/changes/archive/2026-07-19-android-auto-favorite-groups/apply-progress.md | Archived | +| verify-report.md | openspec/changes/archive/2026-07-19-android-auto-favorite-groups/verify-report.md | Archived | +| delta spec | openspec/changes/archive/2026-07-19-android-auto-favorite-groups/specs/android-auto-media/spec.md | Archived | + +## Spec Synchronization + +### Main Spec Updated +**File**: openspec/specs/android-auto-media/spec.md + +**Changes Applied**: +1. **ADDED**: "Favorite Group Sub-Folders" requirement (3 scenarios) + - Car requests Favoritos folder and groups exist + - Car requests a group folder's stations + - Car requests an unknown or stale group id + +2. **ADDED**: "Empty Favorite Group Handling" requirement (2 scenarios) + - Empty group folder is browsed (if shown) + - No user-facing dead end + +3. **MODIFIED**: "Browsable Media Tree" requirement + - Updated description to note `Favoritos` MAY contain favorite-group sub-folders + - Changed precedent note from "Previously: no subtitle requirement..." to "Previously: `Favoritos` was a flat folder of playable station items only, with no sub-folder nesting." + - Added new scenario: "Ungrouped station appears exactly as before (regression guard)" + +**Summary**: 2 new requirements (7 scenarios total), 1 existing requirement enhanced with 1 new scenario, maintaining full backward-compatibility for ungrouped stations. + +## Implementation Verification + +**Verification Command**: +``` +flutter test test/servicios/navegacion_auto_test.dart test/estado/estado_radio_test.dart --concurrency=1 --timeout=60s +``` + +**Result**: PASS — 54/54 tests +- navegacion_auto_test.dart: 33/33 passed +- estado_radio_test.dart: 21/21 passed + +**Files Changed** (5 total, 291 changed lines): +1. `lib/servicios/navegacion_auto.dart` — Added `grupos()` source, snapshot field, `esCarpetaGrupo`, `itemGrupo`, `carpetasFavoritos`, `hijosGrupo` methods +2. `lib/servicios/servicio_audio.dart` — Added two new branches in `getChildren` dispatch +3. `lib/estado/estado_radio.dart` — Push `gruposFavoritos` into Auto snapshot +4. `test/servicios/navegacion_auto_test.dart` — Added 8 new test groups covering pure builders +5. `test/estado/estado_radio_test.dart` — Updated fake, added snapshot-push assertion + +**Regression Guard**: Regression parity test confirms byte-identical output for `Favoritos` folder when no custom groups exist — the no-custom-groups case reuses the existing `hijos()` path, not a parallel reimplementation. + +## Issues Summary + +### WARNING (1) +1. **Test-count report accuracy** (apply-progress.md) + - apply-progress.md originally claimed 55/55 tests (34/34 + 21/21) + - Actual: 54/54 tests (33/33 + 21/21) + - Status: Corrected before archive + - Impact: Cosmetic documentation defect only; no missing coverage + +### SUGGESTIONS (2) +1. **50-group cap boundary test** — uses bulk-of-60 style, not exact 50-vs-51 boundary; consistent with pre-existing precedent +2. **Mixed browsable+playable children car-UI rendering** — flagged as open question, requires DHU/hardware testing; carry forward as known residual risk + +## Rollback Information + +**Rollback is straightforward**: +- Revert the `grupos()` method and snapshot field from `FuenteEmisorasAuto` +- Revert the two new branches in `servicio_audio.dart` `getChildren` +- Revert the `_fuenteAuto?.actualizarSnapshot(grupos: ...)` push in `estado_radio.dart` +- All changes are additive; pre-existing 3-folder tree, group CRUD, phone UI, and SQLite schema remain untouched + +## Verification Compliance + +| Requirement | Status | Evidence | +|-------------|--------|----------| +| Favorite Group Sub-Folders spec compliance | PASS | carpetasFavoritos test cases (459-524), esCarpetaGrupo prefix checks | +| grupo: collision-free vs emisora: | PASS | resolver collision test (557-565) confirms no-op for grupo: prefix | +| Empty group handling | PASS | empty-group fixture test (484-505) confirms omission from tree | +| Regression guard (ungrouped stations) | PASS | byte-identical output via direct hijos() reuse (navegacion_auto.dart:234, test 429-457) | +| Design coherence | PASS | All 7 design decisions verified against implementation | +| Task completion | PASS | All 22 tasks marked complete, confirmed by code inspection | +| TDD cycle compliance | PASS | 54/54 tests passing, pure functions fully unit-tested | + +## SDD Cycle Closure + +This change completes the full SDD cycle for android-auto-favorite-groups: + +1. ✅ **Exploration**: Investigation 1 (Approach 1 — data-model reuse) +2. ✅ **Proposal**: Defined scope, capabilities, risks, rollback +3. ✅ **Spec**: Delta spec with 2 new + 1 modified requirements +4. ✅ **Design**: 4 architecture decisions, pure-function seam strategy +5. ✅ **Tasks**: 5 phases, 22 tasks (foundation → core → wiring → testing → cleanup) +6. ✅ **Apply**: Strict TDD, single PR, 291 changed lines, 54/54 tests green +7. ✅ **Verify**: Fresh adversarial pass, PASS WITH WARNINGS (1 cosmetic, 2 suggestions) +8. ✅ **Archive**: Specs merged, change folder moved, audit trail created + +## Next Steps + +- **No follow-up required** — change is complete and ready for release +- **Known residual risk**: Mixed browsable+playable children rendering on real car UI — schedule DHU/hardware verification before launch if not yet done +- **Optional enhancement**: Consider boundary test for 50-vs-51 group cap as a future refinement (non-blocking) + +## Persistence + +This archive report is persisted to: +1. **Filesystem**: openspec/changes/archive/2026-07-19-android-auto-favorite-groups/archive-report.md (this file) +2. **Engram**: topic_key `sdd/android-auto-favorite-groups/archive-report` (via mem_save, architecture type) + +--- + +**Archive Date**: 2026-07-19 +**Archived By**: sdd-archive executor +**Status**: COMPLETE diff --git a/openspec/changes/android-auto-favorite-groups/design.md b/openspec/changes/archive/2026-07-19-android-auto-favorite-groups/design.md similarity index 100% rename from openspec/changes/android-auto-favorite-groups/design.md rename to openspec/changes/archive/2026-07-19-android-auto-favorite-groups/design.md diff --git a/openspec/changes/android-auto-favorite-groups/proposal.md b/openspec/changes/archive/2026-07-19-android-auto-favorite-groups/proposal.md similarity index 100% rename from openspec/changes/android-auto-favorite-groups/proposal.md rename to openspec/changes/archive/2026-07-19-android-auto-favorite-groups/proposal.md diff --git a/openspec/changes/android-auto-favorite-groups/specs/android-auto-media/spec.md b/openspec/changes/archive/2026-07-19-android-auto-favorite-groups/specs/android-auto-media/spec.md similarity index 100% rename from openspec/changes/android-auto-favorite-groups/specs/android-auto-media/spec.md rename to openspec/changes/archive/2026-07-19-android-auto-favorite-groups/specs/android-auto-media/spec.md diff --git a/openspec/changes/android-auto-favorite-groups/tasks.md b/openspec/changes/archive/2026-07-19-android-auto-favorite-groups/tasks.md similarity index 100% rename from openspec/changes/android-auto-favorite-groups/tasks.md rename to openspec/changes/archive/2026-07-19-android-auto-favorite-groups/tasks.md diff --git a/openspec/changes/android-auto-favorite-groups/verify-report.md b/openspec/changes/archive/2026-07-19-android-auto-favorite-groups/verify-report.md similarity index 100% rename from openspec/changes/android-auto-favorite-groups/verify-report.md rename to openspec/changes/archive/2026-07-19-android-auto-favorite-groups/verify-report.md diff --git a/openspec/specs/android-auto-media/spec.md b/openspec/specs/android-auto-media/spec.md index e5ed21c..848b31e 100644 --- a/openspec/specs/android-auto-media/spec.md +++ b/openspec/specs/android-auto-media/spec.md @@ -18,10 +18,52 @@ The app MUST declare itself as a media app to Android Auto so the car head unit - AND the `AndroidManifest.xml` `` block declares `` - AND PluriWave appears in the car's media app list +### Requirement: Favorite Group Sub-Folders + +The Android Auto browse tree MUST expose favorite groups (`GrupoFavoritos`, as already modeled by `Emisora.grupoFavoritosId` and surfaced by `EstadoRadio.gruposFavoritos`) as browsable, non-playable sub-folders reachable from the existing `Favoritos` folder, without altering the 3 root folders (`Favoritos`, `Todas las emisoras`, `Mis emisoras`). + +#### Scenario: Car requests the Favoritos folder and groups exist + +- GIVEN the user has one or more favorite groups with at least one member station each +- WHEN `getChildren` is called with the `Favoritos` folder id +- THEN it returns one non-playable folder `MediaItem` per surfaced group, in addition to (or instead of, per the design's structural decision) any ungrouped favorite stations +- AND each group folder's id follows a browsable `grupo:` scheme distinct from the `emisora:` playable-item scheme + +#### Scenario: Car requests a group folder's stations + +- GIVEN a favorite group folder with id `grupo:` was returned under `Favoritos` +- WHEN `getChildren` is called with that `grupo:` folder id +- THEN it returns the playable `MediaItem`s for exactly the stations whose `Emisora.grupoFavoritosId` matches `` +- AND those items are sorted and capped using the same ordering and item-count rules already applied to the other folders (`ordenarEmisoras`, 50-item cap) +- AND selecting one of those items plays the corresponding station via the existing `emisora:` playback path, unchanged + +#### Scenario: Car requests an unknown or stale group id + +- GIVEN a `grupo:` id that does not match any group known to the current snapshot +- WHEN `getChildren` is called with that id +- THEN it returns an empty list, not an error + +### Requirement: Empty Favorite Group Handling + +The system MUST produce a browsable tree that never presents a user-selectable folder promising content it cannot deliver: for any favorite group with zero member stations, the tree MUST either omit that group's folder from `Favoritos`'s children, or include it and return an empty (not erroring) child list when browsed. The specific choice between omitting empty-group folders and showing-but-empty, and any related folder-count/flat-vs-nested structural decision for `Favoritos`, is deferred to `sdd-design`; whichever mechanism design selects MUST satisfy both scenarios below. + +#### Scenario: Empty group folder is browsed (if shown) + +- GIVEN a favorite group has zero member stations and the design's chosen mechanism surfaces it as a folder under `Favoritos` +- WHEN `getChildren` is called with that group's `grupo:` +- THEN it returns an empty list, not an error + +#### Scenario: No user-facing dead end + +- GIVEN the full set of favorite groups, including any empty ones +- WHEN the `Favoritos` folder is browsed and then each of its returned children is browsed +- THEN no returned folder child ever throws, hangs, or surfaces a driver-facing error state +- AND the car head unit's total folder/item count presented under `Favoritos` remains within the driver-distraction-safe bounds design establishes + ### Requirement: Browsable Media Tree -`getChildren` MUST return a browsable tree rooted at `AudioService.browsableRootId`, organized into non-playable folders (Favoritos, Todas las emisoras, Mis emisoras) containing playable station items. Playable station items SHOULD carry an audio-quality subtitle when known. -(Previously: no subtitle requirement; folders and playable items were otherwise unchanged.) +`getChildren` MUST return a browsable tree rooted at `AudioService.browsableRootId`, organized into non-playable folders (Favoritos, Todas las emisoras, Mis emisoras) containing playable station items. Playable station items SHOULD carry an audio-quality subtitle when known. The `Favoritos` folder additionally MAY contain non-playable favorite-group sub-folders (see "Favorite Group Sub-Folders"); `Todas las emisoras` and `Mis emisoras` remain flat, unchanged by this capability. +(Previously: `Favoritos` was a flat folder of playable station items only, with no sub-folder nesting.) #### Scenario: Car requests the root @@ -54,6 +96,13 @@ The app MUST declare itself as a media app to Android Auto so the car head unit - THEN `displaySubtitle` SHALL omit the quality hint gracefully (no subtitle, or a subtitle with no quality fragment) - AND the subtitle MUST NOT render literal placeholder text such as "null kbps" or "null · null" +#### Scenario: Ungrouped station appears exactly as before (regression guard) + +- GIVEN a station's `Emisora.grupoFavoritosId` equals `GrupoFavoritos.sinAsignarId` (`'sin_asignar'`, the default when no group is assigned) +- WHEN the `Favoritos`, `Todas las emisoras`, or `Mis emisoras` folders are browsed +- THEN that station appears as a playable `emisora:` item in exactly the same folder(s), position (subject to existing sort rules), title, art, and subtitle as it did before favorite-group folders were introduced +- AND its presence and shape are unaffected by the existence, emptiness, or content of any favorite group + ### Requirement: Playable Item Metadata Every playable `MediaItem` (station) MUST include a non-empty `title` and a loadable `artUri`. Stations without a logo MUST fall back to on-brand artwork, and stations whose logo URL cannot actually be loaded MUST degrade to the same on-brand fallback instead of rendering broken or blank art. The fallback MUST be visually consistent with the phone UI's per-station rotation rather than a generic launcher-icon copy.