docs(29): create gap closure plan for crop preview state
This commit is contained in:
169
.planning/phases/29-image-presentation/29-05-PLAN.md
Normal file
169
.planning/phases/29-image-presentation/29-05-PLAN.md
Normal 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>
|
||||
Reference in New Issue
Block a user