Asset Library

Feature Owner (original implementation): Mich Tapawan — original Background Assets / Asset Library build
Updated by / current owner: Patrick Babala — this revision prepared 2026-10-01
Module: Develop (Content Production)
Priority: P1 — core content-production dependency (Feature Audit ID 3.1)
Status: Handoff — Draft (v1.0)
Date: 2026-10-01
Issue: [3.1] Asset Library (Feature Audit May 2026); related [3.6] Archive Asset, [3.7] Secure Asset Delete, #437 (trash soft-delete), #777 (typed-DELETE confirmation). The GitHub issue number for [3.1] could not be confirmed in this session and is intentionally not fabricated.
PRs covered by this hand-off: #695 (audio background-asset upload fix), #711 / #723 / #725 / #726 / #770 (feature/3.6-archive-asset — archive, bulk operations, sorting, 120-day purge), #867 (SVG upload stored-XSS hardening). Security ticket: M4 / #852.
Companion doc: Internal Technical Guide — Internal-Technical-Guide-Asset-Library.md (architecture, full DB schema, RLS, API request/response specs, helpers, ops, troubleshooting). This Hand-Off stays product-facing; where engineering depth is needed it cross-references the ITG instead of duplicating it.


EXECUTIVE SUMMARY

What is this feature?

The Asset Library (surfaced in the UI as “Background Assets”) is a creator-owned repository of reusable media. Creators upload a file once — image, audio, video, or PDF document — or register an external media link, and then attach that asset across quests, adventures, and activity cards through the editor’s “From Assets” picker. Assets can be renamed, archived (soft-deleted), restored, and permanently deleted.

Why does it matter?

Assets are the raw material of every quest. Without a shared library, creators re-upload the same background images, audio, and video for each new quest, media ownership is scattered, and content is harder to keep consistent. The library is the single place a creator manages reusable media.

What’s the MVP scope?

  • Upload image / audio / video / PDF files with server-enforced MIME and size limits

  • Add external media links (YouTube, Vimeo, audio URLs, image URLs)

  • Browse the active library and sort by name or date added

  • Rename an asset

  • Archive (soft-delete) an asset and view it in the Archive page

  • Restore an archived asset

  • Permanently delete an asset with a typed DELETE confirmation

  • Bulk select, bulk archive, and bulk permanent delete

  • Agency-scoped visibility (an agency member sees the agency’s assets; a standalone creator sees only their own)

  • Automatic video thumbnail extraction (first frame) with placeholder fallbacks

  • “From Assets” selection inside the quest/adventure editors

