Files
GearBox/.planning/milestones/v2.2-phases/29-image-presentation/29-05-PLAN.md
Jean-Luc Makiola 2853477a75
All checks were successful
CI / ci (push) Successful in 1m15s
CI / e2e (push) Has been skipped
CI / deploy (push) Has been skipped
chore: archive v2.2 User Experience Polish milestone
Phases 28-31 archived to milestones/v2.2-phases/
Requirements and roadmap snapshots archived to milestones/

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 16:00:35 +02:00

5.0 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, gap_closure, requirements, must_haves
phase plan type wave depends_on files_modified autonomous gap_closure requirements must_haves
29-image-presentation 05 execute 1
src/client/components/ImageUpload.tsx
true true
truths artifacts key_links
After cropping in the upload crop editor, the GearImage preview immediately reflects the crop values without needing to save the form
path provides contains
src/client/components/ImageUpload.tsx Local crop state that feeds GearImage preview cropZoom
from to via pattern
ImageCropEditor onSave GearImage cropZoom/cropX/cropY props local state in ImageUpload localCrop
Fix cropped image preview not updating immediately after cropping in edit mode.

Purpose: When a user crops an image via the ImageCropEditor inside ImageUpload, the preview should reflect the crop immediately — not only after form save and query refetch.

Output: ImageUpload component with local crop state that feeds into GearImage preview props.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @src/client/components/ImageUpload.tsx @src/client/components/GearImage.tsx @src/client/components/ImageCropEditor.tsx From src/client/components/GearImage.tsx: ```typescript interface GearImageProps { src: string; alt: string; dominantColor?: string | null; cropZoom?: number | null; cropX?: number | null; cropY?: number | null; className?: string; cover?: boolean; } ```

From src/client/components/ImageCropEditor.tsx:

interface CropResult {
  zoom: number;
  x: number;
  y: number;
}
// onSave: (result: CropResult) => void;

From src/client/components/ImageUpload.tsx:

interface ImageUploadProps {
  value: string | null;
  imageUrl?: string | null;
  dominantColor?: string | null;
  onChange: (filename: string | null, dominantColor?: string | null) => void;
  onCropChange?: (crop: { zoom: number; x: number; y: number }) => void;
}
Task 1: Add local crop state to ImageUpload and wire to GearImage preview src/client/components/ImageUpload.tsx In ImageUpload.tsx, make these changes:
  1. Add a local crop state to track the most recent crop values:

    const [localCrop, setLocalCrop] = useState<{ zoom: number; x: number; y: number } | null>(null);
    
  2. In the ImageCropEditor onSave handler (around line 88-91), update localCrop before calling the parent onCropChange:

    onSave={(result) => {
      setLocalCrop(result);
      onCropChange(result);
      setShowCropEditor(false);
    }}
    
  3. In the GearImage render (around line 110-114), pass localCrop values as props:

    <GearImage
      src={displayUrl}
      alt="Item"
      dominantColor={dominantColor}
      cropZoom={localCrop?.zoom}
      cropX={localCrop?.x}
      cropY={localCrop?.y}
    />
    
  4. When the image is removed (handleRemove), also clear localCrop:

    function handleRemove(e: React.MouseEvent) {
      e.stopPropagation();
      setLocalPreview(null);
      setLocalCrop(null);
      onChange(null);
    }
    

This ensures the GearImage preview immediately reflects crop adjustments without waiting for a server round-trip and query refetch. cd /home/jlmak/Projects/jlmak/GearBox && bunx tsc --noEmit --pretty 2>&1 | head -30 After using the crop editor on an uploaded image, the GearImage preview in ImageUpload immediately shows the cropped framing. Removing the image clears both the preview and crop state.

<threat_model>

Trust Boundaries

No new trust boundaries — this is a client-side-only state management fix within existing components.

STRIDE Threat Register

Threat ID Category Component Disposition Mitigation Plan
T-29-05-01 T (Tampering) localCrop state accept Client-side display only; actual crop values are persisted via existing server mutation in parent component
</threat_model>
1. TypeScript compiles without errors 2. Manual: Open item in edit mode, upload image, crop it, verify preview shows crop immediately (without clicking Save) 3. Manual: Open existing item in edit mode, click crop button, adjust, save framing — preview updates immediately

<success_criteria>

  • Cropped image preview updates in edit state immediately after cropping, without needing to save the form
  • No TypeScript errors
  • Image removal clears crop state </success_criteria>
After completion, create `.planning/phases/29-image-presentation/29-05-SUMMARY.md`