Skip to content

fix(catune): GT marker alignment + spectrum/zoom-window perf sweep#142

Merged
daharoni merged 6 commits into
mainfrom
fix/catune-gt-marker-and-perf-sweep
Apr 22, 2026
Merged

fix(catune): GT marker alignment + spectrum/zoom-window perf sweep#142
daharoni merged 6 commits into
mainfrom
fix/catune-gt-marker-and-perf-sweep

Conversation

@daharoni
Copy link
Copy Markdown
Contributor

@daharoni daharoni commented Apr 22, 2026

Summary

Four related CaTune fixes bundled into one PR — one correctness bug that prompted the sweep, plus three more wins turned up while auditing neighbouring code:

  • Ground-truth slider markers were drawn several pixels off from the slider thumb because flex: 1 on the range input was ineffective (parent wasn't flex) and the input stayed at its ~129px browser default. For GCaMP6f simulated data the true FWHM marker sat at ~800 ms instead of the correct ~695 ms. Fixed by making the track container flex and adding a thumb-half-width inset so edge values align exactly. Also converted the marker's inner consts to reactive accessors so it updates when trueValue or toSlider change.
  • Import-preview cell labels were 0-indexed ("Cell 0, Cell 1, …") while the rest of the UI is 1-indexed.
  • Spectrum panel tore down and rebuilt the whole uPlot chart on every cutoff change because the rebuild effect tracked the entire spectrumData signal. Split into a rebuild effect keyed on freqs identity (stable across cutoff-only updates) and a redraw effect keyed on cutoffs + filter toggle; the plugin reads cutoffs live.
  • Spectrum store registered effects and a setTimeout with no onCleanup, so re-imports left a pending timer firing into stale state and retained PSDs for cells that no longer existed. Added onCleanup for the timer and a resetSpectrumStore() wired into the reset path.
  • Zoom window recomputed raw-trace mean/std and deconvolved min/max inside createMemos on every visible card on every solver tick. Moved the work next to the data: CellTraces now carries precomputed rawStats (one-shot at import) and deconvMinMax (fused into the write path), eliminating the O(n) × N-cards scan per tick.

Test plan

  • npx tsc -b apps/catune — clean
  • npm run lint — clean
  • npm -w apps/catune test — 26 tests pass
  • HMR picks up each change without errors
  • Needs human UI verification (Playwright was not used):
    • GCaMP6f demo: ground-truth markers align with the slider thumb at true tPeak (~215 ms) and FWHM (~695 ms)
    • Import preview labels read "Cell 1, Cell 2, …"
    • Adjusting kernel params while the spectrum panel is open: cutoff lines move live, DevTools Performance shows redraw-only work (no uPlot rebuild)
    • Reset / swap demo presets a few times: no console warnings, no growing Float32Array retention in DevTools memory snapshots, spectrum reflects the new dataset
    • Peak/FWHM scrub on a ≥ 8-cell grid feels snappier than before, and zoom-window y-scaling looks identical

🤖 Generated with Claude Code

daharoni and others added 6 commits April 21, 2026 20:51
The true-value marker on Peak/FWHM sliders was drawn as `left: pct%` of
the track container, but `flex: 1` on the range input was ineffective
(parent wasn't flex) so the input kept its ~129px UA default. The marker
floated well to the right of the actual thumb position — users saw a
GCaMP6f simulated marker at ~300ms Peak / ~800ms FWHM instead of the
true ~215ms / ~695ms.

Make the track container `display: flex` so the input fills it, and add
a half-thumb-width inset correction so edge values align exactly (the
thumb center is at `+7px` at pct=0 and `-7px` at pct=100, not at the
literal container edges). Thumb width is kept as a named constant
mirroring the `14px` in `controls.css`.

Also convert the marker's computed values from frozen `const`s inside
the `<Show>` IIFE into reactive accessors, so the marker updates when
`trueValue` / `toSlider` change (e.g., swapping simulation presets).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TracePreview rendered traces as "Cell 0, Cell 1, …" while the rest of
the UI is 1-indexed (`CellCard` shows `Cell {cellIndex + 1}`,
`CellSelector` labels its input "Cell indices (1-indexed)"). Align the
import preview with the app-wide convention.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two issues in the spectrum panel that showed up during parameter tuning
and dataset re-imports:

1. The outer chart-build effect tracked the whole `spectrumData` signal.
   Cutoff-only updates (from tau changes) produced a new `spectrumData`
   object identity, so every tau tick tore down and rebuilt the uPlot
   chart instead of just repainting the two cutoff lines. Split into
   (a) a rebuild effect keyed on the `freqs` typed-array reference
   (stable across cutoff-only updates) and (b) a redraw effect keyed on
   cutoffs + filter toggle. The filter-band plugin now reads cutoffs
   live via accessors, so a plain `uplot.redraw()` picks up new values.

2. `initSpectrumStore()` registered effects and a debounce `setTimeout`
   on module-level state with no `onCleanup`. Re-importing a dataset
   left the previous timer pending (firing into stale state) and
   retained `psdCache` PSDs for cells that no longer existed. Add an
   `onCleanup` for the debounce timer, plus an exported
   `resetSpectrumStore()` that clears caches and last-seen refs; wire it
   into the reset path alongside `clearMultiCellState` / `resetImport`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`CaTuneZoomWindow` recomputed raw-trace mean/std and deconvolved
min/max from typed-array scans inside `createMemo`s. Because props flow
through the multi-cell store (which replaces the whole `CellTraces`
object on every solver write), the memos saw identity churn per tick
and re-ran on every visible card for every solver iteration — an O(n)
scan × N cards × tick cadence that Peak/FWHM drags felt in the
scripting cost.

Move the work next to the data:

- `CellTraces` gains a required `rawStats: RawTraceStats` (mean, std,
  zMin, zMax) and `deconvMinMax: [number, number]`. Helpers
  `computeRawStats` and `computeArrayMinMax` live in the store module.
- `updateOneCellTraces` fuses a single min/max pass with every solver
  write, so deconv min/max is up-to-date in O(n) per write (as opposed
  to O(n × visible cards) per write).
- `cell-solve-manager` computes `rawStats` once at initial cell
  insertion — raw traces never mutate during a session.
- `CardGrid` / `CellCard` / `CaTuneZoomWindow` thread the two fields
  through. The per-card `rawStats` / `deconvMinMax` / `pinnedDeconvMinMax`
  memos are removed. `gtSpikesMinMax` memo stays — GT data is stable
  and infrequent.

Test helper `seedCellTraces` updated to the new required fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI format check caught two lines that fit within the project's width
after the earlier edits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous refactor split the chart setup into a rebuild effect keyed
on a `createMemo(() => spectrumData()?.freqs)` and a redraw effect keyed
on cutoffs. In practice this left the chart blank on first load (likely
an HMR/tracking interaction around the memo's undefined→Float64Array
transition interleaving with `container` signal updates from the Show's
ref).

Fold both paths back into a single effect that tracks spectrumData and
container, same shape as the pre-refactor version. Skip the
destroy-and-rebuild path when the freqs typed-array reference matches
the last build (cutoff-only updates): just call `uplot.redraw()` and
let the filter-band plugin pick up the new cutoffs via its live
accessors. Keep a smaller redraw effect for the filter-enabled toggle.

Same perf win for cutoff scrubs, without the initial-load regression.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@daharoni daharoni merged commit 7e86cdc into main Apr 22, 2026
6 checks passed
@daharoni daharoni deleted the fix/catune-gt-marker-and-perf-sweep branch April 22, 2026 04:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant