docs(sdd): scope dart format to touched files in all verify commands

This commit is contained in:
2026-07-28 20:12:47 +02:00
parent 610016e935
commit e99ecffb0c
3 changed files with 46 additions and 22 deletions
@@ -675,7 +675,10 @@ Consistent with the existing Provider / `ChangeNotifier` architecture. No new me
## Test strategy per work unit
Runner: **`flutter test`**. `flutter analyze` and `dart format .` gate every commit. **`flutter build` is never run.**
Runner: **`flutter test`**. `flutter analyze` and a **scoped** `dart format` gate every commit never bare
`dart format .`, which reformats 27 unrelated pre-existing files on this machine and would break the empty-`git diff`
guards on `navegacion_auto.dart`, `servicio_ecualizador.dart` and `servicio_audio.dart`
(Engram `reference/dart-format-scope-hazard`, id 2511). **`flutter build` is never run.**
Strict TDD is ON: tests first, red before green, for every unit — with the single documented exception in WU7 step (a).
| WU | Tests written first | Must pass unmodified |
@@ -212,7 +212,9 @@ docs check before anything is specced.
7. **Characterise before refactoring shared code.** WU7 writes regression tests for the 8 existing station calls
*before* extracting transport out of `_get` — the refactor is only safe if the old behaviour is pinned first.
8. **Strict TDD throughout.** Tests are written before implementation for every WU. `flutter test` is the runner.
`flutter analyze` and `dart format .` gate every commit. **`flutter build` is never run.**
`flutter analyze` and a **scoped** `dart format` gate every commit — never bare `dart format .`, which reformats
27 unrelated pre-existing files (Engram `reference/dart-format-scope-hazard`, id 2511).
**`flutter build` is never run.**
9. **Translate once.** WU18 batches all new ARB keys at the end rather than paying 13-locale cost per slice.
## Affected Areas
@@ -333,7 +335,7 @@ Commits: conventional commits only. **No `Co-Authored-By`, no AI attribution, ev
- [ ] `PluriWaveTokens` exposes `listSurface`, `liveGreen`, and `offlineAccent`; no screen file introduces a new raw `Color(0x...)` literal.
- [ ] All 13 ARB files have identical key sets, with zero missing keys. `navHome` still exists as a key in all 13 — the rename changed values only.
- [ ] **No ARB file maps `navHome` to a "home" concept.** `app_en.arb` reads `"Listen"`, `app_es.arb` reads `"Escuchar"`, and `git diff` shows **13** changed `navHome` lines. A grep for the literals `"Inicio"`/`"Home"` is **not** sufficient — 11 locales express "home" in their own language (`Beranda`, `Accueil`, `Start`, `Início`, `ホーム`, `首页`, `Главная`, `الرئيسية`, `হোম`, `मुखपृष्ठ`, `Inizio`), so the check must be the 13-line diff, not a literal grep.
- [ ] `flutter test` green, `flutter analyze` clean, `dart format .` produces no diff.
- [ ] `flutter test` green, `flutter analyze` clean, and a **scoped** `dart format` over the files this change touched produces no diff. **Never bare `dart format .`** — verified on this machine, it reformats 27 pre-existing unrelated files (formatter version skew: Dart SDK 3.12.0 vs. the repo's committed formatting), which would break the empty-`git diff` guards below. See Engram `reference/dart-format-scope-hazard` (id 2511).
- [ ] The equalizer renders exactly **5** sliders. `assert(bandas.length == 5)` in `preset_ecualizador.dart` is unchanged, and `git diff` is empty for `lib/servicios/servicio_ecualizador.dart` and the band-application block at `lib/servicios/servicio_audio.dart:749-762`.
- [ ] `test/servicios/servicio_ecualizador_test.dart`, `test/estado/estado_ecualizador_test.dart`, and `test/servicios/servicio_audio_eq_reapply_test.dart` pass **unmodified**.
- [ ] The Países browser calls **`/json/countries`**. Grep confirms `countrycodes` appears nowhere in `lib/`.
+38 -19
View File
@@ -13,8 +13,24 @@
> needs. Tables and checklists are used throughout to keep it scannable despite the length.
> **WU12 does not exist** — the native EQ band-count spike was resolved before planning closed (Engram id 2498, "keep
> 5 bands"). Numbering skips 11→13 on purpose; this is not a gap.
> Strict TDD is ON. Runner: `flutter test`. `flutter analyze` and `dart format .` gate every commit.
> Strict TDD is ON. Runner: `flutter test`. `flutter analyze` and a **scoped** `dart format` gate every commit.
> **`flutter build` is never run.**
>
> ## ⚠️ NEVER run bare `dart format .`
>
> Verified on this machine: `dart format .` reformats **27 pre-existing files unrelated to any work unit**
> (`Formatted 141 files (27 changed)`). Cause is formatter version skew — this machine runs Dart SDK 3.12.0 while
> the repo's committed formatting came from another version. Running it bare would silently break four hard success
> criteria at once: the empty-`git diff` guards on `navegacion_auto.dart`, `servicio_ecualizador.dart` and
> `servicio_audio.dart`, plus "`pantalla_alarma_sonando_dismiss_guard_test.dart` passes unmodified".
>
> **Always scope the formatter to the files this work unit actually touched.** The verify commands below use:
>
> ```
> dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')
> ```
>
> Full detail: Engram `reference/dart-format-scope-hazard` (id 2511).
## Review Workload Forecast (per commit, not per PR)
@@ -147,7 +163,7 @@ Switching Without Push (provider only; consumption is WU5), Escuchar Tab Rename
**Commit**: `refactor(ajustes): split Settings AUDIO/EMISORAS into pushed detail screens`
**Depends on**: WU1
**Spec refs**: `app-navigation-shell` — Push-Chrome on Second-Level Screens (Settings detail screen scenario)
**Verify**: `flutter test test/pantallas/pantalla_ajustes_test.dart test/pantallas/ajustes/ && flutter analyze && dart format .`
**Verify**: `flutter test test/pantallas/pantalla_ajustes_test.dart test/pantallas/ajustes/ && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
**New tests**: one per new screen under `test/pantallas/ajustes/` (7 files, AUDIO + EMISORAS)
**Modified tests**: `test/pantallas/pantalla_ajustes_test.dart` (near-total rewrite)
@@ -179,7 +195,7 @@ modified. Do not attempt to slice under 450.
**Commit**: `refactor(ajustes): split remaining Settings sections into pushed screens`
**Depends on**: WU3a
**Spec refs**: `app-navigation-shell` — Push-Chrome on Second-Level Screens
**Verify**: `flutter test test/pantallas/pantalla_ajustes_test.dart test/pantallas/ajustes/ && flutter analyze && dart format .`
**Verify**: `flutter test test/pantallas/pantalla_ajustes_test.dart test/pantallas/ajustes/ && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
**New tests**: one per new screen under `test/pantallas/ajustes/` (5 files: Grabaciones, Música local, Idioma,
Backup, Info)
**Modified tests**: `test/pantallas/pantalla_ajustes_test.dart` (extended)
@@ -208,7 +224,7 @@ justification as WU3a.
**Depends on**: WU1
**Spec refs**: `favorites-organization` — Chip-Filtered Flat List, Drag-to-Reorder Within the Active Filter, Sort
Action Using Existing Criteria, Group Management Reachable from Favoritos, Custom-Station CTA Preserved
**Verify**: `flutter test test/pantallas/pantalla_favoritos_plural_test.dart && flutter analyze && dart format .`
**Verify**: `flutter test test/pantallas/pantalla_favoritos_plural_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
**Modified tests**: `test/pantallas/pantalla_favoritos_plural_test.dart` (+ new reorder scenario)
- [ ] 4.1 RED — update the test file: chip filter narrows the list ("Todas · N" + one chip per group); drag-to-reorder
@@ -227,7 +243,7 @@ Action Using Existing Criteria, Group Management Reachable from Favoritos, Custo
**Commit**: `feat(escuchar): replace discovery browser with embedded player and favorites grid`
**Depends on**: WU1, WU4
**Spec refs**: `app-navigation-shell` — Root-to-Root Switching Without Push
**Verify**: `flutter test test/pantallas/pantalla_inicio_test.dart test/pantallas/pantalla_inicio_rebuild_test.dart test/widgets/mini_reproductor_configurar_test.dart && flutter analyze && dart format .`
**Verify**: `flutter test test/pantallas/pantalla_inicio_test.dart test/pantallas/pantalla_inicio_rebuild_test.dart test/widgets/mini_reproductor_configurar_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
**Modified tests**: `pantalla_inicio_test.dart`, `pantalla_inicio_rebuild_test.dart`, `mini_reproductor_configurar_test.dart`
- [ ] 5.1 RED — write the ADR-7 anti-cache test: mutate `EstadoRadio` **from outside the widget tree** (simulating
@@ -265,7 +281,7 @@ Action Using Existing Criteria, Group Management Reachable from Favoritos, Custo
**Depends on**: WU5
**Spec refs**: `station-discovery-browse` — Buscar Landing State Shows Discovery Content, Active-Filter Pills and
Results Counter, One-Tap Clear-All-Filters on Empty Results, Client-Side Search Sort Only
**Verify**: `flutter test test/pantallas/pantalla_buscar_shimmer_test.dart test/estado/estado_busqueda_test.dart && flutter analyze && dart format .`
**Verify**: `flutter test test/pantallas/pantalla_buscar_shimmer_test.dart test/estado/estado_busqueda_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
**Modified tests**: `pantalla_buscar_shimmer_test.dart`, `estado_busqueda_test.dart` (non-sort paths unmodified)
- [ ] 6.1 No spike needed here — the sort ruling is already verified (Engram 2505): server-side `order`/`reverse`
@@ -294,7 +310,7 @@ Results Counter, One-Tap Clear-All-Filters on Empty Results, Client-Side Search
**Depends on**: WU6
**Spec refs**: `station-discovery-browse` — Países Browser Over the Verified Countries Contract, Existing Station
Calls Unchanged by Transport Extraction
**Verify**: `flutter test test/servicios/servicio_radio_test.dart test/servicios/servicio_radio_transporte_test.dart test/estado/estado_busqueda_test.dart && flutter analyze && dart format .`
**Verify**: `flutter test test/servicios/servicio_radio_test.dart test/servicios/servicio_radio_transporte_test.dart test/estado/estado_busqueda_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
**New tests**: `test/servicios/servicio_radio_transporte_test.dart`, country-model test, `pantalla_paises_test.dart`
**Modified tests**: `estado_busqueda_test.dart` (additions only); `servicio_radio_test.dart` is **not modified**
@@ -332,7 +348,7 @@ Calls Unchanged by Transport Extraction
**Depends on**: WU1
**Spec refs**: `native-alarms` (delta) — Alarm Card Tap-to-Edit Swipe-to-Delete, Hero Banner Inline Skip;
`alarm-vacation-ranges` — Vacation Summary Row Replaces the Inline Panel
**Verify**: `flutter test test/pantallas/pantalla_alarmas_editor_test.dart test/estado/estado_alarmas_test.dart test/estado/estado_alarmas_snooze_test.dart && flutter analyze && dart format .`
**Verify**: `flutter test test/pantallas/pantalla_alarmas_editor_test.dart test/estado/estado_alarmas_test.dart test/estado/estado_alarmas_snooze_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
**Modified tests**: `pantalla_alarmas_editor_test.dart` (+ new card-gesture scenarios); `estado_alarmas_test.dart`,
`estado_alarmas_snooze_test.dart` must pass unmodified
@@ -358,7 +374,7 @@ Calls Unchanged by Transport Extraction
**Depends on**: WU8
**Spec refs**: `alarm-vacation-ranges` — Active Range Detection, Per-Alarm Pause-Impact Computation, Upcoming Ranges
Query, Past-Ranges History Query, Vacation Summary Row Replaces the Inline Panel (destination screen)
**Verify**: `flutter test test/estado/estado_alarmas_test.dart test/pantallas/pantalla_vacaciones_test.dart && flutter analyze && dart format .`
**Verify**: `flutter test test/estado/estado_alarmas_test.dart test/pantallas/pantalla_vacaciones_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
**New tests**: `pantalla_vacaciones_test.dart`
**Modified tests**: `estado_alarmas_test.dart` (additions only)
@@ -385,7 +401,7 @@ Query, Past-Ranges History Query, Vacation Summary Row Replaces the Inline Panel
**Commit**: `feat(alarmas): rewrite alarm editor with inline time widget`
**Depends on**: WU8
**Spec refs**: `native-alarms` (delta) — Alarm Editor Preserves Date, Fallback Station, and Sound Fields
**Verify**: `flutter test test/widgets/editor_hora_inline_test.dart test/pantallas/pantalla_alarmas_fecha_test.dart && flutter analyze && dart format .`
**Verify**: `flutter test test/widgets/editor_hora_inline_test.dart test/pantallas/pantalla_alarmas_fecha_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
**New tests**: `test/widgets/editor_hora_inline_test.dart` (standalone)
**Modified tests**: `pantalla_alarmas_fecha_test.dart`
@@ -412,7 +428,7 @@ as its own unit per the proposal ("never bundled"). See forecast table footnote
**Depends on**: WU1
**Spec refs**: `native-alarms` (delta) — Ringing Screen Shows a Static Status Label, Dismiss Guard and Force-Stop
Banner Survive the Restyle
**Verify**: `flutter test test/pantallas/pantalla_alarma_sonando_test.dart test/pantallas/pantalla_alarma_sonando_scaffold_test.dart test/pantallas/pantalla_alarma_sonando_dismiss_guard_test.dart && flutter analyze && dart format .`
**Verify**: `flutter test test/pantallas/pantalla_alarma_sonando_test.dart test/pantallas/pantalla_alarma_sonando_scaffold_test.dart test/pantallas/pantalla_alarma_sonando_dismiss_guard_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
**Modified tests**: `pantalla_alarma_sonando_test.dart`, `pantalla_alarma_sonando_scaffold_test.dart`.
**`pantalla_alarma_sonando_dismiss_guard_test.dart` is run but MUST NOT be edited.**
@@ -440,7 +456,7 @@ Banner Survive the Restyle
**Spec refs**: `eq-custom-presets` — Five-Band Equalizer (Regression Guard), Custom Preset Save, Custom Preset
Naming Validates Non-Empty Input, Per-Station EQ Entry Point (component only; consumption is WU14),
Stations-With-Own-EQ Drill-Down, Base-vs-Per-Station Explainer Preserved, Active Output Surfaced on the Main Screen
**Verify**: `flutter test test/widgets/ecualizador_widget_test.dart test/servicios/servicio_presets_personalizados_test.dart test/servicios/servicio_ecualizador_test.dart test/estado/estado_ecualizador_test.dart test/servicios/servicio_audio_eq_reapply_test.dart && flutter analyze && dart format .`
**Verify**: `flutter test test/widgets/ecualizador_widget_test.dart test/servicios/servicio_presets_personalizados_test.dart test/servicios/servicio_ecualizador_test.dart test/estado/estado_ecualizador_test.dart test/servicios/servicio_audio_eq_reapply_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
**New tests**: `servicio_presets_personalizados_test.dart`, `pantalla_ajustes_ecualizador_test.dart`
**Modified tests**: `ecualizador_widget_test.dart`. `servicio_ecualizador_test.dart`, `estado_ecualizador_test.dart`,
`servicio_audio_eq_reapply_test.dart` must pass **unmodified**.
@@ -481,7 +497,7 @@ Stations-With-Own-EQ Drill-Down, Base-vs-Per-Station Explainer Preserved, Active
**Depends on**: WU13
**Spec refs**: `eq-custom-presets` — Per-Station EQ Entry Point From the Player; `multi-device-eq` (delta) —
Per-Station EQ Entry Relocates, Resolution Logic Does Not
**Verify**: `flutter test test/pantallas/pantalla_reproductor_test.dart test/servicios/servicio_ecualizador_test.dart test/estado/estado_ecualizador_test.dart test/servicios/servicio_audio_eq_reapply_test.dart && flutter analyze && dart format .`
**Verify**: `flutter test test/pantallas/pantalla_reproductor_test.dart test/servicios/servicio_ecualizador_test.dart test/estado/estado_ecualizador_test.dart test/servicios/servicio_audio_eq_reapply_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
**Modified tests**: `pantalla_reproductor_test.dart` (or equivalent widget test). Three EQ test files must pass
**unmodified**.
@@ -510,7 +526,7 @@ Per-Station EQ Entry Relocates, Resolution Logic Does Not
**Depends on**: WU3b
**Spec refs**: `recordings-library` — Storage Usage Display, Browsable Recordings List, Per-Recording Menu
Constrained to Rename/Share/Delete
**Verify**: `flutter test test/pantallas/pantalla_grabaciones_test.dart test/servicios/servicio_grabacion_radio_test.dart && flutter analyze && dart format .`
**Verify**: `flutter test test/pantallas/pantalla_grabaciones_test.dart test/servicios/servicio_grabacion_radio_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
**New tests**: `pantalla_grabaciones_test.dart`
**Modified tests**: `servicio_grabacion_radio_test.dart` (only if a listing method is added)
@@ -535,7 +551,7 @@ Constrained to Rename/Share/Delete
**Commit**: `feat(connectivity): restyle offline and reconnect banners`
**Depends on**: WU1
**Spec refs**: crosscutting risk register (reconnect attempt counter is verify-first, not assumed)
**Verify**: `flutter test test/widgets/reconnect_ui_test.dart test/servicios/servicio_audio_reconnect_test.dart && flutter analyze && dart format .`
**Verify**: `flutter test test/widgets/reconnect_ui_test.dart test/servicios/servicio_audio_reconnect_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
**Modified tests**: `reconnect_ui_test.dart`. `servicio_audio_reconnect_test.dart` must pass unmodified.
- [ ] 16.1 Verify-first — run `servicio_audio_reconnect_test.dart` and inspect the reconnect controller: confirm
@@ -554,7 +570,7 @@ Constrained to Rename/Share/Delete
**Commit**: `feat(bienvenida): add monetization-free welcome screen`
**Depends on**: WU1
**Spec refs**: `onboarding-welcome` — Full-Screen Welcome Route, No Monetization Content, CTA Dismisses to Escuchar
**Verify**: `flutter test test/pantallas/pantalla_bienvenida_test.dart && flutter analyze && dart format .`
**Verify**: `flutter test test/pantallas/pantalla_bienvenida_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
**New tests**: `pantalla_bienvenida_test.dart`
- [ ] 17.1 RED — the welcome screen renders as a full-screen route (not a modal `Dialog`) with a logo, headline,
@@ -577,7 +593,7 @@ Constrained to Rename/Share/Delete
**Depends on**: WU1-WU17 (runs last, after `en`/`es` land across all slices)
**Spec refs**: `app-navigation-shell` — Escuchar Tab Rename Preserves the ARB Key (11-locale completion); backs the
UI copy every other capability introduced
**Verify**: `flutter test && flutter analyze && dart format .`
**Verify**: `flutter test && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
**Modified tests**: ARB key-parity guard test (extend existing or add one if none exists)
- [ ] 18.1 RED — an ARB-parity test asserting all 13 `lib/l10n/app_*.arb` files contain an identical key set.
@@ -588,7 +604,8 @@ UI copy every other capability introduced
- [ ] 18.4 GREEN — set the `navHome` value in the remaining 11 locales to a real translation of "Listen" (not a
mechanical copy of the English/Spanish pair).
- [ ] 18.5 REFACTOR — run the ARB-parity check across all 13 files; confirm zero missing/extra keys.
- [ ] 18.6 Verify — full `flutter test` suite green; `flutter analyze` clean; `dart format .` produces no diff;
- [ ] 18.6 Verify — full `flutter test` suite green; `flutter analyze` clean; scoped `dart format` over the files
this change touched produces no diff (NEVER bare `dart format .` — see the warning at the top);
grep confirms no "PRO"/price/trial-duration string exists in any ARB file (final whole-app Success Criteria
check).
@@ -598,7 +615,9 @@ UI copy every other capability introduced
1. `flutter test` — full suite green.
2. `flutter analyze` — clean.
3. `dart format .` — no diff.
3. `dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM main -- '*.dart')` — no diff over the
files this change touched. **NEVER bare `dart format .`** — it reformats 27 unrelated pre-existing files and would
break the empty-diff guards in steps 4 and 5 below (Engram `reference/dart-format-scope-hazard`, id 2511).
4. `git diff --stat lib/servicios/navegacion_auto.dart lib/servicios/servicio_ecualizador.dart` — both empty.
5. `git diff lib/servicios/servicio_audio.dart` — band-application block (`749-762`) unchanged.
6. Grep `lib/` for `countrycodes` — zero hits.