Skip to content

Run 6: SPA Integration — Project Plan

Author: Technical Writer / Team Lead Date: 2026-04-07 Status: Planning Phase: 3.6 — SPA Integration Branch: jmpicnic/image-upload-frontend

Wire the design system image components to the production backend through TanStack Query hooks. Implements the FD-01 typed provider pattern for upload, reachability check, and CDN cookie management. Adds useImageWithCdnRecovery for 403 recovery across all image display contexts. Integrates image components into the item grid and form panel. All new code follows the FD-02 hybrid directory structure.

CriterionVerification
Run 4 exit gate passed: @arda-cards/design-system published with lifecycle types and updated componentsnpm view @arda-cards/design-system versions --registry=https://npm.pkg.github.com shows the version from Run 4
Run 5 exit gate passed: BFF routes implemented and tested in src/server/git -C /Users/jmp/code/arda/projects/image-upload-frontend-worktrees/arda-frontend-app log --oneline -10 — Run 5 commits present
@arda-cards/api-proxy published (Run 1)npm view @arda-cards/api-proxy versions --registry=https://npm.pkg.github.com shows a published version
Phase 3.0 legacy cleanup committedConfirmed during Run 5 entry check
Full check suite passes after Run 5npm run lint && npx tsc --noEmit && npx jest --no-coverage --watchAll=false --forceExit && npm run build in arda-frontend-app worktree
#TaskPersonaDepends OnStatusAcceptance Criteria
T-1Dependencies and QueryClientProvider setupfront-end-engineerEntry criteriaPending@tanstack/react-query and @tanstack/react-query-devtools installed; @arda-cards/design-system bumped to Run 4 version; @arda-cards/api-proxy bumped to Run 1 version; src/providers/query-provider.tsx created with staleTime: 5min, retry: 1, refetchOnWindowFocus: false; QueryClientProvider added to app provider stack in layout.tsx alongside ReduxProvider and AuthProvider; full checks pass before proceeding
T-2SPA API functions layer (src/api/image-upload.ts) with re-exported types (FD-13)front-end-engineerT-1PendingExports getImageUploadUrl, uploadToS3, checkReachability, fetchExternalImage, refreshCdnCookies; no TanStack imports; no direct api-proxy imports in SPA code; rate limit machine-readable codes translated to user-facing messages; unit tests for all functions with mocked fetch
T-3TanStack mutation hooks (src/hooks/image-upload/)front-end-engineerT-2PendinguseImageUpload() orchestrates getImageUploadUrluploadToS3 → returns cdnUrl; always JPEG contentType; invalidates ['items'] on success; single upload enforcement (disabled while another pending); useCheckReachability() and useFetchExternalImage() mutations; imageKeys query key factory; unit tests with QueryClientProvider wrapper
T-4CDN cookie query hook (src/hooks/cdn/) with shared TTL constant (REQ-FE-081)front-end-engineerT-2PendinguseCdnCookies(enabled?) with refetchInterval from resolveAwsNaming().cookieRefreshIntervalMs (~15 min); refetchIntervalInBackground: true; staleTime = cookieRefreshIntervalMs * 0.67; retry: 2; useRefreshCdnCookies() invalidates ['image', 'cdn-cookies']; unit tests verify refetch interval configured and imperative refresh triggers invalidation
T-5CDN cookie provider (src/providers/cdn-cookie-provider.tsx)front-end-engineerT-4PendingReact Context providing { isReady: boolean, refreshNow: () => void }; uses useCdnCookies() and useRefreshCdnCookies() internally; added to provider stack at app level; unit test: context values accessible by children
T-6Wiring hooks (src/hooks/image-upload/use-item-image-upload-dialog.ts)front-end-engineerT-3PendinguseItemImageUploadDialog() returns { handleUpload, handleCheckReachability, isUploading, uploadError, resetUpload } matching ImageUploadDialog prop signatures; handleUpload: (blob: Blob) => Promise<string> wraps mutateAsync(); handleCheckReachability: (url: string) => Promise<boolean> wraps mutateAsync()
T-7ITEM_IMAGE_CONFIG constant (src/constants/item-image-config.ts)front-end-engineerT-1PendingITEM_IMAGE_CONFIG: ImageFieldConfig with aspectRatio: 1:1, acceptedFormats: [jpeg, png, webp, heic, heif], maxSizeBytes: 10 MB, minDimensionPx: 200, entityTypeDisplayName: "Item", propertyDisplayName: "Product Image"
T-7bShared CDN image recovery hook (src/hooks/cdn/use-image-with-cdn-recovery.ts) (FD-16, REQ-FE-078, REQ-FE-080)front-end-engineerT-5PendinguseImageWithCdnRecovery(cdnUrl, options?) returns { src, isRecovering, handleError }; CDN host derived from NEXT_PUBLIC_INFRASTRUCTURE / NEXT_PUBLIC_PARTITION env vars; 403 on CDN URL calls refreshNow() and cache-busts src with ?_r=<timestamp>; non-CDN errors ignored; max 1 retry per URL per mount (configurable); retry count resets on cdnUrl change; unit tests for all 5 behaviors
T-8Grid integrationfront-end-engineerT-7, T-7bPendingItem grid image column uses ImageCellDisplay as cellRenderer with ITEM_IMAGE_CONFIG; createImageCellEditor factory wired with useImageUpload and useCheckReachability; ImageHoverPreview on hover (~500 ms delay); useImageWithCdnRecovery wired into grid image cell renderer with handleError passed as onError prop
T-9Form integrationfront-end-engineerT-6, T-7bPendingImageFormField replaces disabled dropzone placeholder in ItemFormPanel; wired to useItemImageUploadDialog; change and remove supported; remove sends imageUrl: null via Redux updateItem() thunk; onConfirm sets form.imageUrl = result.imageUrl; useImageWithCdnRecovery wired into ImageFormField image display with src and handleError passed through
T-10Unit tests (hooks, API functions, wiring, form, grid)front-end-engineerT-1 through T-9PendingHook tests with QueryClientProvider wrapper in test-utils; API function tests with mocked fetch; wiring hook tests; form integration tests (component render with mocked hooks); grid integration tests (column definition verification); all tests pass with no regressions
T-11Documentation commitfront-end-engineerT-10Pendingmake pr-checks passes in documentation worktree; changes committed referencing Phase 3.6
DecisionSummary
FD-01 (Typed providers)Design system components stay pure; app provides hooks matching component callback signatures
FD-02 (Hybrid code organization)TanStack hooks in src/hooks/; SPA API functions in src/api/; providers in src/providers/
FD-05 (Redux persist for entity)Entity persist after upload uses existing Redux updateItem() thunk; TanStack does not replace Redux
FD-13 (Type re-exports from api-proxy)Request/response types for SPA API layer re-exported from @arda-cards/api-proxy; no new runtime dependency
FD-14 (isEqual for hook memoization)Use deep equality where needed to prevent unnecessary re-renders
FD-15 (Required hooks)Hooks that must be present are required (not optional) in the provider stack
FD-16 (Shared CDN recovery)useImageWithCdnRecovery used in all image display contexts (grid, form, inspector) for consistent 403 handling
FD-17 (crossOrigin for CDN)All CDN image elements use crossOrigin="anonymous" to support canvas operations
  • ui-component
  • unit-tests-frontend
  • typescript-coding
