---
id: ADR-0002
title: Media Manager backend contract and mock storage adapter
status: active
date: 2026-07-15
tags: ["architecture", "media", "backend-gap", "repository-pattern"]
---

## Context

Sprint 4 (Media Manager) needs a media library: upload, browse, delete, and pick
images/files for use across Product Editor, Static Pages (CMS), and Branding.
No media backend exists yet — `/media` currently routes to a "coming soon"
placeholder (`BackofficeComingSoonPageComponent`), and `docs/BACKEND.md` does
not document any upload/storage endpoint. This mirrors the already-documented
draft-publish-flow gap in the Project Editor (see `PE-20260713T010000Z-0003`):
build the real contract, then implement a client-side mock adapter behind the
same interface so the UI never needs to change when the backend ships.

## Decision

- **Domain model** `MediaAsset`: `{ id, url, thumbnailUrl?, filename, mimeType,
  size, width?, height?, altText?: Record<locale, string>, tags?: string[],
  createdAt }`. `altText` follows the platform's `translations.{lang}` rule
  (ADR-0001) — never a flat string.
- **Repository contract** (future backend, to be implemented server-side):
  - `GET /media?page=&pageSize=&search=` → paginated `MediaAsset[]`
  - `POST /media/upload` (multipart) → `MediaAsset`
  - `DELETE /media/:id` → 204
  - `PATCH /media/:id` (altText/tags only) → `MediaAsset`
- **Frontend abstraction**: a `MediaRepository` interface (Repository pattern,
  per `docs/context/features/*` conventions) with two implementations selected
  via DI token:
  - `MockMediaRepository` — stores assets in IndexedDB (not localStorage: binary
    blobs need it) as an interim store until the backend exists. Data URLs are
    generated for rendering; the shape returned matches `MediaAsset` exactly.
  - `HttpMediaRepository` — thin wrapper over the endpoints above, added when
    the backend ships. Swapping providers is the only change required.
- **Media never enters the Bootstrap model.** Like products/orders/users, media
  assets are runtime admin data, not tenant configuration — consistent with
  ADR-0001's rule that Bootstrap contains only what's needed before the app
  starts.
- **Media Picker** is a standalone, reusable dialog (built on the existing
  `app-dialog` Design System primitive) so Product Editor and CMS editors
  consume the same selection UI instead of each building their own.

## Consequences

- Any feature needing to reference an image (product gallery, static page
  hero, branding logo) does so via `MediaAsset.url`/`id`, obtained through the
  shared Media Picker — never a raw file input duplicated per feature.
- When the backend ships, only `MediaRepository`'s DI provider changes; no
  component or facade code should need to change.
