Skip to content

SPA Specification

Sub-system specification for the SPA (Single-Page Application) — the React browser application in arda-frontend-app. The SPA is the primary interaction point for users and is responsible for input detection, image editing, direct upload to Storage, and CDN-based rendering. For the structural overview of all sub-systems and their dependencies, see design.md.

The SPA’s component architecture, props, composition hierarchy, and design tokens are defined in the UX Components specification (19 components across 6 tiers). Implementation deviations from the original spec are recorded in the post-implementation specification. This document focuses on system-level concerns: requirements traceability to scenarios, interfaces with other actors (BFF, Storage), state that spans actor boundaries, and module identification for implementation planning. It does not repeat component-internal details (props, design tokens, composition hierarchy) — see the UX spec for those.

IDRequirementScenariosSource
SPA-FR-001Detect input type automatically (file, drag-drop, clipboard blob, clipboard HTML, clipboard URL text, data URI, camera) and route through appropriate processing path.S1, S2FR-001, FR-002
SPA-FR-001aFor clipboard raw image blob, generate a local preview and route the binary content through the managed upload path on confirm.S1FR-006
SPA-FR-002For clipboard paste with HTML content, extract embedded image URL and route through URL validation + fetch-and-store path.S2FR-005, TD-01
SPA-FR-002aFor data: or blob: URI text input, decode the content as image data and route through the managed upload path. These URI schemes are never persisted. If decoding fails, show the unrecognized-text error.S1FR-007
SPA-FR-003Validate image format (JPEG, PNG, WebP, HEIC, HEIF) and enforce max file size. Attempt auto-compression before rejecting oversized files.S1, S2FR-008, FR-009
SPA-FR-003aEnforce minimum image dimension of 200x200 pixels. Reject with plain-language error if below threshold.S1, S2FR-040
SPA-FR-004Validate URL input is HTTPS-only. Reject all other schemes immediately.S2FR-010
SPA-FR-005Attempt direct reachability check (HEAD) on external URLs. On CORS failure, delegate to BFF reachability endpoint.S2FR-003, FR-011, TD-02
SPA-FR-006Fetch external images SPA-side (direct or via BFF CORS fallback), load into editor canvas.S2FR-004, TD-01
SPA-FR-007Render image preview at entity-type aspect ratio (1:1 for Items). Provide crop, zoom, rotate, pan, reset controls. Preview is shown before confirm regardless of input method.S1, S2FR-014, FR-015, FR-017
SPA-FR-008Display comparison layout when replacing: side-by-side on desktop, tabbed on mobile.S1, S2, S7FR-016
SPA-FR-009Display copyright acknowledgment text inline in the confirm dialog footer. Confirm is always enabled; no blocking checkbox.S1, S2FR-018, FR-019, TD-04
SPA-FR-010Request presigned POST credentials from BFF.S1, S2, S5, S6, S7FR-020
SPA-FR-011Upload image bytes directly to Storage using presigned POST form.S1, S2, S5, S6, S7FR-021, TD-06
SPA-FR-012Use the cdnUrl from the Backend’s presigned credential response and persist on entity via BFF/Backend API.S1, S2, S5, S6, S7FR-022
SPA-FR-013Display confirmation dialog before removing an image. Send null imageUrl on confirm.S3, S7FR-025
SPA-FR-014Render image thumbnails in grid from CDN. Handle loading (shimmer), error (placeholder + badge), and empty (placeholder) states.S4FR-028, FR-030, FR-031
SPA-FR-015Show ImageHoverPreview popover on hover (~500ms). Popover shows larger image preview, no action icons. Dismiss on mouse-out. Not shown for error-state thumbnails.S4FR-029, CD-08 (revised)
SPA-FR-016Open modal editor on double-click / Enter in grid image cell (AG Grid inline edit). On confirm, persist change and refresh grid row thumbnail. On discard, no change.S5FR-032, FR-033
SPA-FR-017Display all validation errors as plain-language messages with corrective suggestions.AllFR-013
SPA-FR-018Request CloudFront signed cookies from BFF at session start, proactively before expiry (~50% of TTL), and immediately on tenant switch.S4FR-036, FR-037, FR-038, TD-11, TD-12
SPA-FR-019On 403 from a CDN image request, refresh cookies via BFF and retry the request (max 1 retry per image request). If the retry also returns 403, show the error-state placeholder with error badge. Show shimmer during the refresh window.S4FR-038, TD-12
SPA-FR-020Only one image upload may be active per browser session. While an upload is in the Uploading state, the SPA shall disable other image upload triggers (form fields, grid cell editors).S1, S2, S5, S6, S7FR-041
SPA-FR-021The image editor shall always produce JPEG output (regardless of input format). HEIC/HEIF inputs are converted to JPEG during editing. The contentType sent to the presigned credential request shall be image/jpeg.S1, S2FR-042, FR-008
SPA-FR-022The Add Item form shall include an optional image field using the unified image input surface. Items saved without an image display a default placeholder.S6FR-034
SPA-FR-023The Edit Item form shall allow changing or removing the item’s image. Changing follows the set-image flow; removing follows the remove flow.S7FR-035
IDRequirementSource
SPA-NFR-001Image thumbnails shall not cause layout reflow when loading asynchronously.NFR-004
SPA-NFR-002The SPA shall support retry on upload failure without re-entering the image. Re-request credentials if expired.NFR-010
SPA-NFR-003HEIC/HEIF detection shall gracefully degrade if the browser does not support the format. Show a format-specific error message.UA-001
SPA-NFR-004Image uploads via presigned POST shall complete within 10 seconds for files up to 10 MB on a typical broadband connection (10 Mbps upload).NFR-001
SPA-NFR-005The image upload capability shall be generic, parameterized by ImageFieldConfig, so that future entity types can adopt it without re-implementing the upload workflow.NFR-015
SPA-NFR-006The SPA shall display an upload progress indicator (percentage or indeterminate) during the Uploading state.NFR-021

