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
Summary
Section titled “Summary”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.
Entry Criteria
Section titled “Entry Criteria”| Criterion | Verification |
|---|---|
Run 4 exit gate passed: @arda-cards/design-system published with lifecycle types and updated components | npm 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 committed | Confirmed during Run 5 entry check |
| Full check suite passes after Run 5 | npm run lint && npx tsc --noEmit && npx jest --no-coverage --watchAll=false --forceExit && npm run build in arda-frontend-app worktree |
Task List
Section titled “Task List”| # | Task | Persona | Depends On | Status | Acceptance Criteria |
|---|---|---|---|---|---|
| T-1 | Dependencies and QueryClientProvider setup | front-end-engineer | Entry criteria | Pending | @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-2 | SPA API functions layer (src/api/image-upload.ts) with re-exported types (FD-13) | front-end-engineer | T-1 | Pending | Exports 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-3 | TanStack mutation hooks (src/hooks/image-upload/) | front-end-engineer | T-2 | Pending | useImageUpload() orchestrates getImageUploadUrl → uploadToS3 → 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-4 | CDN cookie query hook (src/hooks/cdn/) with shared TTL constant (REQ-FE-081) | front-end-engineer | T-2 | Pending | useCdnCookies(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-5 | CDN cookie provider (src/providers/cdn-cookie-provider.tsx) | front-end-engineer | T-4 | Pending | React 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-6 | Wiring hooks (src/hooks/image-upload/use-item-image-upload-dialog.ts) | front-end-engineer | T-3 | Pending | useItemImageUploadDialog() 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-7 | ITEM_IMAGE_CONFIG constant (src/constants/item-image-config.ts) | front-end-engineer | T-1 | Pending | ITEM_IMAGE_CONFIG: ImageFieldConfig with aspectRatio: 1:1, acceptedFormats: [jpeg, png, webp, heic, heif], maxSizeBytes: 10 MB, minDimensionPx: 200, entityTypeDisplayName: "Item", propertyDisplayName: "Product Image" |
| T-7b | Shared CDN image recovery hook (src/hooks/cdn/use-image-with-cdn-recovery.ts) (FD-16, REQ-FE-078, REQ-FE-080) | front-end-engineer | T-5 | Pending | useImageWithCdnRecovery(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-8 | Grid integration | front-end-engineer | T-7, T-7b | Pending | Item 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-9 | Form integration | front-end-engineer | T-6, T-7b | Pending | ImageFormField 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-10 | Unit tests (hooks, API functions, wiring, form, grid) | front-end-engineer | T-1 through T-9 | Pending | Hook 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-11 | Documentation commit | front-end-engineer | T-10 | Pending | make pr-checks passes in documentation worktree; changes committed referencing Phase 3.6 |
Key Decisions in Effect
Section titled “Key Decisions in Effect”| Decision | Summary |
|---|---|
| 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 |
Skills to Load
Section titled “Skills to Load”ui-componentunit-tests-frontendtypescript-coding
Exit Criteria
Section titled “Exit Criteria”| Criterion | Verification |
|---|---|
| All tasks complete with passing unit tests | npx jest --no-coverage --watchAll=false --forceExit — all tests green, no regressions |
| Lint passes | npm run lint — no errors |
| TypeScript typecheck passes | npx tsc --noEmit — no errors |
| Full Next.js build passes | npm run build — succeeds |
| Image upload flow works end-to-end in mock mode | npm run dev:mock — upload, reachability check, and CDN cookie refresh observable in browser |
| No regressions in existing tests | All pre-existing tests still green |
Documentation committed with make pr-checks passing | git -C /Users/jmp/code/arda/projects/image-upload-frontend-worktrees/documentation log --oneline -3 |
Handoff
Section titled “Handoff”Artifacts Consumed
Section titled “Artifacts Consumed”| Artifact | Source | Produced By |
|---|---|---|
@arda-cards/design-system with lifecycle types and updated image components | GitHub Packages | Run 4 |
src/server/ BFF routes and utilities | arda-frontend-app worktree | Run 5 |
@arda-cards/api-proxy with createImageUploadUrl() | GitHub Packages | Run 1 |
Artifacts Produced
Section titled “Artifacts Produced”| Artifact | Location | Consumed By |
|---|---|---|
src/providers/query-provider.tsx and src/providers/cdn-cookie-provider.tsx | arda-frontend-app worktree | Run 7 (included in PR) |
src/hooks/image-upload/ (mutation hooks and wiring hook) | arda-frontend-app worktree | Run 7 (included in PR) |
src/hooks/cdn/ (useCdnCookies, useRefreshCdnCookies, useImageWithCdnRecovery) | arda-frontend-app worktree | Run 7 (included in PR) |
src/api/image-upload.ts (SPA API functions) | arda-frontend-app worktree | Run 7 (included in PR) |
src/constants/item-image-config.ts | arda-frontend-app worktree | Run 7 (included in PR) |
Updated item grid and ItemFormPanel with image components wired | arda-frontend-app worktree | Run 7 (included in PR) |
| Documentation commit referencing Phase 3.6 | documentation worktree | Run 7 (documentation PR) |
Agent Prompt Template
Section titled “Agent Prompt Template”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 theimage-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 pathsor `git -C <path>` for git commands.
Report completion with: (a) a summary of all files created/modified, (b) testpass 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
Copyright: © Arda Systems 2025-2026, All rights reserved