# 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.1–5.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).