EndpointMethodPurposeScenario
/api/image-uploadPOSTRequest presigned POST credentials (TD-13)S1, S2, S5, S6, S7
/api/storage/check-urlPOSTURL reachability check (CORS fallback)S2
/api/storage/fetch-urlPOSTFetch image via BFF (CORS fallback)S2
/api/storage/cdn-cookiesPOSTRequest/refresh CloudFront signed cookiesS4
/api/items/<itemEId>PUTPersist entity with imageUrlS1, S2, S3, S5, S6, S7
TargetMethodPurposeScenario
Presigned POST URLPOSTUpload image bytesS1, S2, S5, S6, S7
CDN URLGETRender thumbnails and full-size imagesS4, all display contexts
EventHandlerScenario
File pick / drag-drop / clipboard pasteImageDropZone input detectionS1
URL text entryImageDropZone URL detectionS2
Crop/zoom/rotate/pan/resetImagePreviewEditorS1, S2
Copyright acknowledgmentCopyrightAcknowledgment inline text (dialog footer)S1, S2
Confirm / DiscardImageUploadDialog state machineS1, S2, S5, S6, S7
Remove imageImageFormField remove control / alert-dialogS3, S7
Hover on image (~500ms)ImageHoverPreview popover (larger preview, no action icons)S4
Double-click / Enter on image cellImageCellEditorImageUploadDialogS5

The ImageUploadDialog organism manages the multi-step upload interaction. The UX-level states (EmptyImage, ProvidedImage, EditExisting, FailedValidation, Warn) are defined in the UX Components specification. The EditExisting state was added during implementation (see D-04) to support editing images that already have a URL.

The system design adds two system-level states that extend the UX state machine with backend interaction:

StateDescriptionTransitions
UploadingPresigned POST credentials requested from BFF, upload to Storage in progress. Progress indicator shown.View (success, entity updated via BFF/Backend) / → UploadError (failure)
UploadErrorUpload or entity persist failed. Error message shown.Uploading (retry — re-request credentials if expired) / → ProvidedImage (back to editor)

These states are invisible to the UX specification (which ends at “confirm”), but are required at the system level because the upload involves asynchronous calls to BFF, Storage, and Backend (S1 Steps 4–6).

In entity forms (Add Item, Edit Item), the image URL is held in React form state alongside other fields. It is not persisted independently — it is submitted as part of the full entity payload on Publish or Save as Draft.

In grid contexts, image state is managed by AG Grid’s cell renderer/editor lifecycle:

  • ImageCellDisplay: stateless renderer reading imageUrl from row data.
  • ImageCellEditor: opens ImageUploadDialog in modal mode. On confirm, calls AG Grid’s stopEditing() with the new value.

