docs(29): create gap closure plan for crop preview state

This commit is contained in:
2026-04-13 13:37:57 +02:00
parent e536f68bd1
commit c98ac6e46f

View File

@@ -0,0 +1,169 @@
---
phase: 29-image-presentation
plan: 05
type: execute
wave: 1
depends_on: []
files_modified:
- src/client/components/ImageUpload.tsx
autonomous: true
gap_closure: true
requirements: []
must_haves:
truths:
- "After cropping in the upload crop editor, the GearImage preview immediately reflects the crop values without needing to save the form"
artifacts:
- path: "src/client/components/ImageUpload.tsx"
provides: "Local crop state that feeds GearImage preview"
contains: "cropZoom"
key_links:
- from: "ImageCropEditor onSave"
to: "GearImage cropZoom/cropX/cropY props"
via: "local state in ImageUpload"
pattern: "localCrop"
---
<objective>
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.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<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
<interfaces>
<!-- GearImage accepts optional crop props -->
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;
}
```
<!-- ImageCropEditor returns CropResult on save -->
From src/client/components/ImageCropEditor.tsx:
```typescript
interface CropResult {
zoom: number;
x: number;
y: number;
}
// onSave: (result: CropResult) => void;
```
<!-- ImageUpload current props -->
From src/client/components/ImageUpload.tsx:
```typescript
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;
}
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Add local crop state to ImageUpload and wire to GearImage preview</name>
<files>src/client/components/ImageUpload.tsx</files>
<action>
In ImageUpload.tsx, make these changes:
1. Add a local crop state to track the most recent crop values:
```typescript
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:
```typescript
onSave={(result) => {
setLocalCrop(result);
onCropChange(result);
setShowCropEditor(false);
}}
```
3. In the GearImage render (around line 110-114), pass localCrop values as props:
```typescript
<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:
```typescript
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.
</action>
<verify>
<automated>cd /home/jlmak/Projects/jlmak/GearBox && bunx tsc --noEmit --pretty 2>&1 | head -30</automated>
</verify>
<done>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.</done>
</task>
</tasks>
<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>
<verification>
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
</verification>
<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>
<output>
After completion, create `.planning/phases/29-image-presentation/29-05-SUMMARY.md`
</output>