CriterionVerification
All tasks complete with passing unit testsnpx jest --no-coverage --watchAll=false --forceExit — all tests green, no regressions
Lint passesnpm run lint — no errors
TypeScript typecheck passesnpx tsc --noEmit — no errors
Full Next.js build passesnpm run build — succeeds
Image upload flow works end-to-end in mock modenpm run dev:mock — upload, reachability check, and CDN cookie refresh observable in browser
No regressions in existing testsAll pre-existing tests still green
Documentation committed with make pr-checks passinggit -C /Users/jmp/code/arda/projects/image-upload-frontend-worktrees/documentation log --oneline -3
ArtifactSourceProduced By
@arda-cards/design-system with lifecycle types and updated image componentsGitHub PackagesRun 4
src/server/ BFF routes and utilitiesarda-frontend-app worktreeRun 5
@arda-cards/api-proxy with createImageUploadUrl()GitHub PackagesRun 1
ArtifactLocationConsumed By
src/providers/query-provider.tsx and src/providers/cdn-cookie-provider.tsxarda-frontend-app worktreeRun 7 (included in PR)
src/hooks/image-upload/ (mutation hooks and wiring hook)arda-frontend-app worktreeRun 7 (included in PR)
src/hooks/cdn/ (useCdnCookies, useRefreshCdnCookies, useImageWithCdnRecovery)arda-frontend-app worktreeRun 7 (included in PR)
src/api/image-upload.ts (SPA API functions)arda-frontend-app worktreeRun 7 (included in PR)
src/constants/item-image-config.tsarda-frontend-app worktreeRun 7 (included in PR)
Updated item grid and ItemFormPanel with image components wiredarda-frontend-app worktreeRun 7 (included in PR)
Documentation commit referencing Phase 3.6documentation worktreeRun 7 (documentation PR)

