Files
pluriwave/openspec/changes/rediseno-funcional/proposal.md
T

34 KiB

Proposal: Functional Redesign (rediseno-funcional)

Source of truth: rediseno-funcional-de-aplicacion/project/PluriWave Rediseno.dc.html, turn t4 only. Binding inputs: Engram sdd/rediseno-funcional/scope-decisions, sdd/rediseno-funcional/open-questions-resolved, sdd/rediseno-funcional/eq-band-spike (id 2498), reference/radio-browser-countries-endpoint (id 2500), preferences/verify-external-apis-officially (id 2501), explore-mobile, explore-auto, explore-crosscutting. Do not re-open decisions recorded there. This artifact intentionally exceeds the default 450-word proposal budget: the change spans 14 mobile screens, 5 Android Auto surfaces, and 18 work units, and the delivery/risk sections are load-bearing for the apply phase.

Intent

Problem. The app's current UI is a glass-everywhere magenta/coral surface built up incrementally over many changes. Three concrete symptoms:

  1. Settings has collapsed under its own weight. lib/pantallas/pantalla_ajustes.dart is 1896 lines rendering 12 always-expanded panels on one scroll. The mockup's "12 panels to 4 groups" framing describes the current app exactly — the redesign premise is verified, not aspirational.
  2. The home tab does not do its job. pantalla_inicio.dart renders a discovery browser (near-you carousel, genre chips, trending grid) while the thing users open the app for — the player and their own stations — is one tap away behind a pushed route.
  3. The visual language drifted from its own tokens. The mockup's #07121A base and #21D4D9 brand already match PluriWaveTokens, but the list surface #102532, the live green #7EE4C2, and the offline accent are used ad-hoc across screens, and .copyWith(fontWeight: w900, letterSpacing: ...) is copy-pasted in nearly every screen file. There is no named type scale.

Why now. The design handoff (turn t4) is approved and the app is in Google Play internal testing — the lowest-risk window to restructure navigation-adjacent UI before a public audience exists. Version is already bumped to 1.2.0+122; 17f8e69 on main is a clean rollback point.

Success looks like. Every mockup screen's intent is implemented in the new visual language, information architecture is unchanged (5 tabs), no existing capability is lost, and no monetization surface ships.

Standing Rule — external APIs are verified, never inferred

Any technical doubt about connectivity to an external API or service MUST be resolved against that provider's official documentation. Inference from existing call sites, from field names, or from what "looks right" is not acceptable evidence. This binds sdd-spec, sdd-design, and sdd-apply for this change and every change after it. Engram: preferences/verify-external-apis-officially (id 2501).

Practical consequence here: no spec, design, or task may assert a Radio Browser endpoint, parameter, or response field that has not been checked against https://api.radio-browser.info/. Where such a check has already been done, the verified contract is recorded below and is authoritative.

Reconciliation Ruling — mockup push-chrome vs. 5 tabs

The mockup states (t4 intro, line 38): "cuatro raíces con barra globo … las pantallas de segundo nivel entran empujadas con cabecera de 56px, flecha atrás y SIN barra de pestañas."

Ruling: adopt the push-chrome rule, reject the root set.

  • Every genuine second-level screen gets 56px header + back arrow + no tab bar: Reproductor completo (2), Países (5), Vacaciones (9bis), Ecualizador (11), Grabaciones (12), and all new Settings detail screens.
  • All five roots keep the tab bar: Escuchar, Buscar, Favoritos, Alarmas, Ajustes.
  • The one deliberate contradiction: mockup screen 4 ("Tus emisoras · grupos") is drawn as a pushed screen with no tab bar. In our IA it is the Favoritos root, so it keeps its tab bar.

Reasoning. The mockup's no-tab-bar rule is a consequence of its 4-root IA, not an independent design law. Its real intent is "navigation depth removes the root switcher." Applying it literally to Favoritos would hide the tab bar on a root tab — a navigation regression, not a redesign. The rule is honoured at the level of intent (depth implies no tabs) and rejected at the level of the specific frame that only looked pushed because the mockup had demoted Favoritos out of the root set. Downstream phases must not copy "no tab bar" onto Favoritos.

Corollary ruling (not covered by the 8 resolutions): the mockup's 4 Settings groups draw exactly 16 rows, omitting Sleep Timer and Backup/restore. Per the standing "do not silently narrow scope" rule, both are kept under the APLICACIÓN group. Sleep timer stays reachable from both Settings and the player tool tray.

