# handoff-260211-1.md
## Scope / Problem Framing

TxSpanUI (the “box fitting” / span-based Tx measurement UI) is currently **dB-first**: it plots `Envelope (dB re max)` and the overlay geometry (rectangle + diagonal) is drawn in dB-space.

During monkey testing, a key workflow from the “piecewise/manual segmentation” UI is missing in TxSpanUI:

- In **manual segmentation UI** (piecewise method), the envelope is plotted in **linear amplitude** and the user can toggle **linear vs log y-axis scale** with `l`. This makes it easy to sanity-check whether the “tail” is just noise.
  - File: `src/envelope_decay_fit/segmentation/manual.py`
  - Key binding: `l` → `_toggle_scale()` (linear ↔ log)
  - Default: log-scale, linear envelope amplitude

- In **TxSpanUI**, there is no equivalent one-key way to switch the visualization into a “raw amplitude” view to answer: **“am I fitting noise?”**
  - File: `src/envelope_decay_fit/segmentation/tx_span.py`
  - Key bindings today: only `t` (cycle Tx) and `q` (quit)

User intent to carry over:
> When in dB mode, the late tail can look like a clean decay even if it is noise. A quick flip to **linear amplitude view** is often the fastest way to validate that the chosen span is not purely fitting noise.

This handoff requests implementing a TxSpanUI “display flip” patterned after the manual segmentation UI’s one-key toggle workflow, but adapted to TxSpan’s dB-based measurement.

---

## Specs

### S-260211-1.1 — Add a display-mode toggle in TxSpanUI
Add a one-keystroke toggle in `TxSpanUI` that flips the displayed y-data between:

- **Mode A (current):** log-amplitude **dB re max(env)** (computed by `_compute_log_envelope_db(env)`)
- **Mode B (new):** **linear envelope amplitude** (recommended: `abs(env)` or `env` if always positive)

This toggle is purely for visualization / QA. It must not affect what points are selected or what measurement is reported.

### S-260211-1.2 — Keybinding parity with piecewise UI
Use the same ergonomic “single-key flip” pattern as the piecewise/manual UI.

Recommendation:
- Bind the display toggle to `l` (same key used in `ManualSegmentationUI`), but note: in manual UI `l` toggles axis scale; in TxSpanUI, `l` will toggle **display mode** (dB ↔ amplitude).

(We’re not changing manual UI behavior.)

### S-260211-1.3 — Keep Tx measurement logic unchanged
`TxSpanUI.measurement` must remain computed in the existing dB workflow:

- `compute_tx_span_measurement(...)` stays as-is
- linearity metrics + flags remain based on `y_db` (current behavior)
- reported `slope_db_per_s`, `zeta`, and flags are unchanged

### S-260211-1.4 — Overlay remains coherent in both modes
The overlay rectangle + diagonal currently assume y-values are in dB.

After the change:
- In **dB mode**: keep the current overlay behavior (no change)
- In **amplitude mode**: draw the overlay using amplitude units so it is visually coherent:
  - still use the active `Tx_db` value, but convert it into an amplitude ratio using the existing amplitude-dB convention (`20*log10`):
    - `ratio = 10 ** (-Tx_db / 20)`
    - if `y0_amp` is the envelope amplitude at `t0`, then `y1_amp = y0_amp * ratio`
  - rectangle should represent the drop from `y0_amp` to `y1_amp` across `[t0, t1]`
  - diagonal line connects `(t0, y0_amp)` to `(t1, y1_amp)`

This ensures the overlay remains meaningful while the *measurement stays dB-based*.

### S-260211-1.5 — Axis labeling updates with mode
When toggling display mode, update:
- line label (legend) and/or ylabel to reflect the current mode:
  - dB mode: `Envelope (dB re max)` (current)
  - amplitude mode: `Envelope amplitude` (match manual UI wording)

Grid/title remain unchanged.

---

