# asset-library.screen Single-screen breakdown for the **asset library drawer** (brief B §B2) — Quinn's grid-first view over `content_assets`, the place she picks the photo to pair with a draft, uploads a fresh set off the camera roll, and answers the K3a NSFW gating question before anything leaves for a non-adult surface. Voice register: working, plain on K3 warnings. ## Layout **iPhone — 3-col grid, drawer slides up from chat:** ``` ┌─────────────────────────────────────────────────┐ │ ◄ Chat Assets [+ Upload] │ 56pt top bar ├─────────────────────────────────────────────────┤ │ [OF] [X] [IG] [Tour] [persona:luxe] [+ filter] │ filter chips row · horizontal scroll ├─────────────────────────────────────────────────┤ │ ┌────┐ ┌────┐ ┌────┐ │ │ │ ▣ │ │ ▣ │ │ ▣ │ │ row 1 — thumbs w/ status corner badge │ │OF✓ │ │ NSFW│ │ ⟳ │ │ ✓=auto-approved · NSFW=K3a flag · ⟳=uploading │ └────┘ └────┘ └────┘ │ │ ┌────┐ ┌────┐ ┌────┐ │ │ │ ▣ │ │ ▣ │ │ ▣ │ │ │ └────┘ └────┘ └────┘ │ │ ┌────┐ ┌────┐ ┌────┐ │ │ │ ▣ │ │ ▣ │ │ ⚠ │ ← upload-failed │ │ └────┘ └────┘ └────┘ │ │ … │ infinite scroll · pagination at 60 ├─────────────────────────────────────────────────┤ │ 124 assets · 12 NSFW · 38 auto-approved │ footer stats └─────────────────────────────────────────────────┘ ``` **iPad — 5-col grid (per brief E):** same layout, wider, persistent left rail for filter chips, footer stats remain. **Asset-detail sheet (modal over drawer, tap a tile):** ``` ┌─────────────────────────────────────────────────┐ │ ◄ Library ⋯ │ ├─────────────────────────────────────────────────┤ │ ┌────────────────────────┐ │ │ │ │ │ hero thumb · 4:5 │ │ ▣ hero │ │ │ │ │ │ │ └────────────────────────┘ │ │ │ │ IMG_4421.heic · 3024×4032 · 4.2 MB │ filename + dims + size │ uploaded May 14 · MinIO ok │ │ │ │ Tags: [luxe] [Berlin] [indoor] [+ add] │ editable tag list │ Surfaces: [OF ✓] [X ✗] [IG ✗] [Tour ✓] │ per-surface auto-approval toggle │ NSFW: ● yes (auto-block from N3 brand sites) │ K3a flag · plain copy │ │ │ Variants (3) │ │ • original · 4.2 MB │ │ • of-feed-crop · 1.1 MB │ │ • tour-tease-blur · 0.8 MB │ │ [ + new variant ] │ │ │ │ [ Schedule on... ] [ Drag to chat ] │ primary actions └─────────────────────────────────────────────────┘ ``` ## Component table | Component | Notes | |---|---| | Top bar | "◄ Chat" returns to scroll position. "+ Upload" opens camera-roll picker + iOS share-sheet target (per B §B2). | | Filter chips row | Horizontal scroll · multi-select · per surface tag (OF/X/IG/Tryst/Tour/...), persona facet, tags array, auto-approved flag, NSFW status. | | Asset tile | 1:1 thumb · corner badge (auto-approved ✓ per surface · NSFW · uploading ⟳ · failed ⚠). Long-press enters multi-select. | | Footer stats | Live count · NSFW count · auto-approved count. Updates as filters narrow. | | Asset-detail sheet | Hero + metadata + editable tags + per-surface approval toggles + NSFW flag + variants list + actions. | | Schedule-on sheet | Per-surface checklist + date/time picker · routes to brief B §B1 calendar after confirm. | | Variants list | Per `content_asset_variants` rows · tap to preview · "+ new variant" routes to producer specialist. | ## States to render 1. **Empty** — "Nothing in the pantry yet. Tap + to add a few photos, or ask the producer to pull from your camera roll." (hearth touch on empty). 2. **Uploading** — tile shows ⟳ spinner over dimmed thumb · "3 of 7 uploading · 2.1 MB/s" strip above grid. 3. **Upload-failed** — tile shows ⚠ amber badge · tap → sheet with plain copy: "IMG_4421.heic didn't finish uploading. Retry or remove?" 4. **Typical grid** — populated 3-col, mix of approval states. 5. **Filter-narrowed** — chips active, footer reads "12 of 124 · luxe + OF". Empty filter result: "Nothing matches. Clear filters?" 6. **Asset-detail expanded** — sheet over drawer per layout above. 7. **Variants list active** — variants section expanded; tapping a variant previews it inline. 8. **Schedule-on sheet** — per-surface checklist + datetime · "Schedule on OF Tue 9pm? Producer will pick the variant." 9. **Auto-approval gate (K3a NSFW)** — toggling a surface ON for an NSFW asset surfaces inline plain-register confirm: "This is flagged NSFW. {surface} doesn't allow it. Force allow anyway?" with [Cancel] [Force allow once] [Always allow for {surface}]. 10. **MinIO offline** — banner top of drawer: "Storage unreachable. Thumbnails may not load; uploads are paused." Plain register. Existing metadata still browsable. 11. **Large-library pagination** — at 60+ assets, footer shows "Showing 60 of 412 · load more" or auto-loads on scroll-end · skeleton tiles while fetching. ## Filters Chip set, multi-select, persists per-session: - **Surface tag** — OF · X · IG · TikTok · Tryst · TS4Rent · Eros · Tour · ... (per O 24-surface roster). - **Persona facet** — luxe · girl-next-door · domme · ... (per `personas.facets` keys). - **Tags array** — free-form, autocompletes off existing tag set. - **Auto-approved flag** — show only assets pre-cleared for at least one surface. - **NSFW status** — yes / no / unset. ## Gestures - **Long-press tile** → enters multi-select mode · top bar swaps to "{n} selected · [Tag] [Schedule] [Delete]". - **Tap tile** → opens asset-detail sheet. - **Drag tile** → drag-into-chat (drop on the chat below the drawer's slide-down handle to attach to current draft). - **Swipe-down on drawer** → dismiss back to chat. - **Pull-to-refresh** → re-poll `GET /api/v1/content-assets`. - **Pinch on grid** → toggle 3-col ⇄ 2-col (iPhone) or 5-col ⇄ 3-col (iPad). ## In-the-wild copy **Empty state** (hearth): > Nothing in the pantry yet. Tap + to add a few photos, or ask the producer to pull from your camera roll. **K3a NSFW gate** (plain): > This is flagged NSFW. X doesn't allow it on the public timeline. Force allow once, always allow, or cancel? **Upload-failed retry** (plain): > IMG_4421.heic didn't finish uploading. Retry or remove? **Schedule-on confirm** (working): > Schedule on OF for Tue 9pm? Producer will pick the variant. ## Edge cases - **Asset originally tagged for OF dragged into a chat draft targeting X** — K3a warning fires inline in chat: "This one is flagged NSFW and tagged for OF. X will reject it. Pick a different variant, or force allow?" Routes to variants list with the tour-tease/blurred variants surfaced first. - **Variant generation in progress** — variants list shows a pending row "of-feed-crop · generating..." with the producer specialist's avatar; tile in the grid keeps the original until variant lands. - **Camera roll permission denied** — Upload button surfaces inline copy: "Cocotte can't see your camera roll. Settings → Photos → CocotteAI." Plain register. - **Duplicate detection** — re-uploading a hashed-match asset surfaces "Already in the library — open existing?" inline. - **K3c-2 KYC vault asset** — vault-flagged assets never appear in this drawer; carve-out per brief K §K3c-2. - **Asset deleted while detail sheet open** — sheet shows "This asset was removed. Close?" plain register. ## Related - [Brief B §B2](./B-drawers.brief.md) — drawer roster + inputs (`GET /api/v1/content-assets`, `POST /api/v1/content-assets` with MinIO presigned URL). - [Brief K §K3a](./K-safety-blocklist.brief.md) — NSFW cross-surface gating; the auto-approval gate is the asset-side enforcement point. - [Brief K §K3c-2](./K-safety-blocklist.brief.md) — KYC vault carve-out (vault assets never surface here). - [Brief R](./R-tours-events-hotels.brief.md) — tour-tease variants feed pre-tour announcements; "Tour" surface tag is set here. - [`specialist-content-onlyfans.contract.md`](./specialist-content-onlyfans.contract.md), [`specialist-content-social.contract.md`](./specialist-content-social.contract.md) — the specialists that consume picked assets + produce variants. - [`specialist-drawer.screen.md`](./specialist-drawer.screen.md) — structural pattern mirrored here.