Fix image-editor adjustments breaking when switching between tools #36

Merged
parkerhdavis merged 1 commit from fix-editor-adjustment-ops into main 2026-05-30 03:48:32 -06:00
parkerhdavis commented 2026-05-30 03:40:21 -06:00 (Migrated from github.com)

Problem

When using the image adjustment tools, moving back and forth between the adjustment sliders and other features could make the sliders stop affecting the image as intended. It worked fine at first and degraded over an edit pass — the symptom was about order of operations, not any single broken control.

Root cause

Slider and curve drags coalesce through pushOp's replaceLastOfType, which only replaced an op when it was the trailing element of the pipeline. Once the user touched a different tool (so a different op type became last), returning to an adjustment appended a duplicate op of that type instead of replacing it. The duplicates then compounded in the CSS filter chain (cssFilterFor emits one primitive per op; accumulateWhiteBalance multiplies across all matching ops), so the image stopped tracking the slider while the slider's readout — which reads the last op of its type — still looked correct.

Changes

  • pushOp (editorStore.ts): replace the last op of the matching type wherever it sits in the pipeline, not only when it's the trailing element. This is the fix for the reported bug.
  • CurveEditor remount (EditorTools.tsx): the editor's key flipped on the first drag tick (when the luminance_curve op first lands), remounting mid-gesture and dropping pointer capture, which froze the curve after one move. It now keys on a nonce that changes only when the curve's presence changes for a reason other than starting a drag (Reset, undo/redo).
  • refreshPreview generation guard (editorStore.ts): added a monotonic token (+ sourcePath check) so an in-flight backend render discards its result if a newer edit or an asset switch/reset superseded it. The backend-render path is currently unreachable (all ops are client-previewable), so this removes a latent stale-preview trap rather than fixing an active bug.

Testing

make typecheck, make lint, and make test all pass (127 Rust tests, 13 frontend tests). No automated coverage exists for the editor store/UI, so the behavioral fix should be confirmed with a manual pass: drag brightness → apply a crop → drag brightness again and confirm the image matches the slider; then bend the luminance curve from flat and confirm it tracks through a continuous drag.

## Problem When using the image adjustment tools, moving back and forth between the adjustment sliders and other features could make the sliders stop affecting the image as intended. It worked fine at first and degraded over an edit pass — the symptom was about order of operations, not any single broken control. ## Root cause Slider and curve drags coalesce through `pushOp`'s `replaceLastOfType`, which only replaced an op when it was the **trailing** element of the pipeline. Once the user touched a different tool (so a different op type became last), returning to an adjustment **appended a duplicate** op of that type instead of replacing it. The duplicates then compounded in the CSS filter chain (`cssFilterFor` emits one primitive per op; `accumulateWhiteBalance` multiplies across all matching ops), so the image stopped tracking the slider while the slider's readout — which reads the *last* op of its type — still looked correct. ## Changes - **`pushOp` (`editorStore.ts`):** replace the last op of the matching type wherever it sits in the pipeline, not only when it's the trailing element. This is the fix for the reported bug. - **CurveEditor remount (`EditorTools.tsx`):** the editor's `key` flipped on the first drag tick (when the `luminance_curve` op first lands), remounting mid-gesture and dropping pointer capture, which froze the curve after one move. It now keys on a nonce that changes only when the curve's presence changes for a reason *other* than starting a drag (Reset, undo/redo). - **`refreshPreview` generation guard (`editorStore.ts`):** added a monotonic token (+ sourcePath check) so an in-flight backend render discards its result if a newer edit or an asset switch/reset superseded it. The backend-render path is currently unreachable (all ops are client-previewable), so this removes a latent stale-preview trap rather than fixing an active bug. ## Testing `make typecheck`, `make lint`, and `make test` all pass (127 Rust tests, 13 frontend tests). No automated coverage exists for the editor store/UI, so the behavioral fix should be confirmed with a manual pass: drag brightness → apply a crop → drag brightness again and confirm the image matches the slider; then bend the luminance curve from flat and confirm it tracks through a continuous drag.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
parker/garnet!36
No description provided.