Shipped scope added after the original build:

  • PDF (DOCUMENT) assets and a 5-minute video duration cap (6bf936f0).

  • 120-day archive + auto-purge across quests, adventures, and background assets (ae533e9b, b813aaa8, PR #770).

  • Sorting and a selection action bar for bulk operations (5d3eeea3, 7de73df0).

  • Standardized destructive confirmation via the shared ConfirmDestructiveModal (13373adb, Issue #777).

  • Upload security hardening — SVG hard-reject, server-pinned content types, and a public-assets bucket MIME allow-list (fix/852-svg-upload-xss, PR #867, M4).

Still deferred: folder organization/sharing for assets, content-hash deduplication, per-creator storage quotas, asset versioning, and any asset management beyond the read-only reviewer picker.


1. USER PAIN POINT & SOLUTION

Current State (Without Feature)

A creator building multiple quests re-uploads the same images, audio, and video for each one. There is no single place to see, rename, reuse, or retire media, and there is no safe way to remove media that is no longer wanted.

Pain Point

  • Emotional: Repetitive uploads are tedious, and accidental permanent deletion is frightening.

  • Functional: No reusable media layer means duplicated storage objects and inconsistent quest visuals.

  • Business Impact: Slower quest production and higher storage waste; media cannot be governed or cleaned up.

Future State (With Feature)

Creators upload media once, reuse it everywhere through a consistent picker, rename it for clarity, and retire it safely — archiving first (restorable for 120 days) and only then deleting permanently behind a typed confirmation.

Marketing Hook

“Build once, reuse everywhere — one media library for every quest, adventure, and activity.”


2. CODEBASE ASSESSMENT

Current Implementation Status

The Asset Library is implemented as a creator dashboard (/creator/background-assets) plus an archive page (/creator/archive), backed by eight API routes under the (background-assets) route group and a set of shared upload helpers. Metadata lives in the public.asset_metadata table; bytes live in the public-assets Supabase Storage bucket.

Primary Files

  • app/creator/background-assets/page.tsx — main “Background Assets” dashboard: fetches active assets, sorts by name/date, and hosts the upload dialog and archive link.

  • app/creator/archive/page.tsx — the Archive (“Trash Bin”) page for soft-deleted assets, with restore, permanent delete, bulk actions, and a days-remaining badge.

  • components/creator/background-assets/AssetList.tsx — the asset grid: thumbnail/audio/music/document rendering, rename, archive, permanent delete, and bulk selection.

  • components/creator/background-assets/assetUploadingDialog.tsx — “Upload Asset” dialog offering file upload or external link.

  • components/creator/background-assets/DeleteAssetModal.tsx — “Move to Archive” confirmation (states the 120-day restore window).

  • components/creator/background-assets/DisplayAsset.tsx — full-screen preview modal for image, video/audio (YouTube embed or native player), and PDF (<iframe> + “Open in new tab” fallback).

  • lib/backgroundAssets/useBackground.ts — client hook that fetches /api/creator/list-assets with cache: "no-store".

  • lib/backgroundAssets/uploadHandlers.ts — client-side validation (MP4-only, size limits, 5-minute duration) and multipart/JSON submission.

  • app/api/creator/(background-assets)/upload-assets/route.ts — file + external-link upload endpoint (validation, storage, metadata insert).

  • app/api/creator/(background-assets)/list-assets/route.ts — active assets for the current creator/agency.

  • app/api/creator/(background-assets)/get-assets/route.ts — legacy duplicate of list-assets.

  • app/api/creator/(background-assets)/get-trash-assets/route.ts — archived assets.

  • app/api/creator/(background-assets)/edit-assets/route.ts — rename.

  • app/api/creator/(background-assets)/delete-assets/route.ts — archive (soft-delete; sets a 120-day expiry).

  • app/api/creator/(background-assets)/restore-assets/route.ts — un-archive.

  • app/api/creator/(background-assets)/purge-assets/route.ts — permanent delete (DB row + Storage object).

  • lib/uploadAssets/validate.ts — shared MIME/extension allow-lists, SVG hard-reject, content-type resolution.

  • lib/uploadAssets/uploadFileToStorage.ts — Buffer upload with server-pinned content type.

  • lib/uploadAssets/saveMetadataToDB.ts / parseRequestData.ts / probeMp4Duration.ts — insert, request parsing, MP4 duration probe.

  • lib/archive/purgeArchivedContent.ts / lib/archive/getDaysRemaining.ts — 120-day retention and purge logic.

  • app/api/cron/check-pending-deletion/route.ts / app/api/cron/purge-expired-archived/route.ts — 30-day warning email and 120-day auto-purge.

Current Behavior Summary

  • Creators upload a file or register an external link; files are validated server-side for type and size, and videos are capped at 5 minutes.

  • Every library asset belongs to a creator, and visibility is agency-scoped.

  • Archiving a single or multiple assets removes them from the active library immediately and starts a 120-day countdown.

  • Archived assets are restorable; permanent deletion requires typing DELETE and is re-validated server-side.

  • A scheduled job emails a 30-day warning and a later scheduled job purges assets older than 120 days.

Strengths Already Present

  • Server-side validation and limits that cannot be bypassed by a direct API call.

  • Safe destructive paths: archive is reversible, permanent delete is typed-confirmation gated on both client and server.

  • Reusable media across the editor through the “From Assets” picker.

  • Agency-aware ownership and listing.

  • Automatic thumbnails for uploaded videos with graceful placeholder fallbacks.

Gaps / Hardening Opportunities

  • Orphaned Storage objects: if the metadata insert fails after a successful upload, the stored object is not cleaned up (upload-assets/route.ts:194-218).

  • Best-effort Storage cleanup: purge logs "Storage cleanup failed:" and continues; failures are not retried.

  • Stale copy: lib/uploadAssets/uploadFileToStorage.ts:111 says “5 MB limit” while the configured image limit is 10 MB; migration comments describe a 30-day retention while the code enforces 120 days.

  • expires_at is write-only: written on archive and cleared on restore, but the purge job uses the deleted_at cutoff instead.

  • Duplicate endpoint: get-assets duplicates list-assets, and its fallback type inference does not cover DOCUMENT.

  • E2E drift: tests/e2e/creator/archive-asset.spec.ts and tests/e2e/creator/secure-asset-delete.spec.ts still expect older copy (“Move to Trash”, “30d left”) and should be refreshed.


3. 4D FRAMEWORK MAPPING

Diagnose

Creators need a durable, reusable store of media so quests can be assembled quickly and consistently, and so obsolete media can be retired without risk.

Design

Media is modeled as first-class creator-owned assets (asset_metadata rows + Storage objects) with an explicit lifecycle: active → archived → permanently deleted.

Develop

Creators upload or link media, tag it with an asset/usage type, browse and rename it, and select it from the editor’s “From Assets” picker.

Deliver

Quests, adventures, and activities render the referenced media, and the archive/retention lifecycle keeps storage clean and recoverable.


4. USER FLOWS

Entry Point

  • Creator dashboard: /creator/background-assets

  • Creator archive: /creator/archive

  • Editor pickers: “From Assets” inside the quest/adventure editors (e.g. Select Background Image → From Assets)

Success Criteria

  • An asset uploads successfully and appears in the library.

  • The asset can be renamed and previewed.

  • The asset can be attached to content via the “From Assets” picker.

  • An archived asset disappears from the active library and appears in the Archive.

  • A restored asset returns to the active library.

  • A permanently deleted asset is gone from both the library and Storage (behind a typed DELETE).

Main Flow (Happy Path)

  • Creator opens Background Assets.

  • Creator clicks Upload Asset and chooses Upload File (or Add External Link).

  • The client validates type/size (and video duration), then submits to the API.

  • The API validates again server-side, uploads bytes to Storage, and writes the asset_metadata row.

  • The asset appears in the grid with a thumbnail (video thumbnails are extracted from the first frame).

  • The creator reuses the asset from an editor’s “From Assets” picker.

  • When done, the creator archives the asset (restorable for 120 days) or permanently deletes it with typed confirmation.

Edge Cases

  • Oversized file: Rejected client- and server-side with a size-specific message.

  • Video over 5 minutes: Rejected after the metadata duration probe.

  • Non-MP4 video: Rejected client-side (“Only MP4 video files are allowed.”).

  • SVG/SVG-named-raster: Rejected before storage (stored-XSS guard).

  • Already archived: Archiving again returns “Assets are already archived”.

  • Already active: Restoring again returns “Assets are not archived”.

  • Permanent delete typed incorrectly: Button stays disabled and the server rejects any token other than DELETE.

  • Upload succeeds but metadata insert fails: The request returns an error; the Storage object may be orphaned and needs manual reconciliation.

Decision Points

  • IF the asset is uploaded as a file → bytes are stored in public-assets and a file_path/file_url are recorded.

  • IF the asset is an external link → no bytes are stored; source is external and file_url points at the third-party URL.

  • IF usage_context is BADGE → the file is stored but no asset_metadata row is created (badge images do not clutter the library).

  • IF the creator is in an agency → the library is scoped to the agency’s users; otherwise only the creator’s own assets are visible.


5. INFORMATION ARCHITECTURE

Primary Information (Always visible)

  • Asset thumbnail / type placeholder (image, music, file icon)

  • Asset name

  • File size (or “Link” for external assets)

Secondary Information

  • Asset type / usage context (used by the picker and editors)

  • Sort control (Alphabetical, Date Added) and Archive link

  • Archive page: days-remaining badge per asset

Tertiary Information (Hidden until needed)

  • file_path / file_url (Storage or external URL)

  • asset_type / source

  • Internal asset IDs used by the API

Actions

  • Primary CTA: Upload Asset / Add External Link

  • Secondary Actions: Rename (pencil), Archive (box), Delete Permanently (trash), Restore, bulk select / Select All / Deselect All


6. WIREFRAMES

No standalone wireframe artifact is stored for this feature.

Key Screens:

  • Background Assets dashboard (header: title, sort dropdown, Archive link, Upload Asset; grid of asset cards)

  • Upload Asset dialog (choose method → file picker or external link form)

  • Move to Archive confirmation modal

  • Permanently Delete File confirmation modal (typed DELETE)

  • Archive page (Cards with thumbnail, days-remaining badge, Restore / Delete Permanently)

  • Full-screen asset preview modal

Annotations:

  • Archive is a separate page reached from the dashboard header.

  • Bulk actions appear in a floating selection action bar once any asset is selected.

  • Destructive modals are the shared, standardized typed-confirmation component.


7. WIREFLOWS

Creator opens Background Assets → Uploads a file or adds an external link → asset appears in the grid → creator picks it from an editor’s “From Assets” picker → later archives it → asset moves to the Archive with a 120-day countdown → creator restores it or permanently deletes it with a typed DELETE.


8. PROTOTYPE

Figma Prototype Link: Not currently available for this feature.

How to test:

  • Open /creator/background-assets.

  • Click Upload Asset → Upload File, and upload an image, an MP3, an MP4 (under 5 min), and a PDF.

  • Add an external link (e.g. a YouTube URL) and confirm it appears as a card.

  • Rename one asset with the pencil action and confirm the name updates.

  • Archive one asset, open /creator/archive, and confirm it shows with a days-remaining badge.

  • Restore it from the Archive and confirm it returns to the library.

  • Archive another asset and permanently delete it by typing DELETE.

  • Open a quest editor, choose a background image → From Assets, and confirm the library assets are selectable.


9. DATA MODEL

Core Table

The feature depends on the public.asset_metadata table with the following concepts:

  • id — asset UUID

  • creator_id — owner, the internal app_users.id (not the auth UID)

  • file_name — display name (editable)

  • file_path / file_url / external_url — Storage path and public/external URL

  • file_type / file_size — MIME type and byte size (NULL for external)

  • asset_type — IMAGE, VIDEO, AUDIO, DOCUMENT, BADGE, profile/cover contexts, or quest_*

  • usage_context — e.g. BACKGROUND, BADGE, quest:profile

  • source — uploaded or external

  • thumbnail_url — preview image (video/audio/document)

  • deleted_at / expires_at — soft-delete and retention timestamp

  • deletion_warning_sent_at — when the 30-day warning email was sent

  • created_at / updated_at

Retention Shape

Archiving sets deleted_at = now and expires_at = now + 120 days. A scheduled job emails a warning 30 days before the 120-day boundary and a later job permanently purges assets whose deleted_at is older than 120 days. Retention is defined once in lib/archive/getDaysRemaining.ts (ARCHIVE_RETENTION_DAYS = 120).

Relationship to Users

Assets link to app_users by creator_id (logical relationship; there is no database foreign key). Visibility is resolved through agencyResourceFilter() — agency members see the agency’s assets, standalone creators see only their own.

Full column types, indexes, triggers, and RLS policies are in ITG §3–4 (Data Model / Schema).


10. API CONTRACTS

Creator — List Active Assets

  • Endpoint: GET /api/creator/list-assets

  • Behavior: Returns non-deleted assets for the current creator/agency, newest first, normalized to { id, name, type, uploaded_at, url, thumbnail_url, source, file_size, asset_type, usage_context }.

Creator — List Archived Assets

  • Endpoint: GET /api/creator/get-trash-assets

  • Behavior: Same shape as list-assets plus deleted_at, ordered by deletion time.

Creator — Upload Asset

  • Endpoint: POST /api/creator/upload-assets

  • Request: multipart/form-data with file, file_name, asset_type, usage_context, optional thumbnail_file; or JSON with external_url, file_name, asset_type, usage_context.

  • Behavior: Validates asset type/usage, MIME/extension, size, and (for video) duration; uploads to public-assets; writes asset_metadata unless usage_context is BADGE; returns the created asset.

Creator — Rename Asset

  • Endpoint: PATCH /api/creator/edit-assets

  • Request: { "asset_id": "uuid", "new_name": "New name" }

  • Behavior: Verifies ownership, then updates file_name.

Creator — Archive Asset(s)

  • Endpoint: DELETE /api/creator/delete-assets

  • Request: { "asset_id": "uuid" } or { "asset_ids": ["uuid", ...] }

  • Behavior: Ownership check per asset; soft-deletes only non-archived rows and returns archivedIds.

Creator — Restore Asset(s)

  • Endpoint: PATCH /api/creator/restore-assets

  • Request: { "asset_id": "uuid" } or { "asset_ids": ["uuid", ...] }

  • Behavior: Restores archived rows and returns restoredIds, missingIds, and skippedIds.

Creator — Permanently Delete Asset(s)

  • Endpoint: DELETE /api/creator/purge-assets

  • Request: { "asset_id" | "asset_ids": ..., "confirm_text": "DELETE" }

  • Behavior: Requires the literal DELETE; deletes DB rows first, then removes their Storage objects (and the video thumbnail) for rows confirmed deleted.

System — Retention Jobs

  • GET /api/cron/check-pending-deletion — sends the 30-day warning email (requires CRON_SECRET).

  • GET /api/cron/purge-expired-archived — purges assets/quests/adventures older than 120 days (requires CRON_SECRET).

Auth checks, exact error messages, and full request/response shapes are in ITG §3–4 (API Specification).


11. DATA REQUIREMENTS

Frontend Needs

The creator dashboard needs:

  • The list of active assets for the current creator/agency.

  • The list of archived assets with deleted_at for the countdown badge.

  • Upload support for file and external-link modes.

  • Rename, archive, restore, and permanent-delete actions.

  • The ability to select the asset’s asset_type/usage_context so editors can filter the picker.

Picker Needs

Editor “From Assets” pickers need:

  • Active, non-archived assets only (archived assets must not be selectable).

  • asset_type and the public url/thumbnail_url.


12. SECURITY & AUTHORIZATION

Who can access this feature?

  • Creator: ✔ (full CRUD on owned/agency assets)

  • Agency Owner / Agency member: ✔ (agency-scoped)

  • Admin: ✔

  • Reviewer: 👁 read-only (asset picker / review flows)

  • Learner: ✘

Authorization Logic

  • Every route calls getCreatorAuthContext(), which requires an authenticated user with a platform role of CREATOR, AGENCY, or ADMIN.

  • Reads are scoped by agencyResourceFilter() — agency members see all agency users’ assets, standalone creators see only their own.

  • Mutations call verifyAgencyResourceOwnership() per selected asset before writing.

  • Permanent deletion is gated by a typed DELETE token that is re-validated server-side (Zod literal).

  • Uploads are hardened against stored XSS: SVG is hard-rejected before storage, stored content types are server-derived rather than client-supplied, and the public-assets bucket carries a MIME allow-list that excludes SVG/HTML/XML/JS.

  • asset_metadata is protected by Row Level Security; the latest policies resolve the caller through app_users by clerk_id = auth.uid().

Auth note: authentication is Supabase Auth (not Clerk). The legacy-named app_users.clerk_id column now stores the Supabase auth UUID. The app writes the internal app_users.id into asset_metadata.creator_id. See ITG §3–4.


13. ERROR HANDLING

Common Errors

  • Missing asset_type / usage_context ("Missing asset_type" / "Missing usage_context").

  • Invalid usage context ("Invalid usage_context: ...").

  • MIME/extension not allow-listed ("Invalid MIME type for <type>: <mime>").

  • SVG upload ("SVG files are not allowed").

  • Size violations: video "Video size must not exceed 25MB.", audio "Audio size must not exceed 10MB.", document "Document size must not exceed 20MB.", image "Image size must not exceed 10MB.".

  • Video duration ("Video exceeds 5 minutes.").

  • Asset not found ("Assets not found", "Asset not found or not authorized").

  • Already archived ("Assets are already archived") / not archived ("Assets are not archived").

  • Missing IDs ("No asset IDs provided").

  • Bad purge payload ("Invalid permanent purge request payload").

  • Upload/DB failures ("Failed to upload asset", "Failed to save metadata").

Handling Guidance

  • Return clear, size/type-specific messages so the client toast is actionable.

  • Keep archive reversible and reserve the typed DELETE for permanent deletion only.

  • Re-validate every destructive token server-side; never trust the client modal.

  • On upload failure after Storage succeeds, reconcile the orphaned object manually until cleanup is added.


14. TESTING CHECKLIST

Happy Path

  • Upload an image, audio, MP4 (< 5 min), and PDF; each appears in the library.

  • Add an external link and confirm it appears with a placeholder/preview.

  • Rename an asset and confirm the new name persists after reload.

  • Attach an asset from the editor’s “From Assets” picker.

  • Archive an asset; confirm it leaves the library and appears in the Archive with a countdown.

  • Restore an archived asset; confirm it returns to the library.

  • Permanently delete an asset by typing DELETE; confirm it is gone after reload.

  • Bulk select and archive/delete multiple assets.

Edge Cases

  • Oversized file is rejected with the correct type-specific message.

  • Video over 5 minutes is rejected.

  • Non-MP4 video is rejected.

  • An SVG renamed to a raster extension is rejected.

  • Re-archiving an archived asset returns “already archived”.

  • Restoring an active asset returns “not archived”.

  • Permanent delete is blocked until DELETE is typed exactly; wrong text shows “Text does not match”.

  • Archived assets do not appear in the editor’s “From Assets” picker.

  • A direct API call with an oversized/typed payload is still rejected server-side.


15. OPEN QUESTIONS

For Product

  • Should assets be organizable into folders or shared across creators? — Still open.

  • Should the library surface storage usage / quotas per creator or agency? — Still open.

  • Should badge images become first-class assets rather than reference-only uploads? — Still open.

For Engineering

  • Should orphaned Storage objects be cleaned up when a metadata insert fails? — Still open (known gap).

  • Should the purge job retry failed Storage cleanups instead of logging and continuing? — Still open.

  • Should expires_at drive purge instead of a recomputed deleted_at cutoff? — Still open.

  • Should the duplicate get-assets endpoint be removed in favor of list-assets? — Still open.

  • Should the archive/permanent-delete E2E specs be refreshed to the current UI copy and 120-day window? — Still open (tests currently expect “Move to Trash” / “30d left”).


16. OUT OF SCOPE (v1.1+)

  • Folder organization and cross-creator asset sharing — still out of scope.

  • Content-hash deduplication and per-creator storage quotas — still out of scope.

  • Asset versioning / history — still out of scope.

  • Reviewer or learner asset management (read-only picker only) — still out of scope.

  • Paid/seat-based limits on library size — still out of scope.


17. SUCCESS METRICS

  • Assets uploaded per active creator.

  • Asset reuse rate (assets referenced by more than one quest/activity).

  • Archive → restore rate (how often creators recover assets).

  • Permanent-deletion volume vs. archive volume.

  • Upload failure rate by type (size/MIME/duration).

  • Storage reclaimed by the 120-day auto-purge.


18. DEPENDENCIES

This feature depends on

  • The asset_metadata data model.

  • The public-assets Supabase Storage bucket and its MIME allow-list.

  • The creator authentication layer (Supabase Auth) and agency ownership helpers.

  • The shared upload validators (lib/uploadAssets/*).

  • The shared typed-confirmation modal (ConfirmDestructiveModal).

  • The retention/purge jobs and the email service (deletion warnings).

These features depend on this

  • Quest/Adventure editors’ “From Assets” media pickers.

  • Activity Cards banner/image selection.

  • Badge image upload (reference-only, intentionally not in the library).

  • Content deletion flows that clean up referenced asset_metadata rows and Storage objects.


19. TIMELINE & OWNERSHIP

Implementation Ownership

  • Original owner: Mich-Tapawan — original Background Assets / Asset Library build (Aug 2025), with later maintenance by Christian Denzon (bulk operations, archive/120-day purge, sorting, M4 hardening), Patrick Babala (PDF/DOCUMENT, video duration, external links, confirmation modal), Jethro Lagmay (soft-delete/trash), scorevi (migration/API standardization), and clydetims (RLS → auth.uid()).

  • Current owner / updater: Patrick Babala — this Hand-Off revision.

  • QA: Not enumerated in this session — Unknown / TBD — needs product input.

  • Shipped: core library (2025-08); archive + 120-day purge, bulk operations, sorting, PDF/DOCUMENT, duration cap, and upload hardening (2026).

  • Estimated Completion: core complete; remaining items are the Open Questions and Out of Scope sections above.


REFERENCES

  • Issue: [3.1] Asset Library (Feature Audit May 2026) — GitHub issue number not confirmable in this session.

  • Related features: [3.6] Archive Asset, [3.7] Secure Asset Delete.

  • Related issues: #437 (Trash Bin soft-delete), #777 (typed-DELETE confirmation).

  • PRs: #695 (audio background-asset upload fix); #711, #723, #725, #726, #770 (feature/3.6-archive-asset — archive, bulk operations, sorting, 120-day purge); #867 (SVG upload stored-XSS hardening).

  • Security ticket: M4 (#852) — SVG stored XSS via the upload flow.

  • Companion ITG: Internal-Technical-Guide-Asset-Library.md.

  • Offline limitation: the [3.1] issue number and QA owner could not be resolved without GitHub access.


VERSION HISTORY

  • 1.0 — 2026-10-01 — Patrick Babala. Initial Hand-Off for the Asset Library (Background Assets): product summary, codebase assessment, 4D mapping, user flows, information architecture, data model, API contracts, security/authorization, error handling, testing checklist, open questions, out-of-scope, metrics, dependencies, ownership, and references. Reconstructed from the current codebase and git history.


Was this article helpful?