docs(openspec): add SDD artifact trail for recent alarm and EQ changes
Persist the exploration, proposal, spec, design, tasks, and verify/archive reports produced during the multi-device EQ, alarm-countdown, and notification-visual-polish SDD cycles.
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
# Tasks: EQ Device Auto-Switch UX
|
||||
|
||||
## Review Workload Forecast
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Estimated changed lines | 380–520 |
|
||||
| 400-line budget risk | High |
|
||||
| Chained PRs recommended | Yes |
|
||||
| Suggested split | PR 1 (service + state) → PR 2 (UI + l10n) → PR 3 (widget tests) |
|
||||
| Delivery strategy | ask-on-risk |
|
||||
| Chain strategy | pending |
|
||||
|
||||
Decision needed before apply: Yes
|
||||
Chained PRs recommended: Yes
|
||||
Chain strategy: pending
|
||||
400-line budget risk: High
|
||||
|
||||
### Suggested Work Units
|
||||
|
||||
| Unit | Goal | Likely PR | Notes |
|
||||
|------|------|-----------|-------|
|
||||
| 1 | Bug fix + rename persistence layer (service + state) | PR 1 | base = main; ~150 lines; no UI dependency |
|
||||
| 2 | Settings UI rewrite + edit modal + l10n | PR 2 | base = PR 1; ~220 lines; depends on Unit 1 APIs |
|
||||
| 3 | Widget tests for modal + integration tests | PR 3 | base = PR 2; ~120 lines; closes test coverage gap |
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Foundation — Service Layer (PR 1 slice)
|
||||
|
||||
> Strict TDD: RED first, then GREEN, then REFACTOR for each task group.
|
||||
|
||||
- [ ] 1.1 **RED** — Write failing unit tests in `test/servicios/servicio_ecualizador_test.dart` for `cargarNombresDispositivos()` returning empty map when SP key absent
|
||||
- [ ] 1.2 **RED** — Add failing test: `guardarNombresDispositivos({id: name})` writes JSON to `eq_nombres_dispositivos_v1` SP key
|
||||
- [ ] 1.3 **RED** — Add failing test: round-trip save → load returns same map
|
||||
- [ ] 1.4 **GREEN** — Add `_keyNombresDispositivos = 'eq_nombres_dispositivos_v1'` to `lib/servicios/servicio_ecualizador.dart`
|
||||
- [ ] 1.5 **GREEN** — Implement `cargarNombresDispositivos()` and `guardarNombresDispositivos()` in `ServicioEcualizador` (reuse existing `_leerMapa`/`_guardarMapa` JSON pattern adapted for `Map<String, String>`)
|
||||
- [ ] 1.6 **GREEN** — Add `nombresDispositivos` field to `ConfiguracionEcualizador` data class; include in `cargar()` and wire into `ServicioEcualizador.cargar()` call
|
||||
- [ ] 1.7 **REFACTOR** — Ensure no duplicate SP read calls; confirm `cargar()` returns `nombresDispositivos` in a single pass; update any `copyWith` if present
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Core — State Layer Bug Fix + Rename API (PR 1 slice)
|
||||
|
||||
- [ ] 2.1 **RED** — Write failing unit tests in `test/estado/estado_ecualizador_test.dart`: after `cargarPersistido()` with `eqMultiDeviceEnabled=true` and fake device service returning `'bt_a2dp:AA:BB'`, assert `dispositivoActualId == 'bt_a2dp:AA:BB'` (spec scenario: correct preset at startup)
|
||||
- [ ] 2.2 **RED** — Add failing test: first-seen device is bootstrapped with copy of resolved preset after `cargarPersistido()` (spec scenario: first-seen device bootstrapped at startup)
|
||||
- [ ] 2.3 **RED** — Add failing test: `obtenerDispositivoActual()` throws → `dispositivoActualId` falls back to `'builtin_speaker'` (spec scenario: graceful failure)
|
||||
- [ ] 2.4 **RED** — Add failing test: `eqMultiDeviceEnabled=false` → `obtenerDispositivoActual()` never called (spec scenario: toggle off)
|
||||
- [ ] 2.5 **GREEN** — In `lib/estado/estado_ecualizador.dart`, inside `cargarPersistido()`, after `_configurarSuscripcionDispositivo()`, add `try/catch` `await obtenerDispositivoActual()` call that seeds `_dispositivoActualId` then calls `_onDispositivoCambiado()` to trigger bootstrap + preset resolution
|
||||
- [ ] 2.6 **RED** — Add failing tests for rename API: `renombrarDispositivo(id, nombre)` writes to `_nombresDispositivos`, calls `notifyListeners()`; empty string is no-op (spec scenarios: rename persists, empty name not persisted)
|
||||
- [ ] 2.7 **RED** — Add failing test: `obtenerNombreDispositivo(id)` returns stored name or empty string
|
||||
- [ ] 2.8 **RED** — Add failing tests for `nombreVisible(deviceId, platformName)` fallback chain: custom > platform > id (all three spec scenarios)
|
||||
- [ ] 2.9 **GREEN** — Add `_nombresDispositivos` map field; implement `renombrarDispositivo()`, `obtenerNombreDispositivo()`, `nombreVisible()`, and `get nombresDispositivos` getter in `lib/estado/estado_ecualizador.dart`; wire to service persist on rename
|
||||
- [ ] 2.10 **REFACTOR** — Extract fallback chain to a single helper, ensure `_nombresDispositivos` is loaded from `ConfiguracionEcualizador.nombresDispositivos` in `cargarPersistido()`
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Integration — Settings UI Rewrite (PR 2 slice)
|
||||
|
||||
- [ ] 3.1 **RED** — Write failing widget test in `test/pantallas/pantalla_ajustes_test.dart`: connection dot is present on active device row, absent on inactive rows (spec scenario: active device shows green indicator)
|
||||
- [ ] 3.2 **RED** — Add failing widget test: tapping a device row opens a bottom sheet with a `TextField` pre-filled with resolved display name and an `EcualizadorWidget` (spec scenario: modal opens on device row tap)
|
||||
- [ ] 3.3 **RED** — Add failing widget test: `EcualizadorWidget` in modal is reachable by scrolling on small viewport (spec scenario: modal is scrollable)
|
||||
- [ ] 3.4 **GREEN** — Rewrite `_SeccionEcualizadorAvanzado` device rows in `lib/pantallas/pantalla_ajustes.dart`: add green dot `Icon` gated on `entry.key == eq.dispositivoActualId`; display name via `eq.nombreVisible(id, device.nombre)`; add `onTap` handler
|
||||
- [ ] 3.5 **GREEN** — Implement `_DialogoEdicionDispositivo` `StatefulWidget` in `lib/pantallas/pantalla_ajustes.dart`: `showModalBottomSheet` with `showDragHandle: true`, `isScrollControlled: true`, `TextField` pre-filled with display name, embedded `EcualizadorWidget`, confirm/dismiss logic calling `eq.renombrarDispositivo(id, name)`
|
||||
- [ ] 3.6 **RED** — Add failing widget test: renaming in modal and confirming calls `renombrarDispositivo`; device row reflects new name after close (spec scenario: rename confirmed in modal persists)
|
||||
- [ ] 3.7 **RED** — Add failing widget test: dismissing modal without confirming leaves name unchanged (spec scenario: modal dismiss without save)
|
||||
- [ ] 3.8 **GREEN** — Wire dismiss-without-save path (no `renombrarDispositivo` call on drag-dismiss)
|
||||
- [ ] 3.9 **REFACTOR** — Extract modal into its own file if `pantalla_ajustes.dart` exceeds 400 lines; ensure fallback chain is called from `EstadoEcualizador`, not inlined in UI
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Localisation (PR 2 slice)
|
||||
|
||||
- [ ] 4.1 Add ~5 new ARB keys to `lib/l10n/app_en.arb`: `eqDeviceEditTitle`, `eqDeviceNameLabel`, `eqDeviceNameHint`, `eqDeviceNameConfirm`, `eqDeviceConnected`
|
||||
- [ ] 4.2 Translate all 5 keys in the 12 remaining locale files (`app_es.arb`, `app_pt.arb`, `app_fr.arb`, `app_de.arb`, `app_it.arb`, `app_ja.arb`, `app_ko.arb`, `app_zh.arb`, `app_ru.arb`, `app_ar.arb`, `app_hi.arb`, `app_tr.arb`)
|
||||
- [ ] 4.3 Run `flutter gen-l10n` and verify no ARB parse errors; confirm generated `.g.dart` includes all new keys
|
||||
- [ ] 4.4 Replace any hardcoded strings in `_SeccionEcualizadorAvanzado` and `_DialogoEdicionDispositivo` with l10n references
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Cleanup + Verification (PR 3 slice)
|
||||
|
||||
- [ ] 5.1 Run full test suite; confirm all RED tasks from Phases 1–3 are GREEN
|
||||
- [ ] 5.2 Verify spec scenario coverage: cross-check each scenario in `openspec/changes/eq-device-autoswitch-ux/spec.md` against a corresponding test (add any gap tests)
|
||||
- [ ] 5.3 Run `flutter analyze`; resolve any new lint warnings introduced by this change
|
||||
- [ ] 5.4 Remove any `TODO`/`FIXME` comments introduced during GREEN phase
|
||||
- [ ] 5.5 Confirm `nombresDispositivos` omission from export schema v4 is documented in design open questions (no code change needed — already deferred per proposal)
|
||||
Reference in New Issue
Block a user