Files
pluriwave/openspec/changes/archive/2026-07-10-eq-device-disconnect-revert/proposal.md
T
FreeTLab 8f7ca8059b
Build & Deploy PluriWave / Análisis de código (push) Successful in 36s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m30s
fix(eq): resolve base-speaker preset live instead of pinning a stale copy
_onDispositivoCambiado() bootstrapped a device-level preset entry for
every never-seen device id, including the built-in speaker. That
persistent level-3 entry masked later global-preset edits (level 3
beats level 4 on every resolution), so disconnecting a BT device or
cold-starting without one could leave the EQ stuck on an outdated
copy instead of the current global preset.

The base speaker is now excluded from the first-seen bootstrap:
disconnect and cold start always resolve through the live hierarchy.
BT/wired/USB devices keep their bootstrap behavior unchanged.
2026-07-10 23:54:01 +02:00

4.5 KiB
Raw Blame History

Proposal: EQ Device Disconnect / Cold-Start Revert

Intent

When an audio device disconnects — or the app opens with no external device connected — the EQ must return to the phone/base configuration, not stay on a stale device preset. Today _onDispositivoCambiado() (estado_ecualizador.dart:210-227) treats every device event identically: there is no explicit disconnect path, no cold-start guarantee, and ZERO disconnect test coverage. On disconnect it re-resolves the hierarchy for the now-active device, which only coincidentally looks like a revert when no station/matrix override interferes. This closes a correctness gap, not a regression.

Scope

In Scope

  • Explicit disconnect handling: re-resolve + APPLY the preset for the now-active device (typically builtin_speaker).
  • Cold-start guarantee: app open with no device connected applies the phone/base resolution, never a stale persisted device preset.
  • Verify the resolved preset reaches the native EQ via the existing audio.aplicarPreset() choke-point (not state-only mutation).
  • Test coverage: disconnect event, cold start without device, connect→disconnect→reconnect cycle, station/matrix override interaction.
  • Respect _eqMultiDeviceEnabled: when OFF, behavior is unchanged (2-level resolution).

Out of Scope

  • Audio-focus ducking EQ re-apply (separate change eq-audiofocus-reapply, Part B).
  • Native Kotlin/Swift device detection (MainActivity.kt, pluriwave/audio_devices) — already emits add/remove correctly.
  • New persistence fields or migration; matrix/station/device preset storage is unchanged.

Capabilities

New Capabilities

  • None.

Modified Capabilities

  • multi-device-eq: revert-on-disconnect and cold-start resolution become explicit, guaranteed requirements. "Revert" = deterministic re-resolution of the existing 4-level hierarchy (matrix → station → device → global) for the now-active device — NOT a snapshot/restore. This is stateless, self-healing, and the only mechanism covering the cold-start case (no snapshot exists to restore).

Approach

Make the disconnect path explicit inside the device-event handler (_onDispositivoCambiado, or a split connect/disconnect dispatch). On any device event — connect, disconnect, or cold-start seed — set _dispositivoActualId to the now-active device, run _resolverPresetActivo(), assign _presetActual, and push it through audio.aplicarPreset(). Cold start already flows through _sembrarDispositivoActual()obtenerDispositivoActual() (fallback builtin_speaker); the change guarantees the resolved base preset is APPLIED on that path, not just seeded. The first-seen bootstrap (presetsDispositivo write) must NOT hijack revert resolution for the base device.

Affected Areas

Area Impact Description
lib/estado/estado_ecualizador.dart Modified Explicit disconnect/cold-start resolve+apply in the device handler
test/estado/estado_ecualizador_test.dart Modified New disconnect, cold-start, reconnect-cycle, override-matrix cases

Risks

Risk Likelihood Mitigation
Regress passing connect/4-level suite (Phase 5, tests 5.15.8, 2.x) Med Reuse _resolverPresetActivo(); run full estado_ecualizador_test.dart green before/after
First-seen bootstrap overwrites base on disconnect Med Guard bootstrap so revert to builtin_speaker resolves from hierarchy, not a forced copy
Double-apply / redundant native pushes on rapid events Low Idempotent apply choke-point; skip when resolved preset unchanged

Rollback Plan

Single-file logic change plus tests. Revert the commit(s) touching estado_ecualizador.dart and its test file; no persistence schema, native, or public-API change means rollback restores the prior coincidental-revert behavior with zero migration.

Dependencies

  • Shared exploration sdd/eq-apply-lifecycle/explore (Part A section). Locked decision: re-resolution, not snapshot/restore.

Success Criteria

  • Disconnect event re-resolves and applies the base-device preset via aplicarPreset().
  • Cold start with no device applies phone/base resolution, not a stale device preset.
  • connect→disconnect→reconnect cycle lands on the correct preset at each step, including station/matrix overrides.
  • With _eqMultiDeviceEnabled=false, behavior is byte-for-byte unchanged (2-level).
  • Full estado_ecualizador_test.dart suite stays green (no connect-path regression).