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>
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 |
|
true | true |
|
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;
}
-
Add a local crop state to track the most recent crop values:
const [localCrop, setLocalCrop] = useState<{ zoom: number; x: number; y: number } | null>(null); -
In the ImageCropEditor onSave handler (around line 88-91), update localCrop before calling the parent onCropChange:
onSave={(result) => { setLocalCrop(result); onCropChange(result); setShowCropEditor(false); }} -
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} /> -
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> |
<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>