# Handoff 2026-02-02 — Manual Segmentation (Matplotlib Interactive) — Expected Behavior Spec

## Goal
Add a **human-in-the-loop manual segmentation workflow** using an interactive Matplotlib figure.

The user selects boundary times on a plot. The fitter uses these boundaries as **literal cut points** (no optimization / wiggle) and computes piecewise decay fits accordingly.

This is an R&D/validation feature intended to:
- quickly test plausible segmentations (transient / established / noise)
- generate reproducible boundary selections stored in metadata
- optionally recompute and redraw fits in near real-time

---

## Non-Goals (Explicit)
- No automatic boundary optimization (“wiggle fit optimization”) in this version
- Do not remove existing automatic segmentation workflows
- Do not require Textual/TUI interaction for this feature

---

## Core UX: Interactive Matplotlib Window

### Base Plot Content
The interactive window must display (minimum):
1) The envelope (or fitted response proxy) vs time
2) The current piecewise fit overlays corresponding to selected boundaries
3) Vertical boundary markers at each selected point

### Selection Model
- User selects boundary points by clicking on the plot area.
- Selected boundaries are **literal x-values** (time) mapped to nearest valid sample index.
- Selected boundaries are maintained as a sorted list in **increasing time order**.

#### Boundary Count → Number of Pieces
Let boundaries be: `b = [b0, b1, ..., bk-1]` sorted by time.

- If `k < 2`: do not perform piecewise segmentation (insufficient info)
- If `k == 2`: fit **1 piece** on `[b0, b1]`
- If `k == 3`: fit **2 pieces** on `[b0, b1]` and `[b1, b2]`
- In general: if `k == m+1`, fit **m pieces** on consecutive intervals.

Notes:
- This model matches the user expectation: “2 points → 1 curve; 3 points → 2 curves; …”.
- Endpoints are explicit. The user defines the span of fitting.

### Mapping Clicks to Data
- A click yields `x_click` in time (seconds).
- Convert to index `i_click` by snapping to nearest time sample:
  - `i_click = argmin(|t - x_click|)`
- Store both:
  - `t_click = t[i_click]`
  - `i_click`
- Display uses `t_click` for deterministic reproducibility.

---

## Controls

### Required Commands
Provide explicit keyboard commands **and/or** Matplotlib Button widgets.

Minimum required actions:

1) **Add point**
- Action: mouse left-click in axes
- Effect: add snapped boundary point, then re-render boundary lines and info

2) **Clear all points**
- Command: `c` (or a button labeled `Clear`)
- Effect: remove all boundaries and reset plot overlays to “no manual segmentation” state

3) **Delete closest point**
- Command: `d` (or a button labeled `Delete nearest`)
- Behavior:
  - choose the boundary whose `t_click` is closest to the current mouse x position
  - delete it
  - redraw

4) **Toggle y-scale lin/log**
- Command: `l` (or a button labeled `Lin/Log`)
- Effect:
  - toggles y-axis scale between `linear` and `log`
  - boundary markers must persist and remain correctly located
  - fit overlays must persist
  - no redefinition/recomputation of boundary positions is allowed during scale toggle

### Optional but Recommended
- Undo last point: `u`
- Confirm/commit selection and exit: `enter`
- Quit without saving: `q`

---

## Minimal Selection Info Display (Required)

The figure must provide minimal, always-visible state feedback.

At minimum show:
- number of selected boundaries `k`
- number of fitted pieces `max(k-1, 0)`
- list of boundary times (snapped): `t = [t0, t1, ...]`

Display location can be:
- a small text box in the plot (top-left), or
- figure suptitle subtitle, or
- a dedicated axis annotation region.

---

## Real-Time Fit Recompute (Preferred)

### Target Behavior
After each add/delete/clear action:
- recompute piecewise fits immediately (if enough points)
- redraw overlays

### Performance Constraint
If recomputation is too slow for real-time interaction:
- degrade gracefully by showing a “Pending recompute” flag in the info display
- allow explicit recompute trigger via key `r`

Default preference: **instant recompute**.

---

## Fit Computation Semantics (Literal Boundaries)

Given snapped boundaries (times → indices):
- define segments as consecutive index ranges
- for each segment, run the existing decay fitting routine on that subset
- compute and overlay:
  - fitted envelope curve on the segment
  - optional derived quantities (ζ, slope) in legend labels

Important:
- do not adjust boundaries during fitting
- do not attempt local optimization around boundaries

---

## Output and Persistence (Required)

When the user commits (e.g., `enter`) or the session completes:

Persist in plot metadata / JSON output:
- `manual_segmentation.enabled = true/false`
- `manual_segmentation.boundaries_time_s = [t0, t1, ...]`
- `manual_segmentation.boundaries_index = [i0, i1, ...]`
- any fit results computed per segment

This ensures exact reproducibility.

---

## Safety / Validity Rules

- Boundaries must be unique (deduplicate after snapping)
- Boundaries must be sorted by time
- Reject segmentation if any segment has fewer than `min_points` samples
  - show a warning in the info display
  - do not crash

---

## Summary

Implement a Matplotlib interactive manual segmentation tool where:
- the user clicks explicit boundary points
- boundaries are snapped to samples and treated as literal
- piecewise fits are recomputed (preferably instantly) and overlaid
- user can clear points, delete nearest point, view minimal state info
- y-axis scale toggles lin/log without breaking boundary locations

---

## Status
- Behavior spec: **locked**
- Ready for OpenCode implementation

