# Media Streamlining Plan: Driver Avatars, Team Logos, League Logos ## Goal Create one clean, conflict-free way to represent and deliver: - Driver avatars (defaults now; user uploads later) - Team logos (seeded) - League logos (seeded) So that: - Seeding never produces conflicting behavior across environments. - The UI never has to guess whether a value is a file path, an API route, a generated asset, or an uploaded asset. - There is exactly one place that decides the final image URL for each entity. ## What exists today (inventory, by responsibility) ### Driver avatars Where they surface: - Driver lists, driver leaderboards, race entry lists, dashboard summaries, and social/friend UI elements. - API payloads sometimes include an avatar URL; other times the client constructs a URL from the driver id. - There are multiple fallback strategies: empty string, null, or client-side default image. Where they come from: - A “default avatar set” of three files exists in the website public assets. - There is also a server route that can generate an avatar image for a driver id. - Some parts of the system treat driver avatar as a user-uploadable media setting. Observed problems: - Mixed meaning of the avatar field: sometimes it is an absolute URL, sometimes a relative path, sometimes a server route string. - Multiple fallbacks implemented in multiple places leads to inconsistent UI and hard-to-debug “missing image” bugs. - Multiple “demo/fake” avatar generators exist, creating divergent behavior between environments. ### Team logos Where they surface: - Team cards, team leaderboards, recruiting/featured team sections. - Sometimes the UI uses `logoUrl` from the API payload; other times it falls back to a server route based on id. Where they come from: - A server route can generate a team logo image for a team id. - Seed logic also “pre-seeds” team logos by writing route strings into an in-memory store. Observed problems: - Team “logoUrl” may be an actual URL, or it may be a placeholder, or it may be a server route string stored as data. - Storing route strings as if they were media values creates conflicts when routes change. - In some persistence modes the “seeded logo store” is not truly persisted, so bootstrapping may re-trigger reseeding or create inconsistent results. ### League logos Where they surface: - League cards, league headers, league pages. - UI tends to call a client-side helper that builds a league-logo URL from id. Where they come from: - A server route can generate a league logo image for a league id. - Seed logic also “pre-seeds” league logos by writing route strings into an in-memory store. Observed problems: - Same class of conflicts as team logos. - There is no single authoritative rule for when a league has a “real” logo versus a generated one. ## Proposed streamlined model (single canonical representation) ### Canonical concept: Media Reference (not a URL) Instead of treating stored values as “final URLs”, define a single canonical *media reference* for each entity image. Media reference types: - **System default**: a fixed asset shipped with the website (driver defaults: male/female/neutral). - **Generated**: deterministically generated from an entity id and a seeded pseudo-random source (team/league logos). - **Uploaded**: a user-uploaded object managed by the media subsystem. - **None**: intentionally unset. Key rule: **only one layer resolves media references into URLs**. ### URL resolution responsibilities - **Backend** resolves *references* into *final URLs* for API payloads. - **Backend** also serves the image bytes for generated assets and uploaded assets. - **Frontend** treats received URLs as ready-to-render and does not invent additional fallbacks beyond a single last-resort placeholder. ## Seeding strategy (cleanest route) ### Teams and leagues: seeded via faker, but without storing URLs Requirement: “seed team and league logos via faker”. Clean approach: - During seed, assign each team/league a **Generated** media reference. - The generator uses faker with a seed derived from the entity id to produce a deterministic “logo identity” (colors, initials, shapes, etc.). - The stored value is **only the reference** (type + seed key), not a route string and not a URL. - When the UI needs to show the logo, it either receives a resolved URL in the API payload or uses a single, standardized media URL builder. Benefits: - Deterministic results: same team id always yields the same logo. - No conflicts when URLs/routes change. - No need to persist binary files for seeded logos. ### Drivers: seeded from the 3 default avatar images Requirement: “seed driver logos from these defaults” and later “normally these would be user uploads”. Clean approach: - During seed, assign each driver a **System default** media reference selecting one of: - male-default-avatar - female-default-avatar - neutral-default-avatar - Selection is deterministic (based on driver id) so reseeding does not change faces randomly. - Later, if a user uploads an avatar, the reference switches to **Uploaded** and overrides the default. Benefits: - No dependency on generated avatars for baseline. - No ambiguous meaning of the avatar field. ## Contract rules (what the UI can rely on) ### Field semantics - Every API payload that includes a driver/team/league image should provide a **single resolved URL field** for that image. - Resolved URL is either: - a valid URL string the UI can render immediately, or - null (meaning: show a generic placeholder). - Never send empty strings. - Never send “sometimes relative file path, sometimes server route” style mixed values. ### Fallback rules - The backend resolver must guarantee a valid URL whenever it can (system default or generated). - The frontend uses exactly one last-resort placeholder if it receives null. - No per-component bespoke fallbacks. ## Streamlining work items (what changes where) ### 1) Centralize media reference resolution Create one “media resolver” concept used by: - API payload assembly for all places that include avatars/logos. - Image-serving routes for generated assets and uploaded assets. This resolver is the only place that knows: - how to map media references to a concrete image URL - what the fallback is when no uploaded media exists ### 2) Stop storing server route strings as data Remove the pattern where seed logic writes values like “/api/media/.../logo” into an in-memory media store. Replace it with: - stored media references (generated/system-default/uploaded) - consistent URL resolution at response time ### 3) Normalize route prefixes and caching behavior - Choose one public URL shape for these images and apply it universally. - Add consistent cache headers for generated assets (deterministic) so the browser and CDN can cache safely. ### 4) Align frontend consumption - Ensure the UI always prefers the resolved URL from the API payload. - Where the UI only has an id (e.g. very lightweight list items), use a single shared “URL builder” instead of ad-hoc string concatenation. - Remove duplicate “if missing then fallback to …” logic sprinkled across components. ### 5) Align tests and demo fakes - Eliminate competing fake avatar/logo generators. - Ensure all test fixtures use the same deterministic rules as seed and runtime generation. - Ensure snapshot/contract tests treat empty string as invalid and expect null instead. ### 6) Make bootstrapping/reseeding conflict-proof - Reseed decision should be based on durable data correctness (presence of required entities) rather than transient “in-memory media store” state. - Ensure “missing avatar/logo” checks are aligned with the new media reference model. ### 7) Migration and cleanup - Define how existing seeded databases are handled: - either a one-time cleanup that rewrites old stored values into the new reference model, or - a documented wipe-and-reseed path for local/dev environments. - Ensure the migration path eliminates stored route strings. ## Mermaid: Target flow ```mermaid flowchart TD UI[Website UI] --> API[API payloads include resolved image URLs] API --> RES[Media resolver] RES --> UP[Uploaded media storage] RES --> GEN[Deterministic generator] RES --> DEF[System default assets] GEN --> IMG[Image response with cache headers] UP --> IMG DEF --> UI IMG --> UI ``` ## Acceptance criteria - Driver avatars always render with one of the three defaults unless an upload exists. - Team and league logos always render deterministically in dev/test seed, without persisting URLs. - No API payload returns empty string for avatar/logo. - No UI component constructs its own bespoke fallback logic. - No bootstrapping loop caused by “missing media” when media is generated or defaults are available.