feat(ui): design token discipline, accessibility and i18n pass
- Replace all hardcoded Color literals outside lib/tema with theme tokens (new static brand palette in PluriWaveTokens); media notification uses the brand color instead of the Material default purple - Favorite button on station cards grows to a 48dp target and becomes an independent semantics node for screen readers (Semantics container fix) - All flutter_animate call sites route through the PluriAnimate reduced-motion gate (zero direct .animate() left) - Locale-aware short dates via intl DateFormat (new lib/l10n/formato_fechas.dart) replacing the hardcoded DD/MM/YYYY; proper plural messages for the favorites counter; example stream URL as a localized key - all 13 locales - Rounded shimmer placeholders matching card radii; shimmer loading state in search instead of a bare spinner; rounded icon variants unified in settings; bottom-sheet conventions on the custom station form - Fix latent debug crash: vacation editor read AppLocalizations in initState - 11 new tests (121 total green), flutter analyze clean
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
**Mode**: Strict TDD (test runner: `flutter test`)
|
||||
**Artifact store**: openspec (Engram unavailable this session)
|
||||
**Delivery**: auto-chain, local apply — no commits, no PRs (user commits at own cadence)
|
||||
**Last updated**: 2026-06-11 (Batch 6)
|
||||
**Last updated**: 2026-06-11 (Batch 7)
|
||||
|
||||
## Batch log
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
| 4 | S7 — Streaming resilience (buffer config, reconnect state machine, UI wiring) | COMPLETE (Dart-only batch; stream-drop on-device verification deferred to user) | 2026-06-11 |
|
||||
| 5 | S4a — ServicioExportImport + EstadoEcualizador extraction + compat getters | COMPLETE (Dart-only batch) | 2026-06-11 |
|
||||
| 6 | S4b — EstadoGrabacion + EstadoBusqueda + scoped rebuilds + compat-getter removal | COMPLETE (Dart-only batch) | 2026-06-11 |
|
||||
| 7 | S5 — Design system, a11y, i18n, polish | COMPLETE (Dart-only batch) | 2026-06-11 |
|
||||
|
||||
## Task status (cumulative)
|
||||
|
||||
@@ -165,9 +166,32 @@
|
||||
| T-S4b-12 | [x] | `flutter analyze` — No issues found |
|
||||
| T-S4b-13 | [x] | `dart format` on 15 touched files (10 reflowed); analyze + suite re-run after |
|
||||
|
||||
### Slice S5 — Design system, a11y, i18n — 18/18 complete
|
||||
|
||||
| Task | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| T-S5-01 | [x] | RED: `tarjeta_emisora_a11y_test.dart` — semantics label/button/toggled + ≥48dp. RED exposed a REAL bug: the card InkWell merged the favorite into ONE semantics node (screen readers could not reach the action independently) |
|
||||
| T-S5-02 | [x] | RED: `pluri_animate_test.dart` — fadeIn/scaleIn lock-in + NEW `pluriFadeSlideIn` (honest RED: method missing) |
|
||||
| T-S5-03 | [x] | RED: `pantalla_alarmas_fecha_test.dart` — en-US `6/11/2026`, NOT `11/06/2026`; es day/month |
|
||||
| T-S5-04 | [x] | RED: `pantalla_favoritos_plural_test.dart` — `stationCount` singular ≠ plural (en, es) |
|
||||
| T-S5-05 | [x] | RED: `pantalla_buscar_shimmer_test.dart` — shimmer during loading, NO spinner (cargando-true EstadoBusqueda subclass seam) |
|
||||
| T-S5-06 | [x] | RED: `notification_color_test.dart` — `configuracionAudioService.notificationColor == PluriWaveTokens.brand` |
|
||||
| T-S5-07 | [x] | GREEN: zero `Color(0x...)` outside lib/tema (rg audit). New static tokens `brand`/`brightCyan`/`auroraTeal`/`skyBlue`; scaffold gradient/orbs, alarmas glows, visualizer stop, premium orbs, tarjeta sweep mapped; Colors.grey/green/white semantic uses → colorScheme/tokens |
|
||||
| T-S5-08 | [x] | GREEN: favorite `Semantics(container: true, ...)` + 48×48 in both variants; `_AssetIcon.semanticLabel` (+`excludeFromSemantics` when decorative); alarm/vacation/ringing images labelled via NEW `alarmIconLabel`/`vacationIconLabel` |
|
||||
| T-S5-09 | [x] | GREEN: `pluriFadeSlideIn` added; ALL `flutter_animate` call sites routed through PluriAnimate (inicio chips/grid, buscar results, reproductor ×8); zero direct `.animate()` in lib/ |
|
||||
| T-S5-10 | [x] | GREEN: NEW `lib/l10n/formato_fechas.dart` (`fechaCortaLocalizada`); `_fechaCorta(l10n, fecha)` delegates via `l10n.localeName` (6 call sites) |
|
||||
| T-S5-11 | [x] | GREEN: `stationCount` ICU plural in ALL 13 locales (ru/ar full category sets); NEW `streamUrlHint` replaces hardcoded `stream.ejemplo.com`; gen-l10n run |
|
||||
| T-S5-12 | [x] | GREEN: shimmer rounded (radiusLg/6) + NEW `esCompacta` row variant; PantallaBuscar loading → 4 compact shimmer rows |
|
||||
| T-S5-13 | [x] | GREEN: 9 ajustes icons → `_rounded`; `*_outlined` family left (no rounded-outline variant exists); `_FormularioEmisora` sheet → `useSafeArea` + `showDragHandle` |
|
||||
| T-S5-14 | [x] | GREEN: `configuracionAudioService` top-level const; `notificationColor: PluriWaveTokens.brand` |
|
||||
| T-S5-15 | [x] | Targeted run 11/11 green (RED first: `+0 -6`) |
|
||||
| T-S5-16 | [x] | Full suite 121/121 (110 baseline + 11 new) |
|
||||
| T-S5-17 | [x] | `flutter analyze` — No issues found; color-literal audit ZERO |
|
||||
| T-S5-18 | [x] | `dart format` on 20 touched Dart files (7 reflowed); analyze + suite re-run after |
|
||||
|
||||
### Remaining slices (not started)
|
||||
|
||||
S5, S6, cross-cutting (T-CC-01, T-CC-02) — all pending.
|
||||
S6, cross-cutting (T-CC-01, T-CC-02) — pending. S6 is now UNBLOCKED (depends on S4b + S5, both complete).
|
||||
|
||||
## Snooze defect fixes (design audit D1–D5 / S1–S5)
|
||||
|
||||
@@ -236,6 +260,61 @@ RED run evidence (Batch 5): `00:00 +0 -2` (both files fail to load — captured
|
||||
|
||||
RED run evidence (Batch 6): `00:00 +0 -3` (all three files fail to load — captured before any lib code). GREEN: targeted 8/8; full suite `00:11 +110: All tests passed!` (103 baseline − 1 moved + 8 new); analyze + suite re-run after format.
|
||||
|
||||
### Batch 7 TDD Cycle Evidence (S5)
|
||||
|
||||
| Task | RED (test written first, failing) | GREEN (implementation passes) | REFACTOR |
|
||||
|------|-----------------------------------|-------------------------------|----------|
|
||||
| T-S5-01/T-S5-08 | `find.bySemanticsLabel` found 0 nodes — the card InkWell MERGED the favorite into one node (real a11y defect, probed with a semantics dump) | `Semantics(container: true)` + 48dp target; node found, button/toggled flags pass | `hasFlag` (deprecated) → `flagsCollection` + `Tristate`; explicit `semantics.dispose()` (addTearDown fires too late for the handle check) |
|
||||
| T-S5-02/T-S5-09 | Compile failure: `pluriFadeSlideIn` undefined | Helper added; 4/4 animate tests green | — |
|
||||
| T-S5-03/T-S5-10 | Load failure: `formato_fechas.dart` missing | `fechaCortaLocalizada` + delegation; en-US/es tests pass | — |
|
||||
| T-S5-04/T-S5-11 | Compile failure: `stationCount` missing from AppLocalizations | ARB plural ×13 + gen-l10n; both locale tests pass | — |
|
||||
| T-S5-05/T-S5-12 | `TarjetaEmisoraShimmer` found 0, spinner found 1 | Compact shimmer variant + buscar loading swap | Shimmer block builder extracted (`bloque`) shared by both variants |
|
||||
| T-S5-06/T-S5-14 | Compile failure: `configuracionAudioService`/`PluriWaveTokens.brand` undefined | Const extraction + brand token; test passes | `electricMagenta` re-pointed at `brand` (no duplicate literal) |
|
||||
|
||||
RED run evidence (Batch 7): `00:02 +0 -6` (4 compile/load failures + 2 honest assertion failures) captured before any lib code. GREEN: targeted 11/11; full suite `00:14 +121: All tests passed!`; analyze + suite re-run after `dart format`.
|
||||
|
||||
## Files changed (Batch 7)
|
||||
|
||||
| File | Action | ~Lines |
|
||||
|------|--------|--------|
|
||||
| `lib/tema/pluriwave_tokens.dart` | Modified | +12 (static `brand`/`brightCyan`/`auroraTeal`/`skyBlue` token definitions; format reflow) |
|
||||
| `lib/tema/pluri_animate.dart` | Modified | +14 (`pluriFadeSlideIn`) |
|
||||
| `lib/l10n/formato_fechas.dart` | Created | +14 (locale-aware short date) |
|
||||
| `lib/main.dart` | Modified | +13/-8 (`configuracionAudioService` const, brand notification color) |
|
||||
| `lib/widgets/pluri_wave_scaffold.dart` | Modified | +8/-8 (gradient + orbs → tokens/colorScheme) |
|
||||
| `lib/widgets/tarjeta_emisora.dart` | Modified | +60/-30 (brightCyan, favorite container semantics + 48dp, rounded shimmer + compact variant) |
|
||||
| `lib/widgets/visualizador_audio.dart` | Modified | +2/-1 (warmCoral token) |
|
||||
| `lib/widgets/pluri_premium_widgets.dart` | Modified | +3/-2 (brightCyan) |
|
||||
| `lib/pantallas/pantalla_alarmas.dart` | Modified | +55/-30 (glow tokens, `_AssetIcon` semanticLabel, locale-aware `_fechaCorta`, vacaciones initState l10n fix) |
|
||||
| `lib/pantallas/pantalla_favoritos.dart` | Modified | +2/-1 (plural counter) |
|
||||
| `lib/pantallas/pantalla_buscar.dart` | Modified | +18/-7 (shimmer loading, pluriFadeSlideIn) |
|
||||
| `lib/pantallas/pantalla_ajustes.dart` | Modified | +20/-12 (rounded icons, semantic colors, l10n hint, sheet conventions) |
|
||||
| `lib/pantallas/pantalla_reproductor.dart` | Modified | +25/-20 (PluriAnimate routing ×8, white overlays → colorScheme/tokens) |
|
||||
| `lib/pantallas/pantalla_inicio.dart` | Modified | +6/-3 (PluriAnimate routing) |
|
||||
| `lib/l10n/app_*.arb` (13 files) | Modified | +4-5 each (`stationCount` plural, `alarmIconLabel`, `vacationIconLabel`, `streamUrlHint`) |
|
||||
| `lib/l10n/gen/*` (14 files) | Regenerated | by `flutter gen-l10n` |
|
||||
| `test/widgets/tarjeta_emisora_a11y_test.dart` | Created | +72 (1 test) |
|
||||
| `test/tema/pluri_animate_test.dart` | Created | +80 (4 tests) |
|
||||
| `test/tema/notification_color_test.dart` | Created | +17 (1 test) |
|
||||
| `test/pantallas/pantalla_alarmas_fecha_test.dart` | Created | +29 (2 tests) |
|
||||
| `test/pantallas/pantalla_favoritos_plural_test.dart` | Created | +21 (2 tests) |
|
||||
| `test/pantallas/pantalla_buscar_shimmer_test.dart` | Created | +42 (1 test) |
|
||||
|
||||
Total Batch 7 lib diff: ~240 insertions / ~120 deletions (incl. ARB, excl. gen/), plus ~260 lines of new tests. Within the ~210-line slice estimate for hand-written lib changes. No Kotlin/native files touched.
|
||||
|
||||
## Deviations from design (Batch 7)
|
||||
|
||||
1. **`Semantics(container: true)` on the favorite button (not in task text).** The task only asked for `Semantics(button, label)` — which ALREADY existed. The RED test proved the real defect: the card-level InkWell merged the favorite into a single semantics node, so screen readers could not reach the action. `container: true` forces an own node; this is the actual S5-R2 fix.
|
||||
2. **`fechaCortaLocalizada` lives in NEW `lib/l10n/formato_fechas.dart`** (task said edit `_fechaCorta` in place). The private top-level function is untestable from `flutter test`; the public helper takes a locale tag (testable without widgets) and `_fechaCorta(l10n, fecha)` delegates via `l10n.localeName`. Spec scenario S5-R4-A is met verbatim.
|
||||
3. **Brand token is `PluriWaveTokens.brand`, not `brandColor`** (task text guessed the name). Defined as a `static const` so it works in the `const AudioServiceConfig` context; `electricMagenta` now references it (single source for 0xFF21D4D9). `brightCyan`/`auroraTeal`/`skyBlue` added the same way for palette colors that had no token.
|
||||
4. **`Colors.white` overlays mapped to `colorScheme.onSurface`/`onPrimary` and `tokens.glassBorder`** — the scheme's `onSurface` is 0xFFF2F7FA (near-white), so visuals are intentionally near-identical while becoming theme-driven.
|
||||
5. **`pluriScaleIn` on the hero adds a fade** the old bare `.scale()` did not have (the shared helper pairs fade+scale). Visually negligible at 420 ms; keeping one canonical scale-entry beats a third helper.
|
||||
6. **Icon variants: only base-name icons got `_rounded`** (9 sites). The `*_outlined` Material family (folder_outlined, backup_outlined, upload/download_outlined, verified_outlined, music_note_outlined) has NO rounded-outline variant; switching them to filled `_rounded` would change their visual weight, so they stay outlined.
|
||||
7. **`streamUrlHint` added as an l10n key** (the prompt's "handled per design"): the hardcoded hint leaked a Spanish-looking host (`stream.ejemplo.com`) into all 13 locales; the key ships the same neutral `stream.example.com` URL everywhere but is now localizable.
|
||||
8. **`_EditorVacacionesSheet` initState l10n crash fixed** (flagged in Batch 2 as a latent debug-mode crash, explicitly brought into this batch's scope): controller now created lazily in `didChangeDependencies`, mirroring the alarm-editor fix.
|
||||
9. **Pagination spinner in PantallaBuscar kept** — S5-R6 covers the initial loading state (explore C11, lines 241-245); the small inline load-more spinner is a different affordance and was left untouched.
|
||||
10. **`flagsCollection`/`Tristate` instead of `SemanticsFlag.hasFlag`** in the a11y test — `hasFlag` is deprecated in the current SDK and `flutter analyze` flags it; also `tester.ensureSemantics()` must be disposed in the test body (an `addTearDown` runs after the framework's handle-leak check).
|
||||
|
||||
## Files changed (Batch 2)
|
||||
|
||||
| File | Action | ~Lines |
|
||||
@@ -495,9 +574,28 @@ From tasks.md Section 11 — S1 items still pending from Batch 1, plus new S2 it
|
||||
4. **Scoped rebuilds (S4-R5):** while audio plays/buffers, home/favorites/settings should feel identical (no visual change expected — the win is fewer rebuilds); list reordering in Ajustes still re-sorts home, search results and favorites.
|
||||
5. **Stop recording on pause/stop/station switch:** unchanged orchestration in EstadoRadio — verify recording stops when playback pauses/stops or station changes.
|
||||
|
||||
## Verification summary (Batch 7)
|
||||
|
||||
- `flutter test`: 121/121 passing (110 baseline + 11 new across 6 files); re-run after `dart format`
|
||||
- `flutter analyze`: No issues found (identical to baseline); re-run after format
|
||||
- `dart format`: applied to all 20 touched Dart files (7 reflowed); gen/ untouched by hand
|
||||
- `flutter gen-l10n`: run once after the 13 .arb edits
|
||||
- Color-literal audit: `rg 'Color(0x' lib` excluding `lib/tema/` and `lib/l10n/gen/` → **0 matches** (was 14)
|
||||
- `rg '.animate()' lib` → 0 direct flutter_animate call sites (all via PluriAnimate)
|
||||
- `flutter build`: NOT run (forbidden)
|
||||
- No Kotlin/native files touched in this batch
|
||||
|
||||
### Manual verification items added by Batch 7 (user)
|
||||
|
||||
1. **Visual parity sweep (S5-R1):** home/buscar/favoritos/ajustes/alarmas/reproductor look unchanged — token mapping was value-preserving (onSurface ≈ white; electricMagenta = brand). The settings "check" icons are now mint (`colorScheme.secondary`) instead of Material green — intentional.
|
||||
2. **TalkBack (S5-R2):** on a station card, the favorite toggle is now reachable as its OWN button ("Añadir a favoritos / Quitar de favoritos") separate from the card; alarm/vacation images announce their labels.
|
||||
3. **Reduced motion (S5-R3):** with "remove animations" enabled, home grid/search results/player screens render instantly with no entry animations.
|
||||
4. **Locale dates (S5-R4):** switch app language to English → alarm editor and vacation ranges show M/D/Y order; Japanese shows Y/M/D.
|
||||
5. **Media notification (S5-R8):** the playback notification accent is teal (brand), not purple, on devices that honor `notificationColor`.
|
||||
|
||||
## Workload / boundary
|
||||
|
||||
- Mode: auto-chain local slices (no PRs)
|
||||
- Current work units: S1, S2a, S2b, S3a, S3b, S7, S4a (committed, latest 0416b30), S4b (complete, in working tree)
|
||||
- Boundary (Batch 6): starts from the clean post-0416b30 tree; ends with S4b fully checked off, suite green (110/110). Rollback = revert the 9 modified lib/test files + delete the 6 new files (Dart-only; no native edits).
|
||||
- Next batch: S5 (design system / a11y / i18n — unblocked since S2b) then S6 (quality gates — now unblocked: depends on S4b + S5).
|
||||
- Current work units: S1, S2a, S2b, S3a, S3b, S7, S4a, S4b (committed, latest 52855e7), S5 (complete, in working tree)
|
||||
- Boundary (Batch 7): starts from the clean post-52855e7 tree; ends with S5 fully checked off, suite green (121/121). Rollback = revert the 27 modified lib files (incl. 13 ARB + gen/) + delete the 7 new files (Dart-only; no native edits).
|
||||
- Next batch: S6 (quality gates — lint hardening + remaining top-5 tests) then cross-cutting T-CC-01/T-CC-02. S6 is the LAST slice.
|
||||
|
||||
Reference in New Issue
Block a user