Use the following prompt to spawn the front-end-engineer agent for this run. Paste the full text as the Task tool input.

You are a front-end-engineer working in the arda-frontend-app worktree for the
image-upload-frontend project.
**Primary worktree**: /Users/jmp/code/arda/projects/image-upload-frontend-worktrees/arda-frontend-app
**Branch**: jmpicnic/image-upload-frontend
**Documentation worktree**: /Users/jmp/code/arda/projects/image-upload-frontend-worktrees/documentation
Load the following skills before starting:
- ui-component
- unit-tests-frontend
- typescript-coding
- path-conventions
- document-writing
Read the specification before beginning:
/Users/jmp/code/arda/projects/image-upload-frontend-worktrees/documentation/src/content/docs/roadmap/completed/item-image-upload/3-frontend-implementation/3.6-spa-integration/specification.md
Your task is to execute Run 6 of the image-upload-frontend project (Phase 3.6 —
SPA Integration). Complete the following tasks in order:
T-1: Install `@tanstack/react-query` and `@tanstack/react-query-devtools`. Bump
`@arda-cards/design-system` and `@arda-cards/api-proxy` to latest published
versions. Create `src/providers/query-provider.tsx` (staleTime 5min, retry 1,
refetchOnWindowFocus false). Add `QueryClientProvider` to `layout.tsx`
provider stack. Run full checks before proceeding.
T-2: Create `src/api/image-upload.ts` with the 5 SPA API functions
(getImageUploadUrl, uploadToS3, checkReachability, fetchExternalImage,
refreshCdnCookies). No TanStack imports. No direct api-proxy imports in
SPA code. Translate RATE_LIMITED codes to user-facing messages.
Write unit tests with mocked fetch.
T-3: Create `src/hooks/image-upload/` with `useImageUpload`, `useCheckReachability`,
`useFetchExternalImage` mutation hooks and `imageKeys` factory.
`useImageUpload` orchestrates get-url → upload → return cdnUrl; always
JPEG; single upload enforcement. Write unit tests with QueryClientProvider.
T-4: Create `src/hooks/cdn/use-cdn-cookies.ts` with `useCdnCookies(enabled?)`
and `useRefreshCdnCookies()`. refetchInterval from
`resolveAwsNaming().cookieRefreshIntervalMs`. Write unit tests.
T-5: Create `src/providers/cdn-cookie-provider.tsx` with CdnCookieContext
providing `{ isReady, refreshNow }`. Add to provider stack in layout.tsx.
Write unit test.
T-6: Create `src/hooks/image-upload/use-item-image-upload-dialog.ts` returning
`{ handleUpload, handleCheckReachability, isUploading, uploadError, resetUpload }`.
T-7: Create `src/constants/item-image-config.ts` with `ITEM_IMAGE_CONFIG`.
T-7b: Create `src/hooks/cdn/use-image-with-cdn-recovery.ts`. CDN host derived
from NEXT_PUBLIC_INFRASTRUCTURE and NEXT_PUBLIC_PARTITION. 403 on CDN URL
triggers `refreshNow()` and cache-busts src with `?_r=<timestamp>`. Max 1
retry per URL per mount. Write unit tests for all 5 behaviors.
T-8: Update item grid column definitions in `src/components/items/`. Wire
`ImageCellDisplay`, `createImageCellEditor`, and `ImageHoverPreview`.
Pass `useImageWithCdnRecovery().handleError` as `onError` prop.
T-9: Update `src/components/items/ItemFormPanel.tsx`. Replace disabled dropzone
with `ImageFormField` wired to `useItemImageUploadDialog`. Support change
and remove (remove sends `imageUrl: null`). Wire `useImageWithCdnRecovery`
into `ImageFormField` image display.
T-10: Verify all unit tests pass (no regressions). Run the full check suite:
npm run lint
npx tsc --noEmit
npx jest --no-coverage --watchAll=false --forceExit
npm run build
Verify image upload flow in mock mode: npm run dev:mock
T-11: In the documentation worktree, run `make pr-checks`. Commit changes
referencing Phase 3.6.
All tool calls must use absolute paths. Never use `cd` — use absolute paths
or `git -C <path>` for git commands.
Report completion with: (a) a summary of all files created/modified, (b) test
pass counts and any pre-existing tests verified, (c) confirmation that
`npm run build`, mock mode, and `make pr-checks` all passed.

Copyright: (c) Arda Systems 2025-2026, All rights reserved