Specification: Phase 3.3 — Component Updates
Update the 5 image-related components in ux-prototype for FD-01 compliance
and lifecycle framework adoption. Source:
ux-prototype-architectural-review.md
(moderate-change components, excluding ItemCardEditor per FD-06).
Follow the ui-component skill for component structure (Static/Init/Runtime
config separation, Storybook integration). Follow the unit-tests-frontend
skill for test conventions.
Entry Criteria
Section titled “Entry Criteria”- Phase 3.2 complete: lifecycle types and
useDraft<T>hook available - infrastructure#439
deployed: CloudFront CORS Response Headers Policy for
https://*.arda.cardswithAccess-Control-Allow-Credentials: true. Required by T-1 change #7 (crossOrigin="use-credentials"on canvas image editing). The component code can be implemented and tested in Storybook without this (Storybook uses local blobs), but production edit-existing-image flow will fail without the CORS policy. - All existing checks pass
T-1: ImageUploadDialog — Indeterminate progress + UploadError
Section titled “T-1: ImageUploadDialog — Indeterminate progress + UploadError”Source: REQ-FE-040, REQ-FE-041, REQ-FE-042
Changes:
- Remove the simulated 50ms progress timer (lines ~186-209 in current code)
- Replace
<Progress value={phase.progress}>with an indeterminate indicator (spinner or pulsing bar with no percentage) - Add
UploadErrorphase to the state machine:type DialogPhase =| ... existing phases ...| { name: 'UploadError'; error: string; imageData: File | Blob | string }; - Add
UPLOAD_ERRORaction and transitions:Uploading→UploadError(ononUploadrejection)UploadError→Uploading(retry)UploadError→EmptyImage(discard and start over)
- Render error message and retry/discard buttons in
UploadErrorphase - Adopt
EditLifecycleCallbacks<ImageUploadResult>— the existingonConfirm/onCancelalready match; add type import for documentation - When the dialog opens in
EditExistingstate (editing a CDN-hosted image), theImagePreviewEditormust setcrossOrigin="use-credentials"on the<img>element that loads the CDN image into the crop canvas. Without this, the canvas is tainted by cross-origin content andtoBlob()/getCroppedImage()throws aSecurityError.react-easy-cropsupports this via themediaPropsprop:mediaProps={{ crossOrigin: 'use-credentials' }}- This attribute must only be set when the image source is a CDN URL
(matches
*.assets.arda.cards). For local blobs and object URLs,crossOriginshould be omitted to avoid unnecessary CORS preflights. - Infrastructure prerequisite: CloudFront must serve CORS response
headers (
Access-Control-Allow-Origin,Access-Control-Allow-Credentials: true) via a Response Headers Policy. See infrastructure#439 — this ticket must be deployed before the edit-existing flow works in production.
Tests:
- Uploading phase shows indeterminate indicator (no progress percentage)
- UploadError phase renders error message
- Retry from UploadError transitions back to Uploading
- Discard from UploadError transitions to EmptyImage
- Default
onUploadhandler still works in Storybook EditExistingwith CDN URL setscrossOrigin="use-credentials"on the crop image elementEditExistingwith local blob URL does not setcrossOrigin
T-2: ImageCellEditor — Factory with typed provider hooks
Section titled “T-2: ImageCellEditor — Factory with typed provider hooks”Source: REQ-FE-043
Changes:
- Extend
createImageCellEditorfactory config to accept typed hooks as required fields:interface ImageCellEditorConfig {config: ImageFieldConfig;useImageUpload: () => { mutateAsync: (file: Blob) => Promise<string>; isPending: boolean };useCheckReachability: () => { mutateAsync: (url: string) => Promise<boolean> };} - Pass hooks through to
ImageUploadDialogasonUpload/onCheckReachability - The
ImageCellEditorConfiginterface makesuseImageUploadanduseCheckReachabilityrequired (not optional). In Storybook, story-level config provides stub implementations viacreateImageCellEditor({ ..., useImageUpload: useStubImageUpload, useCheckReachability: useStubCheckReachability }). This shifts the missing-hook error from runtime to compile time — production column definitions that forget to wire hooks failtsc, not silently at runtime.
Tests:
- Factory creates editor component that renders ImageUploadDialog
- Hooks are forwarded to dialog props
- TypeScript compilation fails when hooks are omitted from config
T-3: ImageFormField — EditableComponentProps + contextErrors
Section titled “T-3: ImageFormField — EditableComponentProps + contextErrors”Source: REQ-FE-044
Changes:
- Add
contextErrors?: FieldError[]prop - Adopt
EditableComponentProps<string | null>interface pattern:- Add
initialValueas the canonical prop (perEditableComponentProps<T>) - Keep
imageUrlas a backward-compatible alias, marked with/** @deprecated UseinitialValueinstead. */ - Component reads
initialValue ?? imageUrlinternally - Keep
onChangecallback
- Add
- Display contextual errors alongside the image field
Tests:
- contextErrors rendered when provided
- Existing behavior unchanged when contextErrors omitted
initialValueprop works as primary data sourceimageUrlprop still works (backward compat) but is deprecated
T-4: ItemGridColumns — Expand lookups + image editor wiring
Section titled “T-4: ItemGridColumns — Expand lookups + image editor wiring”Source: REQ-FE-045
Rationale for lookup expansion: The current ItemGridLookups interface
has only 2 fields (supplier, classificationType), but the item grid has
9+ columns with typeahead editors. The 7 missing fields mean those columns
have their typeahead data sources hardwired or not wired through the typed
provider interface at all. Expanding the interface makes all data dependencies
explicit and type-safe (FD-01), and is a prerequisite for Phase 3.6 where the
app wires TanStack-backed lookup hooks into the grid — it needs the interface
to accept them all.
Changes:
- Expand
ItemGridLookupsinterface:export interface ItemGridLookups {supplier?: (search: string) => Promise<TypeaheadOption[]>;classificationType?: (search: string) => Promise<TypeaheadOption[]>;classificationSubType?: (search: string) => Promise<TypeaheadOption[]>;useCase?: (search: string) => Promise<TypeaheadOption[]>;facility?: (search: string) => Promise<TypeaheadOption[]>;department?: (search: string) => Promise<TypeaheadOption[]>;location?: (search: string) => Promise<TypeaheadOption[]>;sublocation?: (search: string) => Promise<TypeaheadOption[]>;unit?: (search: string) => Promise<TypeaheadOption[]>;} - Add image editor hooks to the column factory:
Both fields are required (noexport interface ItemGridEditorHooks {useImageUpload: () => { mutateAsync: (file: Blob) => Promise<string>; isPending: boolean };useCheckReachability: () => { mutateAsync: (url: string) => Promise<boolean> };}
?), consistent with FD-15 and theImageCellEditorConfigchange in T-2. This shifts the missing-hook error from runtime to compile time. - Wire
createImageCellEditorin the image column definition using the provided hooks
Tests:
- Column defs include image cell renderer and editor when hooks provided
- Column defs work without hooks (Storybook/default mode)
T-5: TypeaheadCellEditor — Evaluate hook-style interface
Section titled “T-5: TypeaheadCellEditor — Evaluate hook-style interface”Source: goal.md scope item
Evaluation: The current lookup: (search: string) => Promise<TypeaheadOption[]>
interface is already a typed callback (FD-01 compliant as a promise-based
provider). The component manages its own debounce, loading, and error state
from the promise.
Decision to make: Convert to hook-style useLookup: (query: string) => { data, isLoading }, or keep the promise callback?
Recommendation: Keep the current promise callback. Reasons:
- Already FD-01 compliant (typed, TanStack-agnostic)
- Component correctly manages loading/error from the promise
- Debounce is a UI concern that belongs in the component
- Converting would require the component to re-render on every keystroke via hook state changes, which is the current behavior anyway
If kept as-is, this task is a no-op except for documenting the decision.
T-6: Full checks (ux-prototype)
Section titled “T-6: Full checks (ux-prototype)”make check # lint + typecheckmake test # unit testsmake build # Storybook buildVerify all existing Storybook stories still render correctly with default handlers.
T-7: Documentation checks and commit
Section titled “T-7: Documentation checks and commit”Update session log / byproducts in the documentation worktree.
# In the documentation worktreemake pr-checksCommit documentation changes referencing Phase 3.3.
Exit Criteria
Section titled “Exit Criteria”ImageUploadDialog: indeterminate progress,UploadErrorstate, lifecycle typesImageCellEditor: factory accepts typed provider hooksImageFormField: acceptscontextErrorsItemGridColumns: expanded lookups interface, image editor hook wiringTypeaheadCellEditor: decision documented (keep or convert)- All unit tests pass, including new tests for each change
- All Storybook stories render with default handlers
- Full local checks pass (
ux-prototype) - Documentation worktree:
make pr-checkspasses, changes committed
STOP: Review component changes before proceeding to Phase 3.4 (publish).
Open Questions and Decisions
Section titled “Open Questions and Decisions”| # | Question | Options | Recommendation | Decision |
|---|---|---|---|---|
| 1 | ImageFormField prop naming: imageUrl vs. initialValue | A: rename to initialValue (consistent with lifecycle), B: keep imageUrl + add initialValue alias, C: keep imageUrl only | B (backward compat + new pattern) | Agreed, mark the alias as “Deprecated” |
| 2 | TypeaheadCellEditor interface | A: convert to hook, B: keep promise callback | B (already FD-01 compliant) | Agreed |
Copyright: (c) Arda Systems 2025-2026, All rights reserved
Copyright: © Arda Systems 2025-2026, All rights reserved