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>
170 lines
5.0 KiB
Markdown
170 lines
5.0 KiB
Markdown
---
|
|
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>
|