---
status: resolved
trigger: "crop editor opens on upload correctly, but after cropping the cropped image isn't shown in the edit state always — after clicking save it is shown correctly"
created: 2026-04-13T12:30:00Z
updated: 2026-04-13T12:35:00Z
---

## Current Focus

hypothesis: GearImage in ImageUpload receives no crop props after cropping — crop values are sent to server via onCropChange but never stored locally or passed to the preview GearImage
test: trace data flow from ImageCropEditor.onSave through ImageUpload to GearImage rendering
expecting: GearImage in ImageUpload has no cropZoom/cropX/cropY props
next_action: return diagnosis

## Symptoms

expected: After cropping in the crop editor, the image preview in edit mode should immediately reflect the crop
actual: Cropped image not shown in edit state after cropping; shows correctly only after Save
errors: None
reproduction: Upload image to item -> crop editor opens -> adjust crop -> close editor -> preview shows uncropped image -> Save item -> page re-renders with crop applied
started: Since Phase 29 implementation

## Eliminated

(none needed — root cause found on first hypothesis)

## Evidence

- timestamp: 2026-04-13T12:32:00Z
  checked: ImageUpload.tsx lines 83-95 — ImageCropEditor onSave handler
  found: onSave calls onCropChange(result) then setShowCropEditor(false). The crop values are passed up to the parent but NOT stored in any local state within ImageUpload.
  implication: After crop editor closes, ImageUpload has no memory of what crop was applied.

- timestamp: 2026-04-13T12:33:00Z
  checked: ImageUpload.tsx lines 109-114 — GearImage rendering after crop editor closes
  found: GearImage is rendered with only src, alt, and dominantColor props. NO cropZoom, cropX, or cropY props are passed. The component never receives crop values.
  implication: GearImage renders uncropped because it literally has no crop data to apply.

- timestamp: 2026-04-13T12:34:00Z
  checked: $itemId.tsx lines 277-294 — onCropChange callback in item detail page
  found: onCropChange triggers updateItem.mutate() which sends crop values to the server immediately. This is a fire-and-forget mutation — it does NOT update local state or the React Query cache synchronously.
  implication: Crop values reach the server, but the local component tree has no access to them until the query is invalidated/refetched.

- timestamp: 2026-04-13T12:34:30Z
  checked: $itemId.tsx lines 326-335 — GearImage in non-edit view mode
  found: Non-edit view reads cropZoom, cropX, cropY from item (React Query cache data). After Save, the mutation invalidates the query, item refetches with crop values, and GearImage renders correctly.
  implication: Confirms the "works after save" behavior — the query refetch provides the crop data.

## Resolution

root_cause: ImageUpload component does not track crop values locally after the crop editor closes. When the crop editor's onSave fires, the crop values are forwarded to the parent ($itemId.tsx) which sends them to the server via updateItem.mutate(), but no local state is updated. The GearImage rendered inside ImageUpload receives zero crop-related props (cropZoom, cropX, cropY are never passed). So the preview always shows the uncropped/default image. After the user clicks Save on the item form, the React Query cache is invalidated, the item refetches with server-side crop values, and the page re-renders in view mode with the correct crop applied.

fix: (not applied — diagnosis only)
verification: (not applied — diagnosis only)
files_changed: []
