# Content Workspace

Blacksite treats the project `assets/` tree as a user-organized content workspace. Folders are for
organization and do not determine asset type. The stable asset registry ID is authoritative; a path
is mutable metadata that moves, validation, and packaging repair through the shared content catalog.

## Content boundaries

- User assets may live in any normal nested folder beneath `assets/`.
- External absolute linked assets are unsupported so projects remain portable and packageable.
- `.index`, `.trash`, thumbnails, recovery data, and derived artifacts are hidden and protected.
- Authored document schemas and typed registry records identify RON assets. Ambiguous or invalid
  documents report diagnostics instead of being guessed from their folder.

## Content Browser workflow

The Content Browser toolbar provides **New Folder**, **Rename**, **Duplicate**, **Cut**, **Copy**,
and **Paste**. The selected folder is the destination for paste and **File > Import Assets**. These
operations use the shared content transaction engine: it previews collisions, rejects managed or
symlink paths, stages filesystem and cached-reference changes together, preserves IDs on moves,
assigns new IDs to copies, and rolls back on failure. Successful move, rename, create-folder, copy,
duplicate, and authored-file creation transactions add a content-specific **Undo Content** entry.
Move/rename undo applies the inverse reference-safe transaction. Undoing newly created content
moves the unchanged result into transactional Trash, so it remains restorable instead of being
permanently deleted. Every undo is blocked if any affected file or folder has changed externally
since commit. Registry and stripped runtime-catalog publication are part of the same rollback
boundary.

Before create-folder, rename, move, copy, duplicate, paste, or drag-to-folder commits, **Review
Content Operation** lists every source/destination, affected registered-asset count, and each cached
RON document whose path hint will be rewritten. Collisions are shown in the same window and disable
commit. Read-only files, provider locks, and source-control conflicts are visible and block commit;
modified/untracked state remains visible but does not. Source and rewrite-document fingerprints are
captured for the review, so any external edit before confirmation also blocks the stale transaction.
Cancel restores the pre-review folder, selection, anchor, status, clipboard, and drag state; a
cancelled drag review clears only the stale drag gesture. Folder-scoped **Paste Into** and
**Import To** pin their destinations without navigating the browser.

Delete remains trash-first. **Trash** in the toolbar, **Open Trash** in a folder/empty-space context
menu, and the restore window expose complete deletion batches. A new batch records the original
paths and registry records before moving bytes under `assets/.trash/`. Model batches also carry
their generated static/animation manifests so deletion or guarded undo cannot leave orphan runtime
artifacts; restoring the batch restores those exact derived bytes with the source. Restore preserves
stable IDs and is blocked if a destination path, derived path, or registry ID is now occupied.
Legacy trash batches are shown when their original paths can be inferred, but have no historical
registry IDs to recover. A normal Move to Trash action also records the exact batch in Content Undo,
so Ctrl/Cmd+Z restores it when the batch fingerprint and destinations remain valid.
Before confirmation, the trash review enumerates external authored RON documents, model mappings,
project-default selection, and dependency records that will become unresolved; references inside
the same selected tree are excluded from that warning.

Selection follows desktop file-manager conventions in both grid and list views: click selects one,
Ctrl/Cmd-click toggles an item, Shift-click selects the visible range from the anchor, and
Ctrl/Cmd+A selects every visible result. Folders are first-class selectable and draggable items;
double-click opens them and clears the previous folder's selection so hidden content cannot be
changed accidentally. Starting a drag on an unselected item selects that item first, while dragging
one member of an existing multi-selection preserves the complete batch. Batch Cut, Copy, Duplicate,
drag-to-folder, and Move to Trash operate on the complete selection while removing nested duplicates
such as a selected folder and one of its children; the menu reports those children as covered by
the parent rather than mislabeling them as ignored. Changing search text, recursive scope, or the
kind filter clears the selection so a now-hidden item cannot receive a keyboard operation. Sorting
and switching grid/list view preserve selection because they do not hide results. Content shortcuts only take ownership while the
pointer is over the content pane: Ctrl/Cmd+C, X, V, D, A and Z, Ctrl/Cmd+Shift+N, F2, and Delete.
Ctrl/Cmd+Z invokes the most recent guarded Content Browser operation; it does not consume scene
history while the pointer is outside the content pane. The browser surface and its shortcuts are
disabled while any Content Browser review/modal or popup menu is open, so a confirmation click or
keystroke cannot also mutate the selection behind it. Cut/Paste keeps the exact requested basename; an occupied destination is
shown as a collision in **Review Content Operation** instead of being silently renamed.
Embedded mesh, material, texture, skeleton, and animation rows are authoring subassets rather than
filesystem entries; generic rename/clipboard/drag-to-folder/trash commands never reinterpret one as
its parent model file.

