Documentation Update Plan — current-system
Changes and additions to current-system/ documentation required to capture
patterns, architecture, and API surfaces introduced by the image upload feature.
Organized by priority; high-priority items should ship with or shortly after
PR arda-cards/arda-frontend-app#742.
New files
Section titled “New files”High priority
Section titled “High priority”architecture/patterns/user-interface/rich-cell-editors.md
Section titled “architecture/patterns/user-interface/rich-cell-editors.md”What it documents:
AG Grid’s cell-editor lifecycle assumes the editor renders within the cell’s
focus scope. Portal-based modal editors (Radix Dialog, MUI Dialog, any
React portal) violate that assumption — stopEditingWhenCellsLoseFocus
unmounts the editor when the portal steals focus. This file documents the
workaround pattern and its industry validation.
Content outline:
- The impedance mismatch: AG Grid edit lifecycle vs. portalled modals.
cellEditorPopup: truewraps the editor in a 0×0 AG-Grid-owned container (hidden subtree defeats focus traps).- Without
cellEditorPopup,stopEditingWhenCellsLoseFocus: truetears down the editor on portal focus-steal. isPopup()runtime handle suppresses AG Grid’s tooltip feature but does not exempt the editor from focus-loss teardown.
- The pattern: grid becomes trigger, host owns editor lifecycle, commit
flows back through the grid’s public transaction API
(
applyTransaction+ dirty-row tracking + debounced publish). - Trigger mechanisms:
onCellDoubleClicked,onCellKeyDown(Enter, F2), orgridContext.openRichEditor(field, row)for renderer-driven triggers. - Commit integration: image edits flow through the same
dirtyRowIdsRef/publishRowpipeline asonNotesSave/onCardNotesSave— one save per row per edit session regardless of which fields changed. - Industry alignment: MUI X DataGrid, Glide Data Grid, Handsontable, TanStack Table all prescribe the same split. Core principle restated across sources: “if the editor owns focus in a way the grid cannot observe, take it out of the grid’s edit state machine.”
- Forward reference to
ColumnEditSpec(#748) — the typed abstraction that generalizes this pattern across inline / custom-editor / rich column kinds.
Decision references: FD-19 (superseded), FD-20, FD-21.
functional/image-storage/index.md
Section titled “functional/image-storage/index.md”What it documents: The full image storage subsystem: upload flow, CDN delivery, CORS configuration, and signed-cookie authentication. Consolidates the S3 + CloudFront + BFF signing story in one place.
Content outline:
- Architecture overview: SPA → BFF → Backend → S3 presigned POST → SPA direct upload → CDN delivery.
- BFF routes:
POST /api/image-upload— proxy to backend’s/v1/item/image-upload; returns presigned POST URL + form fields + CDN URL.POST /api/storage/check-url— URL reachability check with SSRF protection.POST /api/storage/fetch-url— external image fetch (10 MB limit).POST /api/storage/cdn-cookies— CloudFront signed cookie issuance.GET /api/storage/sign-url?url=<cdn-url>— CloudFront signed URL for a single resource (Approach D / FD-22 fallback for non-*.arda.cardsorigins).
- Authentication: all BFF routes require both
Authorization: Bearer(access token) andX-ID-Token(ID token). The BFF extractscustom:tenantfrom the ID token; sending only the access token causes 401 “JWT token missing required attributes or expired”. Implemented via sharedgetBffAuthHeaders()helper. - Error response format: machine-readable codes (
RATE_LIMITED,VALIDATION_FAILED, etc.), not user-facing text. SPA API layer translates codes to localized messages. - Dual-mode CDN auth (FD-22, Approach D):
- On
*.arda.cardsorigins: cookie-based auth (production path, zero-cost pass-through viaCdnAuthProvider). - On all other origins (Amplify preview, localhost): per-image
signed URL via
GET /api/storage/sign-url. CloudFront natively accepts both signed cookies and signed URLs. useResolvedImageUrl(cdnUrl)hook andResolvedImage/ResolvedImgcomponents resolve URLs transparently. All image render sites in the app use these.
- On
- Two levels of CDN image access (critical nuance):
- Display (
<img src>, grid cells, hover previews, card views): works everywhere with signed URLs. No CORS headers needed. - Canvas-based editing (
react-easy-cropinImagePreviewEditor): requires CORS headers from CloudFront because the<img>element hascrossOrigin="use-credentials"(FD-17). Signed URLs authenticate the request but do not satisfy the browser’s CORS check. The CloudFront Response Headers Policy must include the page’s origin. - Implication: image display works on all origins; edit-existing
crop works on
*.arda.cards(permanent) and*.amplifyapp.com(permanent per infrastructure#442) but requires a temporary CLI change for localhost.
- Display (
- CloudFront CORS (Response Headers Policy):
Access-Control-Allow-Origins:https://*.arda.cards(all envs),https://*.amplifyapp.com(non-prod only).http://localhost:<port>excluded from CDK; applied via CLI for local dev only (knowledge-base documented).
- S3 bucket CORS:
AllowedOrigins:https://*.arda.cards,https://*.amplifyapp.com(non-prod).AllowedMethods:POST.- Required for browser-direct presigned POST uploads.
http://localhost:<port>excluded from CDK; applied via ad-hoc CLI for local dev only (see knowledge-base).
crossOrigin="use-credentials"on crop<img>elements:- Required only when loading CDN images onto an HTML canvas
(
react-easy-crop). Triggers CORS preflight. - Display-only
<img>tags (thumbnails, previews) do NOT setcrossOriginto avoid unnecessary preflights.
- Required only when loading CDN images onto an HTML canvas
(
- CloudFront signing key management:
CLOUDFRONT_KEY_PAIR_IDenv var (public key identifier, not secret).- Private key fetched from AWS Secrets Manager at runtime using
derived name
${Infrastructure}-${Partition}-ImageCdnSigningKey. - 8-hour in-memory cache TTL.
- Sequence diagram (PlantUML):
- Upload: SPA → BFF
/api/image-upload→ BackendPOST /v1/item/image-upload→ STS AssumeRole → S3 presigned POST → response to SPA → SPA direct POST to S3 → BFFPUT /api/arda/items/{id}to persist URL. - Display: SPA → BFF
/api/storage/cdn-cookies→ CloudFront signer → Set-Cookie headers →<img src>to CDN with cookies.
- Upload: SPA → BFF
Decision references: FD-10, FD-11, FD-12, FD-13, FD-17. Infrastructure references: #439 (CloudFront CORS), #441 (STS AssumeRole), #442 (S3 bucket CORS + CloudFront CORS for preview domains).
Medium priority
Section titled “Medium priority”architecture/patterns/user-interface/design-system-integration.md
Section titled “architecture/patterns/user-interface/design-system-integration.md”What it documents:
How @arda-cards/design-system (canary) components are consumed by
arda-frontend-app; the factory-hook injection pattern; Tailwind v4
@source directive coupling.
Content outline:
- Package entry points:
@arda-cards/design-system(stable),/canary(experimental),/extras(supplementary). - Factory hook injection pattern (
ImageCellEditorConfig):useImageUploadanduseCheckReachabilityare required fields (not optional). Storybook provides stubs.- Compile-time safety: missing hooks fail
tsc, not runtimeconsole.warn.
- Tailwind v4
@sourcedirective:arda-frontend-app/src/app/globals.cssmust include@source "../../node_modules/@arda-cards/design-system/dist"for Tailwind to scan design-system classes.- When using
npm linkfor local dev, the@sourcepath must point at the linked package’s source.
- Consumer responsibilities: include
@source, provide factory hooks, wrap in required providers (CdnCookieProvider,QueryClientProvider).
Decision references: FD-01, FD-15.
architecture/patterns/user-interface/cdn-image-recovery.md
Section titled “architecture/patterns/user-interface/cdn-image-recovery.md”What it documents: Client-side CDN 403 recovery mechanism: automatic cookie refresh and cache-busted retry.
Content outline:
CdnCookieProvidercontext: wraps the app, callsPOST /api/storage/cdn-cookieson mount and at 50% TTL.useImageWithCdnRecovery(cdnUrl, options?)hook:- Detects CDN URLs (matches
*.assets.arda.cards). - On
<img>error event: callsrefreshNow()from context, bumpssrcwith cache-buster query param. - Maximum one retry per URL per mount — prevents refresh storms.
- Detects CDN URLs (matches
ImageDisplaystays pure (design-system component, no auth awareness). The recovery hook is wired at the app level.- Failure mode: if cookie refresh itself fails (BFF 500), the image shows the error placeholder (initials + badge). No infinite retry loop.
Decision references: FD-16.
architecture/code-organization.md
Section titled “architecture/code-organization.md”What it documents:
The hybrid layer-with-feature-grouping code organization pattern adopted
for arda-frontend-app.
Content outline:
- Directory structure:
src/server/— BFF routes + server-only utilities. Guarded byimport 'server-only'orimport '@/server'.src/server/lib/— server-only utilities (cloudfront-signer, secrets, ssrf-validator, rate-limiter).src/api/— SPA-side API functions (plainfetchwrappers calling BFF routes). No TanStack, no@arda-cards/api-proxyruntime imports.src/hooks/— feature-grouped React hooks (e.g.,src/hooks/image-upload/,src/hooks/cdn/).src/providers/— React context providers.src/lib/— shared utilities consumed by both client and server (e.g.,auth-headers.ts,shared/aws-naming.ts).
- Dependency direction rules:
src/server/may import fromsrc/lib/andsrc/lib/shared/.src/api/may import fromsrc/lib/(for auth headers) and type-only from@arda-cards/api-proxy.src/hooks/imports fromsrc/api/(not fromsrc/server/).src/components/imports fromsrc/hooks/andsrc/lib/.
- BFF byte-buffering policy (REQ-FE-020): BFF routes must not buffer or store image file bytes. Enforced by code review; future structural lint planned.
Decision references: FD-02, FD-11.
Low priority
Section titled “Low priority”architecture/patterns/user-interface/copyright-acknowledgment.md
Section titled “architecture/patterns/user-interface/copyright-acknowledgment.md”- Inline text in
ImageUploadDialogconfirm footer. - No checkbox gate or blocking acknowledgment step.
- Server-side logging deferred (TD-04).
Decision references: FD-18, SD-07.
Existing files to update
Section titled “Existing files to update”High priority
Section titled “High priority”functional/api-endpoint-catalog.md
Section titled “functional/api-endpoint-catalog.md”Add a new section for image storage / BFF routes:
| Method | Path | Purpose |
|---|---|---|
POST | /api/image-upload | Proxy to backend; returns presigned POST URL + CDN URL |
POST | /api/storage/check-url | URL reachability check with SSRF protection |
POST | /api/storage/fetch-url | External image fetch (10 MB limit, SSRF-guarded) |
POST | /api/storage/cdn-cookies | CloudFront signed cookie issuance (204, Set-Cookie) |
Medium priority
Section titled “Medium priority”functional/reference-data/item/index.md
Section titled “functional/reference-data/item/index.md”Expand “Integrations” section to cover:
- Image upload sequence: SPA → BFF → S3 presigned POST
→ Item Service
PUT /v1/item/{id}to persistimageUrl. - CDN cookie requirement for displaying uploaded images.
- Canvas-editing CORS requirement for edit-existing flow.
- Reference to
functional/image-storage/index.mdfor full details.
architecture/patterns/user-interface/frontend-architecture.md
Section titled “architecture/patterns/user-interface/frontend-architecture.md”Add subsection “State Management Strategy”:
- Redux Toolkit (redux-persist): entity CRUD, auth, UI preferences — the established pattern for domain state.
- TanStack Query: image-specific operations (upload credentials, reachability, CDN cookies) — service-oriented, cache-managed queries with automatic refresh.
- Rationale: Redux pattern is established for item CRUDQ; introducing TanStack for new service-oriented operations avoids refactoring the existing Redux slices while providing better cache semantics for operations with TTLs (CDN cookies) and mutations (uploads).
architecture/user-interaction/edit-lifecycle/ (any appropriate file)
Section titled “architecture/user-interaction/edit-lifecycle/ (any appropriate file)”Add image-upload-specific note:
EditLifecycleCallbacks<ImageUploadResult>specialization.- Scope limited to 6 image-related components (FD-03).
- Future:
useComposedDraft<T>for composed validation across parent and child components.
Implementation notes
Section titled “Implementation notes”- All new files must include frontmatter:
title,tags,domain,maturity,author. - Internal links must include
.mdextension. - Diagrams use PlantUML exclusively (not Mermaid).
- Run
make pr-checks(not justmake test) before pushing. - These documentation updates can ship as a separate PR from the code
changes. They do not block the feature release but should land before
the project moves to
completedstatus.
Copyright: © Arda Systems 2025-2026, All rights reserved