The full component inventory (19 components), their props, composition hierarchy, design tokens, and classification are defined in the UX Components specification. The post-implementation specification records deviations from the original spec (D-01 through D-05).

This section maps those components to system design scenarios and identifies the system-level integration points — where components interact with other actors (BFF, Storage) rather than performing purely client-side work.

ComponentScenariosSystem-Level Integration
ImageUploadDialogS1, S2, S5, S6, S7Calls BFF /api/image-upload (presigned POST credentials). Uploads to Storage via presigned POST. Calls BFF entity update API on confirm. Manages Uploading/UploadError states.
ImageDropZoneS1, S2URL text entry triggers SPA-side reachability check and BFF fallback (/api/storage/check-url, /api/storage/fetch-url).
ImagePreviewEditorS1, S2Output (crop parameters) consumed by getCroppedImage to produce the Blob uploaded to Storage.
ImageComparisonLayoutS1, S2, S7No direct system integration — pure UI.
ImageDisplayS4, all displayRenders <img> with CDN URLs. CloudFront signed cookies sent automatically by browser. Handles 403 (triggers cookie refresh via SPA-FR-019).
ImageCellDisplayS4, S5Stateless renderer — reads imageUrl from AG Grid row data.
ImageCellEditorS5Opens ImageUploadDialog in modal mode. On confirm, calls stopEditing() with new CDN URL.
ImageFormFieldS6, S7Triggers ImageUploadDialog for edit and alert-dialog for remove. Remove sends null imageUrl via BFF.
ImageInspectorOverlayS4, S5No direct system integration — renders CDN image at full size.
ImageHoverPreviewS4, S5, S6, S7No direct system integration — popover preview of CDN image.
CopyrightAcknowledgmentS1, S2Displays acknowledgment text inline in the confirm dialog footer. Confirm is always enabled. SPA-only, no backend logging (TD-04).
getCroppedImageS1, S2Produces the final Blob from crop/zoom/rotate parameters. Input to presigned POST upload.

The components use two type interfaces for backend connectivity, defined in src/types/canary/utilities/image-upload-handlers.ts (see post-implementation specification):

TypePurposeWiring
ImageUploadHandlerCallback for the presigned POST upload flow. Called by ImageUploadDialog on confirm. Production implementation calls BFF /api/image-upload then uploads to Storage.Passed as prop to ImageUploadDialog and ImageFormField. Storybook stories use mockUpload.
ImageReachabilityCheckCallback for URL reachability. Called by ImageDropZone on URL input. Production implementation calls BFF /api/storage/check-url.Passed as prop through ImageUploadDialog. Storybook stories use mockReachabilityCheck.

All generic image components are parameterized by ImageFieldConfig (defined in src/types/canary/utilities/image-field-config.ts). Entity-specific constants (e.g., ITEM_IMAGE_CONFIG) supply the values at render time.

The system design requirements map to ImageFieldConfig fields:

Config FieldValue (Items)Requirement
aspectRatio1 (1:1 square)FR-014, SD-06
acceptedFormats['image/jpeg', 'image/png', 'image/webp', 'image/heic', 'image/heif']FR-008
maxFileSizeBytes10 * 1024 * 1024 (10 MB)FR-009
maxDimension2048 (pixels, longest edge for compression)FR-009
minDimension200 (pixels, shortest edge)FR-040
entityTypeDisplayName"Item"FR-013 (error messages)
propertyDisplayName"Product Image"FR-013 (error messages)
ModuleChangeScenarios
item-grid-columnsImageCellDisplay replaces inline renderer. ImageCellEditor added for inline edit.S4, S5
add-item-formImageFormField added as optional field using ITEM_IMAGE_CONFIG.S6
edit-item-formImageFormField added for change/remove image alongside other entity fields.S7
LibraryPurposeUsed By
react-easy-cropCrop/zoom/pan UI with locked aspect ratioImagePreviewEditor
react-dropzoneDrag-and-drop and file selection handlingImageDropZone
browser-image-compressionAuto-compression (85% JPEG, max 2048px)ImageUploadDialog
heic2anyHEIC/HEIF conversion (lazy-loaded)ImageUploadDialog

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