Right-clicking an unselected file, folder, or embedded model subasset makes it the primary selection
before opening its menu. Right-clicking one member of an existing multi-selection preserves the
complete batch. File menus expose type-specific actions plus Rename, Cut, Copy, Duplicate, and Move
to Trash. Folder menus add destination-scoped import, paste, and creation actions. Right-clicking
unused content space clears any old selection and opens workspace actions for **Import Here**,
**Import To**, **Create Material Here**, **Create Materials From Folder**, **New Folder**, **Paste**,
**Select All**, **Refresh**, and grid/list view selection. New Materials are authored directly in the
current folder; the Material Library's create button retains `assets/materials/` as its convenient
default.
Double-clicking a scene opens it, a model toggles its embedded contents, and other asset types reveal
their editable Details. The footer and Details pane summarize multi-selections; single-selection
Details remains the authoritative editor for import and material settings. Drag the Details divider
to resize it for long slot/shader forms; double-click the divider to restore its default width.
Texture Details uses the same pane for semantic/color-space intent, mip generation, UASTC or
uncompressed KTX2 output, resize limits, sampler settings, and normal-Y conversion. Changes become
an **UNSAVED** Texture document immediately; explicit Save publishes the settings and queues
affected-only derived processing without rewriting the editable source image.

Thumbnail cache keys are typed by asset category. Texture cards may use direct image loads, while
model cards always queue the offscreen geometry studio—even when a glTF has a base-color texture.
Expanded mesh rows request their exact mesh-subasset render, and source materials use a separate
material-sphere key. Missing dependencies produce an explicit failed thumbnail; a model or mesh
card never substitutes an albedo texture. Replacing, retrying, or invalidating a thumbnail also
unregisters its last Egui image handle, so repeated catalog refreshes release the prior GPU image
instead of accumulating hidden render targets.

**Import Here** pins the current/selected folder before the native picker opens. **Import To...**
first opens a project-folder chooser without navigating away from the current browser location.
After files are chosen, a second review lists every source dependency and exact final project path;
collisions disable commit and cancel publishes nothing. Source/dependency fingerprints and target
absence are checked again at commit so external changes after review cannot overwrite project data.

External FBX and textual glTF imports preserve relative texture/buffer layouts as one staged bundle.
The complete multi-source picker batch is preflighted before mutation: existing targets and two
sources mapping to one target are reported as collisions, and any publication failure removes all
new files and directories from that batch. Missing dependencies, traversal, and symlink escapes
also fail before the destination changes. GLB is self-contained. Files already under `assets/` are
shown as **Adopt in place** in the review and processed without copying or type-folder sorting.

Folder/file Move preserves registry IDs and rewrites cached paths in project documents, model
defaults, the runtime catalog, and generated manifests. Copy/Duplicate assigns new IDs, remaps only
references internal to the copied tree, and leaves the originals plus external references
untouched. Copied models discard inherited generated-manifest paths so normal processing publishes
fresh artifacts under the copied model ID.
Out-of-editor moves reconcile imported assets only when one missing record has the matching kind
and source fingerprint. If the watcher finds one new file matching multiple missing records, it
opens **Resolve Ambiguous External Moves** and leaves registry/manifests unchanged. Each row must
either preserve exactly one candidate ID or intentionally **Register as New Asset**; one old ID
cannot be assigned to two new paths. **Review Later** leaves a **Resolve Moves** toolbar action, and
Apply rechecks the registry, files, and candidate set before publishing. Headless processing fails
with the same candidate paths until the choice is completed in the editor; neither path silently
replaces stable identity.

Imported static and skinned model Details expose stable material slots. Each slot selects its source
material, a project Material/Instance, or **Default**; bulk Source and Default actions are
available. Default intentionally skips the imported source and resolves through the project
fallback, then immutable DefaultGrid. Reimport reconciles by stable slot ID and retains removed project assignments as explicit
orphans. The displayed source health makes a missing imported material distinct from an intentional
Default selection. **Locate** reveals the referenced project Material/Instance, while **Clear**
returns an active model slot to Source. Orphan rows provide **Locate** and **Clear Orphan** so a
removed source slot can be reviewed and resolved explicitly before applying the import settings.

