Skip to content

FRD: Media-Processing Wrapper

Document Summary

FieldDetails
Feature NameMedia-Processing Wrapper
StatusDraft
OwnerDavid Holmes
ContributorsEngineering
Target Releasev2.0.0 (P2)
Related LinksRoadmap item #76, ADR-027 (Default Tech Stack)
Last Updated2026-05-26
Open Source Librariessharp, fluent-ffmpeg

Introduction

Overview

The Media-Processing Wrapper library provides a vendor-neutral contract for image and video operations: transform URLs, generate thumbnails, retrieve optimization metadata, and handle fallback behavior. The design system already ships media UI components (media.tsx, media-card.tsx), but there is no shared abstraction for the underlying media-processing service. This library bridges that gap so application code can request transforms, thumbnails, and metadata without coupling to Cloudinary, Imgix, or any specific provider.

Goals

  • Define a common MediaProvider interface for URL-based transforms (resize, crop, format conversion).
  • Provide a typed thumbnail-generation API with preset sizes and custom dimensions.
  • Expose optimization metadata (format, dimensions, file size, content type) through a typed schema.
  • Define fallback behavior when a provider is unavailable or a transform fails.
  • Ship a mock adapter for testing and a passthrough adapter for development.

Non-Goals

  • Implementing production Cloudinary, Imgix, or S3 adapters (shipped separately).
  • Client-side image editing or cropping UI.
  • Video transcoding or streaming.
  • CDN configuration or cache invalidation.
  • File upload (covered by the file-upload recipe).

Scope

In Scope

AreaDescription
Media contractMediaProvider interface for transformUrl, thumbnailUrl, getMetadata
Transform optionsTyped options for width, height, crop mode, format, quality
Thumbnail presetsNamed presets (sm, md, lg, xl) with configurable dimensions
Optimization metadataMediaMetadata type: format, width, height, fileSize, contentType
Fallback behaviorStrategy pattern for unavailable providers: return original URL, placeholder, or throw
Mock adapterIn-memory adapter returning predictable URLs for testing
Passthrough adapterReturns original URLs unchanged for local development
Unit testsFull coverage of transforms, thumbnails, metadata, and fallback
DocumentationStorybook MDX docs with usage examples

Out of Scope

