247 lines
20 KiB
Markdown
247 lines
20 KiB
Markdown
# 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 docked Content Browser maps the standalone Penpot title row to the editor dock tab instead of
|
||
rendering a second title inside the panel. At wide widths it uses the Penpot 55 px single-row
|
||
content toolbar: navigation, import, folder creation, breadcrumbs, search, type filtering, and view
|
||
controls share one dense row. At 1400 px and wider, sort order and the grid thumbnail-size slider
|
||
also become first-class controls in that row; narrower wide and compact layouts keep those
|
||
lower-frequency controls in overflow. Sorting preserves selection, while thumbnail scaling is a
|
||
presentation-only change and is disabled in list view. Below the 1000 px responsive breakpoint,
|
||
the compact 88 px two-row
|
||
toolbar keeps search in the flexible center lane, Sources and Details become
|
||
explicit mutually exclusive drawers, Import/New remain on the breadcrumb row, and infrequent
|
||
operations move to overflow. The default compact view is the content grid; no hidden pane silently
|
||
consumes its width. Wide mode uses a 216 px Sources pane and a 290 px default Details pane; the
|
||
Details divider remains resizable and double-click restores 290 px.
|
||
|
||
Grid mode uses one continuous row-major stream for folders and assets. Folders retain navigation
|
||
and drop-target behavior but use the same bounded card contract as files; they never force
|
||
the first asset onto a new row. The 56 px content heading owns the folder name, sort summary, and
|
||
selection chip before that single card stream begins. Each card clips a single bounded name and a
|
||
separate asset-kind line; long or Unicode names expose their full value by tooltip instead of
|
||
wrapping across card bounds. Sources, that shared card presentation, and bounded Details rows live
|
||
in `editor_ui::content_browser`; production supplies real project state and handles the actions,
|
||
while the UI Gallery supplies fixtures to the same functions. Details paths and dependency
|
||
identities receive the exported 32 px two-line treatment; other scalar values stay one line.
|
||
|
||
The Content Browser 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 fixed footer reports catalog health, thumbnail/derived processing, and
|
||
the current selection without moving card labels. The content header reports folder/item count,
|
||
active sort, and a bounded selection chip. 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.
|
||
|
||
Successful 256 px previews are persisted beneath the managed, untracked
|
||
`assets/.thumbnails/v3/` cache. The artifact key hashes the typed thumbnail identity, renderer
|
||
version, source bytes, model bundle dependencies, resolved Material values, referenced textures,
|
||
and custom shader files. A restart decodes that small validated PNG instead of reloading a 4K
|
||
texture or re-rendering geometry. Catalog refreshes revalidate signatures, and retained prior
|
||
generations restore the last-good image even after a restart while a changed preview is rebuilt or
|
||
its broken source is diagnosed; corrupt/missing artifacts self-heal, retryable studio
|
||
failures receive one bounded automatic retry, and **Regenerate Thumbnail** explicitly evicts the
|
||
selected key. Publication is atomic, retains one prior generation until the replacement is valid,
|
||
and trims only old derived entries after the cache exceeds its 512 MiB soft limit.
|
||
|
||
The grid slider scales cards from 68×88 through the 116×136 default to 132×152. Only the preview
|
||
region scales: every size reserves the same 40 px identity footer, keeps the name and kind bounded,
|
||
and paints the category keyline inside the card bottom. Canonical 256 px thumbnail artifacts are
|
||
aspect-fitted as squares rather than stretched to the card's changing preview rectangle.
|
||
|
||
**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 cards use a clipped one-line identity plus a separate kind line, a top-right
|
||
grid-card status rail, and fixed-width list markers, so state never paints over or shifts an asset
|
||
name. Every registered asset category retains its theme-owned classification color as a narrow
|
||
keyline along the bottom of the complete card, including when a rendered thumbnail is ready. The
|
||
keyline never tints or otherwise alters preview content; folders remain neutral, while loading,
|
||
failure, dirty, and selection indicators remain independent states. The canonical category palette
|
||
is documented in the [editor design system](design-system.md#asset-category-colors). Material
|
||
Library cards consume the same authored-document status model.
|
||
|
||
## 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).
|