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

63 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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).