From e99ecffb0cfa9947d12220f9d84c87747fe862e9 Mon Sep 17 00:00:00 2001 From: freetlab Date: Tue, 28 Jul 2026 20:12:47 +0200 Subject: [PATCH] docs(sdd): scope dart format to touched files in all verify commands --- openspec/changes/rediseno-funcional/design.md | 5 +- .../changes/rediseno-funcional/proposal.md | 6 +- openspec/changes/rediseno-funcional/tasks.md | 57 ++++++++++++------- 3 files changed, 46 insertions(+), 22 deletions(-) diff --git a/openspec/changes/rediseno-funcional/design.md b/openspec/changes/rediseno-funcional/design.md index 8f39f08..f605eac 100644 --- a/openspec/changes/rediseno-funcional/design.md +++ b/openspec/changes/rediseno-funcional/design.md @@ -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 | diff --git a/openspec/changes/rediseno-funcional/proposal.md b/openspec/changes/rediseno-funcional/proposal.md index f2ebdb6..a77efda 100644 --- a/openspec/changes/rediseno-funcional/proposal.md +++ b/openspec/changes/rediseno-funcional/proposal.md @@ -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/`. diff --git a/openspec/changes/rediseno-funcional/tasks.md b/openspec/changes/rediseno-funcional/tasks.md index 273ec6f..3fd8f9b 100644 --- a/openspec/changes/rediseno-funcional/tasks.md +++ b/openspec/changes/rediseno-funcional/tasks.md @@ -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.