docs(openspec): archive android-auto-favorite-groups
Merges its delta requirements into the android-auto-media base spec.
This commit is contained in:
@@ -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:<id> collision-free vs emisora:<uuid> | 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
|
||||||
@@ -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` `<application>` block declares `<meta-data android:name="com.google.android.gms.car.application" android:resource="@xml/automotive_app_desc"/>`
|
- AND the `AndroidManifest.xml` `<application>` block declares `<meta-data android:name="com.google.android.gms.car.application" android:resource="@xml/automotive_app_desc"/>`
|
||||||
- AND PluriWave appears in the car's media app list
|
- 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:<id>` scheme distinct from the `emisora:<uuid>` playable-item scheme
|
||||||
|
|
||||||
|
#### Scenario: Car requests a group folder's stations
|
||||||
|
|
||||||
|
- GIVEN a favorite group folder with id `grupo:<id>` was returned under `Favoritos`
|
||||||
|
- WHEN `getChildren` is called with that `grupo:<id>` folder id
|
||||||
|
- THEN it returns the playable `MediaItem`s for exactly the stations whose `Emisora.grupoFavoritosId` matches `<id>`
|
||||||
|
- 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:<uuid>` playback path, unchanged
|
||||||
|
|
||||||
|
#### Scenario: Car requests an unknown or stale group id
|
||||||
|
|
||||||
|
- GIVEN a `grupo:<id>` 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:<id>`
|
||||||
|
- 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
|
### 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.
|
`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: no subtitle requirement; folders and playable items were otherwise unchanged.)
|
(Previously: `Favoritos` was a flat folder of playable station items only, with no sub-folder nesting.)
|
||||||
|
|
||||||
#### Scenario: Car requests the root
|
#### 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)
|
- 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"
|
- 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:<uuid>` 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
|
### 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.
|
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.
|
||||||
|
|||||||
Reference in New Issue
Block a user