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.
Actor Requirements
Section titled “Actor Requirements”Functional
Section titled “Functional”| ID | Requirement | Scenarios | Source |
|---|---|---|---|
| SPA-FR-001 | Detect input type automatically (file, drag-drop, clipboard blob, clipboard HTML, clipboard URL text, data URI, camera) and route through appropriate processing path. | S1, S2 | FR-001, FR-002 |
| SPA-FR-001a | For clipboard raw image blob, generate a local preview and route the binary content through the managed upload path on confirm. | S1 | FR-006 |
| SPA-FR-002 | For clipboard paste with HTML content, extract embedded image URL and route through URL validation + fetch-and-store path. | S2 | FR-005, TD-01 |
| SPA-FR-002a | For 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. | S1 | FR-007 |
| SPA-FR-003 | Validate image format (JPEG, PNG, WebP, HEIC, HEIF) and enforce max file size. Attempt auto-compression before rejecting oversized files. | S1, S2 | FR-008, FR-009 |
| SPA-FR-003a | Enforce minimum image dimension of 200x200 pixels. Reject with plain-language error if below threshold. | S1, S2 | FR-040 |
| SPA-FR-004 | Validate URL input is HTTPS-only. Reject all other schemes immediately. | S2 | FR-010 |
| SPA-FR-005 | Attempt direct reachability check (HEAD) on external URLs. On CORS failure, delegate to BFF reachability endpoint. | S2 | FR-003, FR-011, TD-02 |
| SPA-FR-006 | Fetch external images SPA-side (direct or via BFF CORS fallback), load into editor canvas. | S2 | FR-004, TD-01 |
| SPA-FR-007 | Render 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, S2 | FR-014, FR-015, FR-017 |
| SPA-FR-008 | Display comparison layout when replacing: side-by-side on desktop, tabbed on mobile. | S1, S2, S7 | FR-016 |
| SPA-FR-009 | Display copyright acknowledgment text inline in the confirm dialog footer. Confirm is always enabled; no blocking checkbox. | S1, S2 | FR-018, FR-019, TD-04 |
| SPA-FR-010 | Request presigned POST credentials from BFF. | S1, S2, S5, S6, S7 | FR-020 |
| SPA-FR-011 | Upload image bytes directly to Storage using presigned POST form. | S1, S2, S5, S6, S7 | FR-021, TD-06 |
| SPA-FR-012 | Use the cdnUrl from the Backend’s presigned credential response and persist on entity via BFF/Backend API. | S1, S2, S5, S6, S7 | FR-022 |
| SPA-FR-013 | Display confirmation dialog before removing an image. Send null imageUrl on confirm. | S3, S7 | FR-025 |
| SPA-FR-014 | Render image thumbnails in grid from CDN. Handle loading (shimmer), error (placeholder + badge), and empty (placeholder) states. | S4 | FR-028, FR-030, FR-031 |
| SPA-FR-015 | Show ImageHoverPreview popover on hover (~500ms). Popover shows larger image preview, no action icons. Dismiss on mouse-out. Not shown for error-state thumbnails. | S4 | FR-029, CD-08 (revised) |
| SPA-FR-016 | Open 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. | S5 | FR-032, FR-033 |
| SPA-FR-017 | Display all validation errors as plain-language messages with corrective suggestions. | All | FR-013 |
| SPA-FR-018 | Request CloudFront signed cookies from BFF at session start, proactively before expiry (~50% of TTL), and immediately on tenant switch. | S4 | FR-036, FR-037, FR-038, TD-11, TD-12 |
| SPA-FR-019 | On 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. | S4 | FR-038, TD-12 |
| SPA-FR-020 | Only 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, S7 | FR-041 |
| SPA-FR-021 | The 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, S2 | FR-042, FR-008 |
| SPA-FR-022 | The Add Item form shall include an optional image field using the unified image input surface. Items saved without an image display a default placeholder. | S6 | FR-034 |
| SPA-FR-023 | The Edit Item form shall allow changing or removing the item’s image. Changing follows the set-image flow; removing follows the remove flow. | S7 | FR-035 |
Non-Functional
Section titled “Non-Functional”| ID | Requirement | Source |
|---|---|---|
| SPA-NFR-001 | Image thumbnails shall not cause layout reflow when loading asynchronously. | NFR-004 |
| SPA-NFR-002 | The SPA shall support retry on upload failure without re-entering the image. Re-request credentials if expired. | NFR-010 |
| SPA-NFR-003 | HEIC/HEIF detection shall gracefully degrade if the browser does not support the format. Show a format-specific error message. | UA-001 |
| SPA-NFR-004 | Image 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-005 | The 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-006 | The SPA shall display an upload progress indicator (percentage or indeterminate) during the Uploading state. | NFR-021 |
Interfaces
Section titled “Interfaces”Outbound: BFF API Calls
Section titled “Outbound: BFF API Calls”| Endpoint | Method | Purpose | Scenario |
|---|---|---|---|
/api/image-upload | POST | Request presigned POST credentials (TD-13) | S1, S2, S5, S6, S7 |
/api/storage/check-url | POST | URL reachability check (CORS fallback) | S2 |
/api/storage/fetch-url | POST | Fetch image via BFF (CORS fallback) | S2 |
/api/storage/cdn-cookies | POST | Request/refresh CloudFront signed cookies | S4 |
/api/items/<itemEId> | PUT | Persist entity with imageUrl | S1, S2, S3, S5, S6, S7 |
Outbound: Storage (Direct)
Section titled “Outbound: Storage (Direct)”| Target | Method | Purpose | Scenario |
|---|---|---|---|
| Presigned POST URL | POST | Upload image bytes | S1, S2, S5, S6, S7 |
| CDN URL | GET | Render thumbnails and full-size images | S4, all display contexts |
Inbound: User Events
Section titled “Inbound: User Events”| Event | Handler | Scenario |
|---|---|---|
| File pick / drag-drop / clipboard paste | ImageDropZone input detection | S1 |
| URL text entry | ImageDropZone URL detection | S2 |
| Crop/zoom/rotate/pan/reset | ImagePreviewEditor | S1, S2 |
| Copyright acknowledgment | CopyrightAcknowledgment inline text (dialog footer) | S1, S2 |
| Confirm / Discard | ImageUploadDialog state machine | S1, S2, S5, S6, S7 |
| Remove image | ImageFormField remove control / alert-dialog | S3, S7 |
| Hover on image (~500ms) | ImageHoverPreview popover (larger preview, no action icons) | S4 |
| Double-click / Enter on image cell | ImageCellEditor → ImageUploadDialog | S5 |
ImageUploadDialog State Machine
Section titled “ImageUploadDialog State Machine”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:
| State | Description | Transitions |
|---|---|---|
Uploading | Presigned POST credentials requested from BFF, upload to Storage in progress. Progress indicator shown. | → View (success, entity updated via BFF/Backend) / → UploadError (failure) |
UploadError | Upload 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).
Form-Level Image State
Section titled “Form-Level Image State”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.
Grid-Level Image State
Section titled “Grid-Level Image State”In grid contexts, image state is managed by AG Grid’s cell renderer/editor lifecycle:
ImageCellDisplay: stateless renderer readingimageUrlfrom row data.ImageCellEditor: opensImageUploadDialogin modal mode. On confirm, calls AG Grid’sstopEditing()with the new value.
Modules
Section titled “Modules”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.
Scenario Mapping
Section titled “Scenario Mapping”| Component | Scenarios | System-Level Integration |
|---|---|---|
ImageUploadDialog | S1, S2, S5, S6, S7 | Calls BFF /api/image-upload (presigned POST credentials). Uploads to Storage via presigned POST. Calls BFF entity update API on confirm. Manages Uploading/UploadError states. |
ImageDropZone | S1, S2 | URL text entry triggers SPA-side reachability check and BFF fallback (/api/storage/check-url, /api/storage/fetch-url). |
ImagePreviewEditor | S1, S2 | Output (crop parameters) consumed by getCroppedImage to produce the Blob uploaded to Storage. |
ImageComparisonLayout | S1, S2, S7 | No direct system integration — pure UI. |
ImageDisplay | S4, all display | Renders <img> with CDN URLs. CloudFront signed cookies sent automatically by browser. Handles 403 (triggers cookie refresh via SPA-FR-019). |
ImageCellDisplay | S4, S5 | Stateless renderer — reads imageUrl from AG Grid row data. |
ImageCellEditor | S5 | Opens ImageUploadDialog in modal mode. On confirm, calls stopEditing() with new CDN URL. |
ImageFormField | S6, S7 | Triggers ImageUploadDialog for edit and alert-dialog for remove. Remove sends null imageUrl via BFF. |
ImageInspectorOverlay | S4, S5 | No direct system integration — renders CDN image at full size. |
ImageHoverPreview | S4, S5, S6, S7 | No direct system integration — popover preview of CDN image. |
CopyrightAcknowledgment | S1, S2 | Displays acknowledgment text inline in the confirm dialog footer. Confirm is always enabled. SPA-only, no backend logging (TD-04). |
getCroppedImage | S1, S2 | Produces the final Blob from crop/zoom/rotate parameters. Input to presigned POST upload. |
Backend Integration Types
Section titled “Backend Integration Types”The components use two type interfaces for backend connectivity, defined in
src/types/canary/utilities/image-upload-handlers.ts (see
post-implementation specification):
| Type | Purpose | Wiring |
|---|---|---|
ImageUploadHandler | Callback 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. |
ImageReachabilityCheck | Callback 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. |
ImageFieldConfig Parameterization
Section titled “ImageFieldConfig Parameterization”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 Field | Value (Items) | Requirement |
|---|---|---|
aspectRatio | 1 (1:1 square) | FR-014, SD-06 |
acceptedFormats | ['image/jpeg', 'image/png', 'image/webp', 'image/heic', 'image/heif'] | FR-008 |
maxFileSizeBytes | 10 * 1024 * 1024 (10 MB) | FR-009 |
maxDimension | 2048 (pixels, longest edge for compression) | FR-009 |
minDimension | 200 (pixels, shortest edge) | FR-040 |
entityTypeDisplayName | "Item" | FR-013 (error messages) |
propertyDisplayName | "Product Image" | FR-013 (error messages) |
Modified Modules
Section titled “Modified Modules”| Module | Change | Scenarios |
|---|---|---|
item-grid-columns | ImageCellDisplay replaces inline renderer. ImageCellEditor added for inline edit. | S4, S5 |
add-item-form | ImageFormField added as optional field using ITEM_IMAGE_CONFIG. | S6 |
edit-item-form | ImageFormField added for change/remove image alongside other entity fields. | S7 |
External Dependencies
Section titled “External Dependencies”| Library | Purpose | Used By |
|---|---|---|
react-easy-crop | Crop/zoom/pan UI with locked aspect ratio | ImagePreviewEditor |
react-dropzone | Drag-and-drop and file selection handling | ImageDropZone |
browser-image-compression | Auto-compression (85% JPEG, max 2048px) | ImageUploadDialog |
heic2any | HEIC/HEIF conversion (lazy-loaded) | ImageUploadDialog |
Copyright: (c) Arda Systems 2025-2026, All rights reserved
Copyright: © Arda Systems 2025-2026, All rights reserved