## Implementation

### I-260211-1.1 — Introduce display-mode state in TxSpanUI
File: `src/envelope_decay_fit/segmentation/tx_span.py`

Add a field on `TxSpanUI`, e.g.:
- `display_mode: str = "db"` (values: `"db"` or `"amp"`)

Optional: precompute both representations in `run()` and store:
- `self._y_db = _compute_log_envelope_db(self.env)`
- `self._y_amp = np.abs(self.env)`  (or `self.env` if guaranteed positive)

This avoids recomputing on every refresh.

### I-260211-1.2 — Extend `_on_key` to handle `l`
Current `_on_key` only handles `t` and `q`.
Add:

- `if key == "l": self._toggle_display_mode(); return`

Add new method:
- `_toggle_display_mode()`:
  - flip `self.display_mode`
  - update the plotted line data
  - update ylabel and line label
  - call `_refresh_overlay()` (so overlay matches mode)
  - redraw

### I-260211-1.3 — Refactor overlay refresh to branch on mode
In `_refresh_overlay()` (same file), it currently:
- computes measurement in dB space (keep)
- computes `y_db = _compute_log_envelope_db(env)` and uses it to compute `y0`, `y1`
- draws rectangle/line in dB units

Refactor so:
- measurement is computed first (as today)
- choose “display y array” based on mode:
  - dB mode: use `y_db` and `y1 = y0 - Tx_db`
  - amplitude mode: use `y_amp` and `y1 = y0 * 10**(-Tx_db/20)`

Important: when using amplitude mode, use `finite_mask` based on `y_amp` (e.g., `np.isfinite(y_amp)`), and ensure the mask excludes nonpositive/NaN as needed.

### I-260211-1.4 — Keep API surface stable
No changes to:
- `compute_tx_span_measurement`
- exported `TxSpanMeasurement`
- CLI API (unless TxSpanUI is invoked there; behavior should remain stable)

---

## Tests

### T-260211-1.1 — Update / add TxSpanUI key toggle smoke test
File: `tests/test_tx_span_ui.py`

Add a test that:
1. Instantiates `TxSpanUI` with a simple envelope
2. Runs headless (`monkeypatch plt.show`)
3. Calls `ui._on_key(...)` with a stub event where `event.key = "l"`
4. Asserts:
   - `ui.display_mode` flipped from `"db"` to `"amp"` (or vice-versa)
   - `ui.env_line.get_ydata()` now matches the expected representation:
     - if amp mode: approximately `abs(env)` (or `env`)
     - if dB mode: approximately `_compute_log_envelope_db(env)`
   - `ui.ax.get_ylabel()` matches the expected label for the mode

(Use a minimal “Event stub” object with `.key` attribute.)

### T-260211-1.2 — Overlay conversion sanity (unit-ish test)
Add a small test (same file or new) that:
- sets a known `t0,t1`, sets `Tx_db` to 20, and a known `y0_amp`
- toggles to amp mode, calls `_refresh_overlay()`
- asserts overlay line y-values match:
  - `y1_amp = y0_amp * 10**(-Tx_db/20)`

Keep it tolerant (float approx).

---

## Definition of Done

### DoD-260211-1.1
In TxSpanUI interactive session:
- pressing `l` visibly toggles between:
  - dB re max view
  - linear amplitude view

### DoD-260211-1.2
Overlay remains meaningful in both modes:
- rectangle + diagonal reflect `Tx_db` in the active display units

### DoD-260211-1.3
All tests pass, including new toggle tests.

### DoD-260211-1.4
No changes in reported TxSpanMeasurement values (same span, same Tx selection).

---

## Non-goals

- NG-260211-1.1: Do not add vertical zoom / ylim framing controls.
- NG-260211-1.2: Do not change Tx measurement math, flags, or how zeta is computed.
- NG-260211-1.3: Do not alter manual segmentation UI behavior or keybindings.