AreaReason
Vendor-specific adaptersShipped separately per provider
File uploadCovered by the file-upload recipe (item #78)
Video transcodingDifferent concern requiring specialized infrastructure
CDN/cache configurationInfrastructure concern
Client-side image editingUI component concern

Users and Pain Points

User Groups

UserDescriptionNeeds
Frontend developersEngineers rendering images with transforms in UI componentsA consistent API for generating transform URLs regardless of provider
Backend developersEngineers generating thumbnail URLs in API responsesA typed thumbnail API with presets
Design system maintainersEngineers maintaining media.tsx and media-card.tsxA clean integration point for media processing in existing components

Pain Points

UserPain PointImpact
Frontend developersTransform URL construction is scattered across components with provider-specific string manipulationProvider lock-in; inconsistent URL patterns; hard to change providers
Backend developersNo shared thumbnail preset definitions; each service defines its own sizesInconsistent thumbnail dimensions across products
Design system maintainersmedia.tsx has no standard way to request optimized imagesComponents render unoptimized images or duplicate optimization logic

Definitions

TermDefinition
Media providerAn object implementing MediaProvider that generates transform URLs for a specific service
Transform URLA URL that instructs a media service to apply operations (resize, crop, format) to an image
Thumbnail presetA named size configuration (e.g., sm: 150x150, md: 300x300)
Optimization metadataInformation about an image: format, dimensions, file size, content type
Fallback strategyThe behavior when a provider cannot process a request: return original URL, use placeholder, or throw
Passthrough adapterA provider that returns URLs unchanged; useful for local development

Current State

Existing Behavior

The design system ships media.tsx and media-card.tsx components that accept image URLs directly. There is no abstraction for generating transform URLs, requesting thumbnails, or retrieving metadata.

Current Limitations

  • No shared interface for media transforms; each app constructs provider-specific URLs inline.
  • No thumbnail preset system; thumbnail sizes are hardcoded per feature.
  • No fallback behavior when a media provider is unavailable.
  • No mock adapter for testing image-dependent components.

Existing Workarounds

  • Developers construct Cloudinary or Imgix URLs manually with string concatenation.
  • Tests use static image URLs without testing transform logic.
  • Components accept full URLs, pushing transform responsibility to the consumer.

Proposed Solution

Summary

Ship a TypeScript library (@dmwd/media) exporting a MediaProvider interface, transform and thumbnail types, a metadata schema, fallback strategies, and mock/passthrough adapters.

Key Capabilities

  • MediaProvider interface with transformUrl, thumbnailUrl, and getMetadata methods.
  • TransformOptions type: { width?, height?, crop?, format?, quality? }.
  • ThumbnailPreset type with named sizes and a custom(width, height) builder.
  • MediaMetadata type: { format, width, height, fileSize, contentType, blurhash? }.
  • FallbackStrategy type: 'original' | 'placeholder' | 'throw'.
  • MockMediaProvider returning predictable, assertion-friendly URLs.
  • PassthroughMediaProvider returning original URLs unchanged.

User Experience

Not directly applicable. Indirectly, users see faster-loading, correctly-sized images because the library enables consistent optimization.

Developer Experience

Developers inject a MediaProvider and call provider.transformUrl(src, { width: 800, format: 'webp' }) to get an optimized URL. Components like media.tsx can accept a provider prop or use a context. In tests, MockMediaProvider returns deterministic URLs. In local dev, PassthroughMediaProvider skips transforms.


Requirements

IDRequirementPriorityNotes
FR-001The library must export a MediaProvider interfaceMustCore contract
FR-002The library must export TransformOptions and ThumbnailPreset typesMustTyped transforms
FR-003The library must export a MediaMetadata typeMustOptimization info
FR-004The library must ship MockMediaProvider and PassthroughMediaProviderMustTesting and dev
FR-005The library must support configurable fallback strategiesMustResilience
FR-006The library should export a MediaContext React provider for component integrationShouldDX convenience

Priority Definitions

PriorityMeaning
MustRequired for this feature to ship.
ShouldImportant, but can be deferred if needed.
CouldNice to have. Not required for initial release.

Functional Requirements

IDRequirementUser BenefitPriority
FUNC-001transformUrl(src, options) returns a new URL with the requested transforms appliedConsistent transform APIMust
FUNC-002thumbnailUrl(src, preset) returns a URL for the specified thumbnail presetEasy thumbnail generationMust
FUNC-003getMetadata(src) returns MediaMetadata for the given sourceComponents can render placeholders with correct aspect ratiosShould
FUNC-004Thumbnail presets include sm (150x150), md (300x300), lg (600x600), xl (1200x1200)Consistent sizing across productsMust
FUNC-005TransformOptions supports width, height, crop (fill, fit, cover), format (webp, avif, jpeg, png), and quality (1-100)Full transform controlMust
FUNC-006When fallback is 'original', return the unmodified source URL on provider failureGraceful degradationMust
FUNC-007When fallback is 'placeholder', return a configurable placeholder URL on provider failureVisual feedback that something went wrongMust
FUNC-008When fallback is 'throw', throw a typed MediaTransformError on provider failureCallers can handle errors explicitlyMust
FUNC-009MockMediaProvider.transformUrl returns a URL containing the transform parameters as query stringsTests can assert on requested transformsMust

Non-Functional Requirements

IDRequirementCategoryPriority
NFR-001Zero runtime dependencies (core library)MaintainabilityMust
NFR-002React context provider is a separate entry point (@dmwd/media/react) to keep the core framework-agnosticCompatibilityMust
NFR-003All public types exported from package entry pointMaintainabilityMust
NFR-004transformUrl and thumbnailUrl must be synchronous (URL construction only)PerformanceMust
NFR-005Bundle size under 3 KB minified + gzipped (core, excluding React provider)PerformanceShould

API / Interface Requirements

Public API

NameTypeDescriptionRequired
MediaProviderinterfacetransformUrl, thumbnailUrl, getMetadataYes
TransformOptionstype{ width?, height?, crop?, format?, quality? }Yes
ThumbnailPresettype'sm' | 'md' | 'lg' | 'xl' | { width: number; height: number }Yes
MediaMetadatatype{ format, width, height, fileSize, contentType, blurhash? }Yes
FallbackStrategytype'original' | 'placeholder' | 'throw'Yes
MediaTransformErrorclassError with src, options, and causeYes
MockMediaProviderclassPredictable URLs for testingYes
PassthroughMediaProviderclassReturns original URLsYes
THUMBNAIL_PRESETSconstMap of preset names to dimensionsYes

Example Usage

import { type MediaProvider, PassthroughMediaProvider, THUMBNAIL_PRESETS } from "@dmwd/media";
const provider: MediaProvider = new PassthroughMediaProvider();
// Transform
const optimized = provider.transformUrl("/images/hero.jpg", {
width: 800,
format: "webp",
quality: 80,
});
// Thumbnail
const thumb = provider.thumbnailUrl("/images/hero.jpg", "md");
// Returns URL for 300x300 thumbnail
// Custom thumbnail
const custom = provider.thumbnailUrl("/images/hero.jpg", { width: 400, height: 250 });

API Notes

  • transformUrl and thumbnailUrl are synchronous; they construct URLs, not fetch images.
  • getMetadata is async because it may require a network call to the provider.
  • The React context provider (MediaContext) is in a separate entry point to keep the core usable in non-React environments.
  • MockMediaProvider encodes transform parameters in the returned URL for test assertions.

Accessibility Requirements

IDRequirementNotes
A11Y-001Library must not interfere with alt text on imagesTransform URLs must not strip or alter alt text handling
A11Y-002Placeholder fallback images should have appropriate alt text guidance in docsDocument that consumers must provide alt text for placeholders

Checklist

  • Keyboard support is defined. (N/A — no interactive UI)
  • Focus behavior is defined. (N/A — no interactive UI)
  • Screen reader behavior is defined. (N/A — no interactive UI)
  • Color contrast requirements are met. (N/A — no UI)
  • Reduced motion behavior is considered. (N/A — no animation)
  • Semantic HTML expectations are documented. (Consumers handle <img> semantics)
  • ARIA usage is defined only where needed. (N/A — no UI)

Content and Documentation Requirements

IDRequirementLocationPriority
DOC-001API reference with all exported types, classes, and constantsStorybook MDXMust
DOC-002Quick start guide with transform and thumbnail examplesStorybook MDXMust
DOC-003Integration guide for existing media.tsx and media-card.tsxStorybook MDXMust
DOC-004Fallback strategy guideStorybook MDXMust
DOC-005Guide for building a vendor adapter (Cloudinary, Imgix)Storybook MDXShould
DOC-006Testing guide with MockMediaProviderStorybook MDXMust

Documentation Should Include

  • Overview and provider pattern explanation
  • Installation and import
  • Transform URL construction
  • Thumbnail presets and custom sizes
  • Fallback strategies
  • React context integration
  • Testing with MockMediaProvider
  • Building a vendor adapter
  • Integration with existing media components

Dependencies

DependencyTypeOwnerStatusNotes
TypeScript 5.xEngineeringDavid HolmesReadyBuild toolchain
VitestEngineeringDavid HolmesReadyTest runner
React 18+EngineeringDavid HolmesReadyPeer dependency for @dmwd/media/react only
media.tsx, media-card.tsxDesign SystemDavid HolmesReadyExisting components to integrate with

Risks and Tradeoffs

Risk / TradeoffImpactMitigation
Generic interface may not expose all vendor-specific featuresSome transforms (face detection, AI cropping) may not fitUse metadata or extra fields for vendor extensions
Synchronous transformUrl assumes URL-based transformsSome providers require API calls for transformsDocument that providers needing async transforms should use getMetadata or a separate async method
Thumbnail presets may not fit all use casesDifferent products may need different default sizesPresets are exported as constants; consumers can define their own
PassthroughMediaProvider may mask issues in developmentTransforms work in prod but are untested in devDocument that MockMediaProvider is better for catching transform issues in tests

Open Questions

IDQuestionOwnerStatusResolution
Q-001Should transformUrl accept a srcSet option for responsive images?David HolmesOpen
Q-002Should MediaMetadata include a blurhash field for placeholder rendering?David HolmesOpen
Q-003Should the React provider support SSR with server-side metadata fetching?David HolmesOpen
Q-004Should we define a MediaAsset type that bundles URL + metadata for richer component integration?David HolmesOpen

Acceptance Criteria

IDCriteriaRelated Requirement
AC-001MediaProvider interface is exported with transformUrl, thumbnailUrl, getMetadataFR-001
AC-002transformUrl(src, { width: 800, format: 'webp' }) returns a URL reflecting those transformsFUNC-001
AC-003thumbnailUrl(src, 'md') returns a URL for a 300x300 imageFUNC-002
AC-004Custom thumbnail thumbnailUrl(src, { width: 400, height: 250 }) worksFUNC-002
AC-005Fallback 'original' returns the source URL when provider failsFUNC-006
AC-006Fallback 'throw' throws MediaTransformError when provider failsFUNC-008
AC-007MockMediaProvider encodes transforms in the returned URLFUNC-009
AC-008All public types re-exported from package indexNFR-003
AC-009Unit tests pass covering transforms, thumbnails, metadata, and all fallback strategiesFR-001 through FR-005
AC-010Storybook MDX docs render without errorsDOC-001

LLM Handoff Instructions

Expected LLM Behavior

  • Follow the requirements and acceptance criteria in this document.
  • Do not expand scope beyond the In Scope section.
  • Respect the Out of Scope section.
  • transformUrl and thumbnailUrl must be synchronous (pure URL construction).
  • getMetadata must be async.
  • Keep the React provider in a separate entry point (@dmwd/media/react).
  • The mock adapter must encode parameters in the URL for test assertions (e.g., mock://image?w=800&fmt=webp).
  • Integrate with existing media.tsx and media-card.tsx where natural, but do not refactor those components.

LLM Should Not

  • Invent undocumented product behavior.
  • Implement vendor-specific adapters (Cloudinary, Imgix).
  • Add image processing dependencies (sharp, jimp).
  • Change unrelated components.
  • Fetch actual images in tests.
  • Make transformUrl async.

Decision Log

DateDecisionReasonOwner
2026-05-26Synchronous transformUrl and thumbnailUrlURL construction should not require async; keeps components simpleDavid Holmes
2026-05-26Separate React provider entry pointCore library stays framework-agnosticDavid Holmes
2026-05-26Three fallback strategies (original, placeholder, throw)Covers graceful degradation, visual feedback, and explicit error handlingDavid Holmes
2026-05-26Four named thumbnail presetsStandardizes common sizes; custom sizes are still supportedDavid Holmes

Document History

DateAuthorChange
2026-05-26David HolmesInitial draft