Scope

In Scope

Ordered as work units (WU). This ordering is the dependency graph, not a suggestion.

WU Deliverable Depends on Est. lines Risk
WU1 Design tokens + shared primitives + the Inicio-to-Escuchar rename: add listSurface #102532, liveGreen #7EE4C2, offlineAccent #E8879A; named text styles (heroTime, eyebrowLabel, cardTitle, screenTitle); shared push-chrome scaffold (56px header + back, no tab bar); retitle the first tab in lib/app.dart and update the navHome value in app_en.arb + app_es.arb (see rename ruling below) 160-260 Low
WU2 Android Auto verification-only: re-run test/servicios/navegacion_auto_test.dart, formally close A1-A5 as already-done. Zero code changes. 0 None
WU3a Settings shell + AUDIO/EMISORAS groups: 12 panels to grouped nav rows, extract detail screens, controls reused verbatim WU1 350-450 Low (mechanical)
WU3b Settings GRABACIONES Y MÚSICA / APLICACIÓN groups (incl. sleep timer + backup) WU3a 350-450 Low (mechanical)
WU4 Favoritos restyle: chip group filter replaces stacked panels, flat reorderable list (drag-to-reorder is NEW), swap_vert sort, custom-station CTA WU1 300-400 Medium
WU5 Escuchar restructure: embedded player hero (square art, live pill, waveform, transport row with sleep as 5th action, tool-tray entry chip) + "Tus emisoras" favorites grid + "Ver todas" switching to the Favoritos tab (index switch, not push) WU1, WU4 350-450 Medium-High
WU6 Buscar: discovery content (near-you / genres / trending / countries entry) becomes the tab's landing state; active-filter pills with bottom-sheet pickers; results counter; one-tap "clear N filters". Opens with an official-docs check on Radio Browser sort support (resolution 8, still open) WU5 350-500 Medium
WU7 Países browser (NEW screen): "Tus idiomas" + full alphabetical list with per-country counts, over the verified /json/countries contract below. Includes the _get transport extraction in servicio_radio.dart plus regression coverage for the 8 existing station calls WU6 300-400 Medium
WU8 Alarmas root restyle: minimal cards (giant time + station + switch), tap = edit, swipe = delete (resolution 2), hero banner with inline "Saltar", Vacaciones inline panel becomes a summary row + chevron WU1 400-550 Medium
WU9 Vacaciones manager (NEW screen 9bis): active-range hero, per-alarm impact line, upcoming ranges, past-ranges history. Needs new EstadoAlarmas query methods WU8 350-450 Medium-High
WU10 Alarm editor sheet rewrite: inline giant HH:MM editor (custom widget), always-visible weekday circles, restyled sliders. Keeps the date field, fallback-station picker, and sound dropdown (resolution 3), collapsed into an advanced section WU8 500-650 High
WU11 Alarm ringing restyle: full-bleed blurred art, giant time, 3 snooze tiles, full-width stop pill. Static "Subiendo volumen" label with no seconds counter (resolution 4). Force-stop retry banner and single-exit dismiss guard preserved verbatim WU1 200-300 High (safety-critical)
WU12 Native EQ band-count spikeRESOLVED before planning closed. Tombstone row: the number is retired, not missing. Evidence in Engram id 2498, ruling below 0
WU13 Ecualizador settings screen — restyle at 5 bands: header enable toggle, base-vs-per-station explainer, preset chips, 5 custom vertical sliders (mockup's 7 rejected — see below), "Guardar como preset" (NEW custom presets), "Salida activa" surfaced from Advanced, "Emisoras con ajuste propio" drill-down (NEW UI over existing presetsPorEmisora). No model, persistence, or EQ-test change from the band axis. WU3a 400-550 Low-Medium
WU14 Reproductor completo restructure: square art, favorite moved into transport row, single subtitle line, 4-tile tool-tray grid (EQ propio / Grabar / sleep-timer / Compartir) opening bottom sheets, quality row + "Cambiar". Per-station EQ sheet shares the WU13 editor component WU13 450-600 Medium-High
WU15 Grabaciones library (NEW list): storage usage bar, per-recording rows with playback, menu = Rename / Share / Delete constrained to what servicio_grabacion_radio.dart already exposes (resolution 6) WU3b 300-400 Medium
WU16 Connectivity states: offline banner restyle, reconnect attempt counter (verify the reconnect controller exposes an attempt count before promising the label), shimmer skeletons already correct WU1 150-250 Low
WU17 Welcome / onboarding screen (14): full-screen route in the new visual language — logo, headline, body, 3 feature bullets, "Empezar a escuchar" CTA. No PRO pill, no "14 días", no pricing card, no secondary "free version" link (resolution 7) WU1 150-200 Low
WU18 i18n batch: owns both new keys AND modified values for existing keys. ~35-45 new keys across all 13 ARB locales (~455-585 translated strings), plus the navHome value in the 11 locales WU1 did not touch. Run once, after WU1-WU17 land in en/es all 0 eng. Low

Total estimated changed lines: ~4,550-6,450, excluding tests.

Resolved ruling — the Inicio-to-Escuchar rename changes the ARB value, not the key.

Verified directly: navHome sits at line 4 of all 13 ARB files (app_en.arb = "Home", app_es.arb = "Inicio", and 11 further localized values — Beranda, Accueil, Start, Início, Inizio, ホーム, 首页, Главная, الرئيسية, হোম, मुखपृष्ठ). So this is a value change to an existing key, not a new key — which is why neither WU1's original scope nor WU18's "new keys" scope covered it. Both are corrected above.

Consequence for WU18: this is 13 real translations of "Listen", not a find-and-replace. Only en and es are mechanical; the other 11 need a translator, exactly like any new key.

Option Ruling
Change navHome's value in place ADOPTED. 13 value edits, zero key churn, zero call-site churn, and no window where a locale can drift out of key-set parity mid-refactor.
Introduce navListen, retire navHome Rejected.

Reasoning for the rejection: binding decision 2 keeps PluriIconGlyph.home as the Escuchar tab icon. "Home" therefore already survives as this tab's internal identifier by explicit user decision. Renaming the ARB key to navListen while the icon glyph stays home would make internal naming less consistent, not more, and would drag every call site and any key-asserting test along for no user-visible gain.

Ownership split (so no two units edit the same line): WU1 changes the value in app_en.arb and app_es.arb — the two locales every slice lands in per approach point 9. WU18 carries it to the remaining 11 locales alongside the new-key batch.

Resolved ruling — equalizer stays at 5 bands. The mockup's 7-band layout (t4, screen 11) is rejected as not reliably implementable. Spike evidence (Engram sdd/rediseno-funcional/eq-band-spike, id 2498):

  • There is no native Kotlin equalizer code at all — zero MethodChannel/EventChannel EQ plumbing under android/app/src/main/kotlin. The Kotlin side holds only the waveform downsampler and the alarm service.
  • Equalization runs through just_audio's AndroidEqualizer, which wraps the Android system AudioEffect Equalizer. Band count comes from params.bands, reported by the device/OS — the app does not choose it. Most Android devices report exactly 5.
  • lib/servicios/servicio_audio.dart:749-762 is the only place gains are applied and already guards with i < params.bands.length && i < preset.bandas.length. Rendering 7 sliders would silently no-op the last two on typical hardware — shipping controls that do nothing.
  • lib/modelos/preset_ecualizador.dart:5-8 hard-asserts bandas.length == 5.

Consequence: WU13 is a restyle only. No model change, no persistence change, no EQ test churn from this axis.

Verified contract — Radio Browser countries (WU7). Checked against https://api.radio-browser.info/ and https://de1.api.radio-browser.info/. Full detail: Engram reference/radio-browser-countries-endpoint (id 2500). This closes half of open question 8. Downstream phases use these facts verbatim — do not re-derive them.

  • Endpoint: /json/countries, optional substring filter /json/countries/{filter}. /json/countrycodes is DEPRECATED — do not use it.
  • Response fields are exactly name (string), iso_3166_1 (string), and stationcount (STRING, not int). An as int cast throws at runtime. Parse with int.tryParse.
  • Params/defaults: order (default name, also accepts stationcount), reverse (false), hidebroken (false), offset (0), limit (100000).

Two implementation constraints this imposes:

  1. lib/servicios/servicio_radio.dart:168 hardcodes lastcheckok: '1' into every request built by _get. That is a station-only filter and must NOT be sent to /json/countries. The countries call therefore cannot reuse _get as-is.
  2. Required shape: extract the transport loop from _get (server discovery, host rotation, bounded retries, User-Agent, timeout, status check, json.decode) into a generic private helper returning List<dynamic>. _get then layers the station-specific concerns on top (lastcheckok, Emisora.fromApi, _compararCalidad sort); the countries call reuses transport only. No behaviour change for the 8 existing methods — but this touches a shared code path, so WU7 must ship regression coverage for those 8 calls.

Already correct, needs no work: _uri (lines 103-105) forces hidebroken=true on every request, which is exactly what we want given the endpoint default of false; mirror discovery via all.api.radio-browser.info matches the documented guidance; the User-Agent is built per instance at lines 68-86.

Still open under question 8: the search sort control (screen 6). Not yet verified — WU6 opens with an official- docs check before anything is specced.

Out of Scope — explicitly

Excluded Why
Monetization: PRO tier, 14-day trial, pricing card, IAP, subscription state, feature gating, the Settings "PRO · 11 meses" pill User decision (resolution 7). Zero monetization infrastructure exists today; in_app_purchase stays commented out at pubspec.yaml:56. Ships as its own SDD change at public release. Do not display a paid-tier promise the app cannot honour.
Alarm-ringing live volume countdown ("Subiendo volumen · 18 s") Resolution 4. Requires a native to Flutter progress channel that is deliberately absent per a documented architecture constraint in pantalla_alarma_sonando.dart. Static label only.
Any literal Android Auto pixel work (A1-A5) The app uses the classic MediaBrowserService model (automotive_app_desc.xml, no androidx.car.app). Android Auto's system templates render everything; the only controllable surface is MediaItem metadata, which already matches. A4's waveform is non-implementable on this platform. WU2 is verification-only.
Adopting the mockup's 4-root IA (Escuchar / Explorar / Alarmas / Ajustes) Binding decision 1. Would delete the Favoritos root and demote the player to a pushed route.
Switching tab icons to Material Symbols Binding decision 2. PluriIconGlyph.* stays; PluriIconGlyph.home becomes the Escuchar icon.
The mockup's 7-band equalizer Rejected outright. Band count is device-reported via just_audio's AndroidEqualizer (params.bands), not app-chosen; most devices report 5, so 2 of 7 sliders would silently do nothing. See the resolved ruling above.
A runtime-dynamic band count driven by params.bands.length Technically the only correct way to support non-5-band devices, but it ripples through the fixed-length assert(bandas.length == 5), the 7 SharedPreferences keys in servicio_ecualizador.dart, and every EQ test. Worth its own future change — not this one.
Domain/business-logic changes to the EQ resolution hierarchy Already a 4-level matrix in estado_ecualizador.dart. The redesign relocates editing surfaces only.

Capabilities

Contract with sdd-spec. Existing spec names verified against openspec/specs/.

New Capabilities

  • app-navigation-shell: the 5-root tab contract, the push-chrome rule (56px header + back + no tab bar) and its explicit Favoritos exemption, and root-to-root switching (Escuchar "Ver todas" to Favoritos).
  • station-discovery-browse: discovery content as the Buscar landing state, active-filter pills, results counter, clear-all-filters, the Países browser over the verified /json/countries contract, and search sorting only if official docs confirm support.
  • favorites-organization: chip-filtered flat list, drag-to-reorder, sort action, group management surfaced from Favoritos.
  • alarm-vacation-ranges: active-range detection, per-alarm pause-impact computation, upcoming and past ranges.
  • recordings-library: browsable recordings list, storage usage, per-recording rename/share/delete.
  • eq-custom-presets: user-defined preset save/name/list, per-station EQ entry point from the player, "stations with their own EQ" drill-down.
  • onboarding-welcome: first-run welcome surface, explicitly monetization-free.

Modified Capabilities

  • multi-device-eq: the per-station EQ editing surface moves into the player's tool tray and gains custom presets. Band count stays 5 — resolved, not pending.
  • native-alarms: alarm-card affordances change (tap = edit, swipe = delete), vacation ranges move to a dedicated screen, ringing-screen presentation changes. Scheduling and dismiss-guard semantics are unchanged and must not regress.
  • android-auto-media: no requirement change — verification only, listed here so sdd-spec records the confirmation rather than writing a delta.

Approach

  1. Tokens before screens. WU1 lands the named tokens and text styles first so the copy-paste .copyWith(fontWeight: w900) pattern is not re-applied 14 times and then refactored 14 times.
  2. Cheap certainty first. WU2 (Android Auto) costs nothing and formally closes 5 of the 19 mockup screens.
  3. Mechanical before creative. The Settings restructure (WU3a/WU3b) is the largest line count in the plan but the lowest logic risk — panels become nav rows, controls are moved verbatim. Landing it early proves the push-chrome primitive against real screens.
  4. Restyle, do not re-architect, the EQ. The band-count question is settled at 5. WU13 changes only the presentation layer; preset_ecualizador.dart, servicio_ecualizador.dart, and servicio_audio.dart are untouched by the band axis.
  5. Share components, do not duplicate. WU13 (Settings EQ editor) ships before WU14 (player tool tray) so the per-station EQ bottom sheet reuses the same editor widget.
  6. Verify external APIs officially, then record the contract. Per the standing rule above. The /json/countries contract is already verified and recorded; search sort is not, and WU6 starts by checking it.
  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.
  9. Translate once. WU18 batches all new ARB keys at the end rather than paying 13-locale cost per slice.

Affected Areas

Area Impact Description
lib/tema/pluriwave_tokens.dart, pluriwave_theme.dart Modified 3 new colour tokens, named text styles. No token removed, none changed.
lib/app.dart, lib/widgets/pluri_bottom_navigation.dart Modified WU1 retitles the first tab to Escuchar; PluriIconGlyph.home unchanged. Tab list otherwise stable.
lib/pantallas/pantalla_inicio.dart (413 lines) Restructured Discovery content out, player hero + favorites grid in.
lib/pantallas/pantalla_buscar.dart (357 lines) Restructured Gains discovery landing state, filter pills, counter, sort.
lib/pantallas/pantalla_favoritos.dart (287 lines) Restructured Stacked panels to chip-filtered reorderable list.
lib/pantallas/pantalla_alarmas.dart (1383 lines) Restructured + split Cards simplified; vacation panel extracted to its own screen; editor sheet rewritten.
lib/pantallas/pantalla_alarma_sonando.dart (313 lines) Restyled Full-bleed layout. Dismiss guard + force-stop banner preserved.
lib/pantallas/pantalla_ajustes.dart (1896 lines) Split 12 panels to 4 grouped nav lists + ~8 new detail screen files.
lib/pantallas/pantalla_reproductor.dart (907 lines) Restructured Square art, transport row, 4-tile tool tray, share, quality switch.
lib/pantallas/ (new files) New Países, Vacaciones manager, recordings library, welcome, ~8 settings detail screens.
lib/modelos/preset_ecualizador.dart Modified Custom presets only. assert(bandas.length == 5) stays.
lib/servicios/servicio_ecualizador.dart, lib/servicios/servicio_audio.dart Unchanged EQ band handling and gain application are untouched — restyle only.
lib/estado/estado_alarmas.dart Modified New queries: current range, per-alarm impact, past ranges.
lib/servicios/servicio_radio.dart Modified Extract the transport loop out of _get into a generic helper (WU7). _get keeps lastcheckok/Emisora.fromApi/sort. Shared code path — needs regression coverage for all 8 existing station calls.
lib/estado/estado_busqueda.dart Modified Country list + counts; sort parameter only if official docs confirm support (WU6).
lib/l10n/app_*.arb (13 files) Modified ~35-45 new keys each, plus modified values for existing keys — known: navHome (WU1 does en/es, WU18 does the other 11). Key sets stay identical throughout.
lib/servicios/navegacion_auto.dart Unchanged Verification only.
test/ (59 files, 15,143 lines) Modified + New See risk register.

Risks

Risk Likelihood Impact Mitigation
EQ 5 to 7 bands blocked by the native layerRETIRED, resolved by spike (Engram id 2498). Band count is device-reported via params.bands; 7 sliders would silently no-op on typical 5-band hardware. Closed. Equalizer stays at 5 bands; WU13 is a restyle. Residual risk moves to the row below.
Spec drift back toward 7 bands. The approved mockup still draws 7 sliders, so a downstream phase could copy it from the design source without reading this ruling. Medium Medium The 5-band ruling is stated in the WU table, the resolved ruling block, Out of Scope, Capabilities, and Success Criteria. Any spec or task asserting 7 bands is rejected on sight. assert(bandas.length == 5) in preset_ecualizador.dart is the compile-time backstop.
Non-5-band devices remain unsupported. Devices whose OS reports 6+ bands get the extra bands left at default gain. This is pre-existing behaviour, not a regression introduced here. Low Low Explicitly out of scope. A runtime-dynamic band count is its own future change — it touches the fixed-length assert, the 7 SharedPreferences keys in servicio_ecualizador.dart, and every EQ test.
pantalla_ajustes.dart (1896 lines) restructure. Largest single extraction in the plan; ~8 new files. High (it will happen) Medium Mechanical extraction — controls move verbatim, business logic untouched. Split across WU3a/WU3b so neither PR exceeds ~450 lines. pantalla_ajustes_test.dart gets a near-total rewrite, written first per strict TDD.
15,143 lines of existing tests assume the current structure. Confirmed impacted: pantalla_inicio_test, pantalla_inicio_rebuild_test, pantalla_favoritos_plural_test, pantalla_buscar_shimmer_test, pantalla_alarmas_editor_test, pantalla_alarmas_fecha_test, pantalla_ajustes_test, reconnect_ui_test, widget_test. The three EQ test files (servicio_ecualizador_test, estado_ecualizador_test, servicio_audio_eq_reapply_test) are no longer at risk now that band count is fixed at 5. High Medium Each WU owns its test updates; no WU merges with a red suite. Hard rule: pantalla_alarma_sonando_dismiss_guard_test.dart must pass unmodified — if a restyle requires changing it, stop and escalate, because that test guards a real safety mechanism.
Reconnect attempt counter may not exist in the controller Medium Low WU16 verifies servicio_audio_reconnect_test.dart first; if no attempt count is tracked, ship the banner restyle without the counter rather than adding plumbing.
Refactoring _get in servicio_radio.dart regresses the 8 existing station calls. WU7 must split transport from station-specific concerns because lastcheckok: '1' (line 168) cannot be sent to /json/countries. Medium High — every station listing in the app flows through _get Pure extraction, no behaviour change intended. WU7 ships regression coverage for all 8 existing station calls first (strict TDD: characterisation tests before the refactor). Server discovery, host rotation, retries, User-Agent and timeout move as one block — no logic edits during the move.
stationcount parsed as int and throws at runtime. The /json/countries field is a string, which reads like an int. Medium Medium Contract is recorded above and in Engram id 2500: parse with int.tryParse. Spec must state the field type explicitly; a fixture with a string stationcount is required in the WU7 tests.
Search sort support is still unverified (resolution 8, second half) Medium Medium WU6 opens with an official-docs check, not a code spike. Per the standing rule, no spec may assert a sort parameter that has not been confirmed at api.radio-browser.info. If unsupported, drop the "Ordenar" control rather than faking client-side ordering.
Downstream phases infer API behaviour instead of checking the docs. Medium High The standing rule is stated at the top of this proposal. sdd-spec and sdd-design must cite the official doc (or the recorded Engram contract) for every external-service claim. Uncited API claims are rejected.
Custom inline time editor (WU10) is the largest genuinely-new widget Medium Medium Isolated in its own PR, never bundled. Drag/tap adjust logic is unit-tested independently of the sheet.
i18n volume: ~455-585 translated strings across 13 locales High Low (ops, not eng.) WU18 batches once at the end. Slices land in en/es only.
Scope creep back into monetization via the "PRO" pill Low High Explicitly excluded above and in resolution 7. Any PR reintroducing it is rejected.

Delivery Shape

This change is roughly 4,550-6,450 changed lines excluding tests — an order of magnitude beyond the 400-line review budget. A single PR is not reviewable.

Recommendation: feature-branch-chain.

  • feat/rediseno-funcional is the tracker branch and the only branch that merges to main.
  • PR #1 targets feat/rediseno-funcional. Each subsequent child PR targets the immediately previous PR's branch, so review diffs stay scoped to one work unit. If GitHub shows earlier slices in a child diff, rebase/retarget until clean.
  • Rationale over stacked-to-main: this is a coordinated visual language change. Half-landed on main means a release where some screens use the new tokens and others do not. The tracker branch gives one atomic rollback.

Sequence (each row is one PR):

PR1  WU1   tokens + push-chrome primitive        (unblocks everything)
PR2  WU2   Android Auto verification             (zero code, quick win)
PR3  WU3a  Settings shell + AUDIO/EMISORAS
PR4  WU3b  Settings GRABACIONES/APLICACIÓN
PR5  WU4   Favoritos restyle
PR6  WU5   Escuchar restructure
PR7  WU6   Buscar landing + filters (spike first)
PR8  WU7   Países browser
PR9  WU8   Alarmas root restyle
PR10 WU9   Vacaciones manager
PR11 WU10  Alarm editor sheet          (own PR, never bundled)
PR12 WU11  Alarm ringing restyle       (safety-critical review)
PR13 WU13  Ecualizador settings screen (5-band restyle)
PR14 WU14  Reproductor + per-station EQ sheet
PR15 WU15  Recordings library
PR16 WU16  Connectivity states
PR17 WU17  Welcome / onboarding
PR18 WU18  i18n batch, 13 locales

18 PRs (was 19 — the EQ spike PR is gone, resolved before planning closed). PR2 is effort-light with no dependencies and may be pulled forward at any point. PR13 must merge before PR14 so the per-station EQ sheet reuses the same editor widget.

Commits: conventional commits only. No Co-Authored-By, no AI attribution, ever.

Rollback Plan

  • The repo has zero git tags; the rollback convention is the version-bump commit.
  • Full rollback: reset feat/rediseno-funcional to 17f8e69 on main (the 1.1.16+121 state before 1.2.0+122).
  • Partial rollback: because delivery is a feature-branch chain, any single PR can be reverted on the tracker branch without touching main. Nothing reaches main until the tracker merges.
  • WU2 (Android Auto) produces no code and needs no rollback.
  • lib/servicios/navegacion_auto.dart is untouched, so Android Auto behaviour cannot regress.

Dependencies

  • WU12 native EQ spikeresolved before planning closed; no longer a dependency. See Engram sdd/rediseno-funcional/eq-band-spike (id 2498).
  • Radio Browser /json/countries (WU7) — verified against the official docs; contract recorded above and in Engram id 2500. No longer a blocker.
  • Radio Browser search sort (WU6) — not yet verified. Must be checked against the official docs before it is specced. Per the standing rule, do not assume.
  • Translation capacity for 13 locales (WU18) — an ops dependency, not an engineering one.
  • No new packages anticipated. in_app_purchase stays commented out.

Success Criteria

  • All 5 tabs present and named Escuchar / Buscar / Favoritos / Alarmas / Ajustes, using existing PluriIconGlyph.* icons, with PluriIconGlyph.home on Escuchar.
  • Favoritos renders its tab bar (the documented exemption from the push-chrome rule).
  • Every second-level screen renders a 56px header with a back affordance and no tab bar.
  • pantalla_ajustes.dart root renders 4 grouped nav lists, zero inline controls, and is under 400 lines.
  • Escuchar renders an embedded player hero and a "Tus emisoras" grid sourced from favorites; "Ver todas" switches to the Favoritos tab without pushing a route.
  • Discovery content (near-you / genres / trending / countries) is reachable from the Buscar tab's landing state.
  • No string in the shipped build contains "PRO", a trial duration, or a price. Grep-checkable across lib/l10n/app_*.arb.
  • The alarm-ringing status label renders without a seconds counter, and the force-stop retry banner is still present.
  • test/pantallas/pantalla_alarma_sonando_dismiss_guard_test.dart passes unmodified.
  • test/servicios/navegacion_auto_test.dart passes with lib/servicios/navegacion_auto.dart unchanged (git diff is empty for that file).
  • Alarm edit, skip-next, and delete are all still reachable (tap / hero pill / swipe respectively) — no capability lost.
  • Sleep timer and backup/restore are still reachable from Settings.
  • 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.
  • 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/.
  • stationcount is parsed with int.tryParse; a test fixture supplies it as a string and the screen renders the count without throwing.
  • lastcheckok is not present in the countries request. All 8 existing station calls still send it.
  • Regression tests for the 8 existing ServicioRadio station calls exist and pass both before and after the _get transport extraction.
  • Every external-API claim in the spec, design, and tasks artifacts cites either the official Radio Browser docs or a recorded Engram contract. No uncited API assertions.