## 2026-01-04 (context: Tech debt analysis + Intrinsic Calibration Sprint planning)

### Dashboard UX Principles
- Progressive disclosure (summary → detail on click)
- "What's next?" banner answering user's primary question
- Status at a glance (< 2 seconds to understand state)
- Action proximity (actions near data they affect)
- Dashboard = status display, Tabs = workflow actions

### Workspace Widget Cleanup
- [ ] Filesystem watcher already exists (workspace_widget.py:22-25, 61) - reuse for granular signals
- [ ] Note: File management is interleaved with workflow, not a separate phase

### Signal/Slot Patterns Observed
- View→Controller: synchronous method calls (correct)
- Controller→View: asynchronous signals (correct)
- [ ] Consider documenting contracts in widget docstrings

## 2026-01-05 (context: working on #737 - link camera.undistort to frame_emitter)

### CameraUndistortView Enhancements (post-#737)
- [ ] **Bidirectional scaling** - Current view only zooms out (scale_factor > 1). Should support:
  - Zoom in (crop) when undistorted image is smaller than original
  - Zoom out (pad) when undistorted image is larger than original
  - Issue observed: left side of undistorted frame expands beyond bounds, "losing information"

- [ ] **Expose metadata for scale/crop indicator widget** - Future UI needs:
  - Original frame size and aspect ratio
  - Undistorted bounds relative to original frame
  - Current scale factor (actual zoom level)
  - Translation offset (where undistorted region sits)
  - **Rationale**: When viewing undistorted image, user can't tell if they're looking at
    80% zoom or 150% zoom without visual context. A small overlay widget showing
    original frame outline relative to current view would solve this.

### Future UI: Undistortion Visualization (post-#737)
- [ ] **Dual view for calibration quality inspection**:
  - View A: Scaled to show complete undistorted content (may have different aspect)
  - View B: Same-size output used for export (may clip or have borders)
  - Overlay showing original frame boundary on undistorted view
  - Helps user understand what data they're losing at edges
- [ ] **3D inspection viewer** (long-term):
  - Camera frustums in 3D scene with undistorted frame as texture on image plane
  - "Look through" each camera to compare 2D detections vs 3D projections
  - Would reveal calibration issues and distortion model problems
  - Requires: undistorted frames + knowing the projection matrix used

## 2026-01-07 (context: working on #861 - automatic calibration frame selector)

### Test Data Reorganization (future)
- [ ] **Consolidate test session structure** - Current test sessions are scattered with varying structures
  - Some have full calibration pipelines, some partial
  - CSV column schemas evolved (e.g., obj_loc_z added later)
  - Consider: canonical test session structure, documented schema versions
- [ ] **CharucoTracker export XYZ** - Should export obj_loc_x, obj_loc_y, obj_loc_z from the start
  - Currently z is backfilled as 0 for planar boards
  - Would simplify schema handling and backward compatibility
- [ ] **Test fixture cleanup** - After #835/#836 epic, prune tests that reference deprecated patterns
  - IntrinsicCalibrator tests may become obsolete
  - grid_history_q patterns should be removed

## 2026-01-09 (context: working on #835 - IntrinsicCalibrationPresenter)

### Type Inconsistency: CameraData.size
- [ ] **Unify image_size types across codebase** - `CameraData.size` is `list[int]` but
  `calibrate_intrinsics()` and `select_calibration_frames()` expect `tuple[int, int]`.
  - Currently working around with conversion: `(camera.size[0], camera.size[1])`
  - Should either:
    - Change `CameraData.size` to `tuple[int, int]` (breaking change, more correct)
    - Change function signatures to accept `Sequence[int]` (more flexible)
  - `tuple[int, int]` is more semantically correct for fixed-size pairs

### Type Inconsistency: FramePacket.frame
- [ ] **Fix FramePacket.frame type annotation** - Currently typed as `NDArray[float64]` but
  OpenCV frames are `uint8`. Should be `NDArray[np.uint8]` or more permissive `NDArray`.

## 2026-01-10 (context: working on #871 - RecordedStream TaskManager integration)

### threading.Condition for Zero-CPU Pause (learning exercise)
- [ ] **Refactor pause spinlock to use Condition** - Current sleep-based polling works but:
  - Wakes every 100ms even when nothing changed
  - ~100ms latency to respond to unpause
  - Small but non-zero CPU usage

  Better pattern using `threading.Condition`:
  ```python
  from threading import Condition

  # In __init__:
  self._pause_condition = Condition()
  self._paused = False

  # In loop (replaces spinlock):
  with self._pause_condition:
      while self._paused and not token.is_cancelled:
          if not self._jump_q.empty():
              break
          self._pause_condition.wait(timeout=0.1)  # Zero CPU during wait

  # To pause:
  with self._pause_condition:
      self._paused = True

  # To unpause:
  with self._pause_condition:
      self._paused = False
      self._pause_condition.notify()  # Wakes thread immediately
  ```

  Benefits: True blocking (zero CPU), instant response to unpause
  Trade-off: Slightly more complex API (need lock wrapper for pause/unpause)

## 2026-01-10 (context: working on #877 - TRC export fix)

### Post-Processing: Filtering for Noisy Landmarks
- [ ] **Explore Butterworth or other filtering** - Integration test for #877 revealed:
  - Body landmarks: ~1-5mm interpolation error (excellent)
  - Hand/finger landmarks: 30-150mm+ error (poor tracking + interpolation)
  - Linear interpolation can't fix fundamentally jittery tracking

  Filtering could help:
  - Smooth jitter in non-gap-filled data too
  - `WorldPoints.smooth()` exists but is behind `APPLY_EXPERIMENTAL_POST_PROCESSING` flag
  - Current impl: `butter(order, cutoff_freq, btype="low", fs=fps)` with `filtfilt`

  Questions to explore:
  - What cutoff frequency makes sense? (6Hz current default - seems low for fast motion)
  - Should filtering be per-landmark-type? (hands need different params than torso)
  - Does filtering before gap-fill or after matter?

  **Not urgent** - main focus is camera calibration. Capture here to revisit later.
