Enables the existing Sharp pipeline for all uploads — WebP conversion,
auto-orient, 2560px cap, content-hash filenames. Upload toast now
shows compression savings percentage.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a "3D Models" bucket (path: models, accept: .glb/.gltf/.usdz)
to the cases scope. Previously 3D files were mixed into the Media
bucket at the root. Now they have their own tab with proper file
type hints and purple accent color matching the AR viewer UI.
- cases/media bucket no longer lists .glb/.gltf/.usdz in accept
- New bucketHint for models bucket warns on non-3D file uploads
- Network page 3D tab updated with purple accent + "3D Models"
button label pointing to the dedicated bucket
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The unified bucket browser graduated from "acceptable" to "actually
useful for bulk work". Editors can now manage dozens of files in a
single session without dragging each one through a modal.
NEW FEATURES (frontend)
1. BULK SELECTION
- Click on a file when nothing's selected → opens it as before.
- Click on the corner checkbox, or click the file once selection is
active → toggle that one in/out.
- Shift-click → range select between last anchor and current item.
- Cmd/Ctrl-click → toggle without affecting others.
- "Select all" toggle in the toolbar respects the search filter.
2. BULK ACTIONS TOOLBAR
When at least one file is selected the toolbar morphs into:
[N selected] [Delete] [Move to: Videos | Renders | …]
Delete fires the new bulk DELETE endpoint with filePaths[], shows
a single toast for the whole batch + per-file failure breakdown.
Move iterates PATCH /api/assets per file (sequential, with a 'Moving…'
indicator in the bucket helper bar).
3. DRAG TO MOVE BETWEEN BUCKETS
Drag any file (or the whole selection if you started the drag from
a selected file) onto another bucket tab. The tab highlights green
with 'drop to move' while you hover. Drop fires the same per-file
PATCH flow. No dialog, no friction.
4. RENAME IN PLACE
Double-click a filename (in either grid or list view) → input opens
in place. Enter saves, Escape cancels, blur saves. Sanitizes to
safe characters. PATCH endpoint refuses to overwrite an existing
file (returns 409, surfaced as a toast).
5. KEYBOARD HINT FOOTER
Bottom-of-modal cheat sheet: Click / ⇧Click / ⌘Click / 2× click /
drag onto another tab. So new editors don't have to discover the
power-user features.
NEW BACKEND (src/app/api/assets/route.ts)
PATCH method
{ scope, slug, fromPath, toPath } → fs.renameSync.
Used for both rename (same dir, new name) and move (different bucket).
Refuses to overwrite an existing destination (409 conflict).
Creates intermediate folders if needed.
DELETE extended
Now accepts either { filePath: "x" } or { filePaths: ["a", "b"] }.
Bulk path deletes one-by-one and returns per-file success/failure
so the UI can show a precise toast.
REVIEWED FOR REGRESSIONS
- Single-file API still works — old { filePath } DELETE shape preserved.
- The 4 inline AssetManager call sites (network, news, applications,
parts) use AssetBucketBrowser via the alias added in the previous
commit; their integration is unchanged. Same props, same onSelect
callback shape.
- Toast/Confirm calls go through the existing HqUiProvider mounted in
hq-command/layout.tsx — no extra wiring.
Every HQ Command panel had its own ~210-line AssetManager component
copy-pasted into the page file. Same UI, same API, four diverging
implementations — and no consistent metaphor for "where does this file
go?". Editors had to think about subfolder names (videos/, renders/)
that the front-end implicitly expects.
ONE COMPONENT. CLEAR BUCKETS. SAME PATHS.
src/components/hq/AssetBucketBrowser.tsx — the single picker. Takes
scope + slug, shows bucket tabs (Media / Videos / Renders / etc.) and
maps each to the on-disk path the public site already reads from:
cases: Media (root) | Videos (/videos) | Renders (/renders)
applications: Media (root) | Videos (/videos) | Renders (/renders)
news: Media (root)
parts: Media (root) | Renders (/renders)
footage: Hero Reel (root)
branding: Brand Assets (root)
Drop a file into the Videos tab → POSTs to /api/assets with path=videos
→ lands at /public/{scope}/{slug}/videos/<file> — exactly where
ApplicationClient.tsx and CaseStudyModal.tsx already look. Zero
front-end path changes, zero data migration.
UX upgrades the editor sees:
- Tabs make the bucket layout discoverable instead of buried in folder
navigation. Each tab has its own description and accent colour.
- Soft-warning hints flag obvious mismatches ("Videos bucket usually
holds .mp4 — this file may not display correctly") without blocking
the upload.
- Search + grid/list views.
- Hover actions per file: copy URL, delete (with confirm).
- Persistent on-screen path (/{scope}/{slug}/{bucket}) so editors can
always see the canonical location.
REPLACEMENT (4 page files)
- network/page.tsx: 719 → 513 lines (-206) — direct alias
- news/page.tsx: 484 → 313 lines (-171) — direct alias
- applications/page.tsx: 555 → 433 lines (-122) — adapter wraps the
picker's onSelect into the markdown-syntax onInsert callback this
panel uses. No call-site changes.
- parts/page.tsx: 413 → 320 lines (-93) — direct alias
Net: -592 lines of duplicated UI, +560 lines of single shared component.
Future bucket-layout changes live in one file instead of four.
NO PATH/API CHANGES — /api/assets is unchanged. The on-disk layout is
unchanged. Existing assets keep rendering on the public site. Existing
DB rows (mediaFileName, galleryJson, videosJson, rendersJson) are
unaffected because we never moved files.