The former whole-model Source Materials/Authoring Override switch is migration input only. Explicit
processing expands legacy Authoring Override into a Default selection for each stable slot, resets
the legacy field, and publishes only per-slot selections to current registry/manifests. Validation
requires an FBX texture bundle while any slot uses Source; an all-Project/Default model keeps missing
source textures visible as informational provenance instead of a release blocker.

For glTF, GLB, and FBX, **Extract Editable Materials Here** opens a conversion review containing
every source material, converted metallic/roughness and render state, detected external texture
channels, and the editable final project paths. Embedded texture payloads remain source-owned; the
review still converts supported scalar PBR values. Confirmed entries become normal
`*.material.ron` assets and every model draw slot using that source material is changed to a
`Project(...)` selection in the same rollback-capable content transaction. The generated model
manifest is refreshed immediately so editor and packaged runtime bindings agree. A first
extraction uses a collision-free path. On a later extraction into the same folder, matching source
path and stable source-slot provenance opens an explicit diff: choose **Apply Reviewed Update** to
replace the edited Material, or **Create New Copy** to preserve it as a separate revision. Apply is
guarded by the reviewed file fingerprint, participates in transaction rollback, and is blocked if
the file changes before publication; mismatched provenance can never overwrite an unrelated
Material. Each extracted Material records read-only source path, stable source-slot identity, and
source-content fingerprint provenance in Details.

For loose texture sets, right-click their folder or unused space inside it and choose **Create
Materials From Folder**. The review window detects common PBR suffixes such as `albedo`,
`basecolor`, `diff`, `normal`, `nor_gl`, `roughness`, `metallic`, `ao`, `orm`/`arm`, `height`, and
`displacement`. Every row shows its confidence and stays editable. Change a row's **Target** to
merge it into another material or split it into a new material; change **Role** to correct a guess.
Unresolved images remain visible and start unchecked. Creation publishes every selected group as
one atomic content transaction and chooses a suffixed filename instead of overwriting an existing
Material. Packed ORM/ARM maps use R=occlusion, G=roughness, and B=metallic.

Project Settings > Content selects an optional project fallback Material/Instance. Clear restores
the immutable **Default Grid — Engine Built-in**. DefaultGrid is world-space and UV-independent, so
broken or unassigned meshes stay visible.

Material, Material Instance, model-import, Texture-import, and project-default edits live in an
editor-owned authored-document store until explicitly saved. **UNSAVED** is editor state and is
shown separately from Git state. `Ctrl+S` saves the last edited context; `Ctrl+Shift+S` and
**File > Save All** save every dirty scene, asset document, and project setting. Selection changes
never discard a dirty asset. Source publication is guarded and atomic, then affected-only derived
processing runs in the background while the previous valid artifact remains active.
Material scalar/color/render-state saves do not show `PROCESSING`; only packed-map binding changes
do. Content Browser and Material Library cards share clipped two-line labels, a top-right grid-card
status rail, and fixed-width list markers, so state never paints over or shifts an asset name.

## Headless processing

Run `cargo process-assets --project <root>` to deterministically refresh registry v3, normalized
static/animation model manifests, processed Texture artifacts, canonical Material ARM maps, and
`assets/content.catalog.ron` without a GPU. All planned artifacts and both catalogs publish under
one rollback boundary. Use `--check` in CI for a strictly read-only comparison that fails on any
stale artifact, and `--json` for machine-readable diagnostics. Texture and packed-Material keys are
resolved before image decode; an existing content-addressed artifact is reused, so an unchanged
check validates source hashes and catalog references without recompressing images.
Editor startup, the toolbar/empty-space **Refresh** action, and the shared watcher all use the same
processor. Saved authored documents enqueue revisioned, coalesced jobs rather than running the
whole processor on the UI frame. The watcher debounces and deduplicates user-content paths, ignores
managed/generated publication noise, incrementally rescans the same registry/catalog model,
refreshes selection-safe browser state, and invalidates thumbnail caches. Selection-only UI changes
never fingerprint or republish content. Editor transactions suppress their filesystem events until commit. Failures remain visible
in the editor status line. When an external deletion leaves authored references unresolved, the
status includes their count and affected document paths. Thumbnail rendering remains editor-owned.
Ambiguous move repair is rediscovered on startup and manual refresh, so choosing **Review Later**
cannot lose the repair entry across an editor restart.

The permanent storage, material precedence, and fallback decisions are in
[ADR 0045](../adr/0045-content-workspace-and-material-fallback-contract.md).
