# M2 Content Workspace Native Evaluation

> **Evidence record — not current product guidance.** Use the [documentation index](../../../README.md) for current behavior and architecture.

**Date:** 2026-07-14

**Milestone:** [M2 - Content workspace and asset pipeline](https://git.spacetrainclubhouse.com/Falling-Metal-Interactive/Blacksite/milestone/12)

**Epic:** [Gitea #59](https://git.spacetrainclubhouse.com/Falling-Metal-Interactive/Blacksite/issues/59)

**Base commit:** `931f561e7ff1489f897e22ebb3cc7545fcfef702`

**Implementation revision:** Candidate tree passed all gates; exact commit assigned at publication

**Release-candidate commit:** Pending publication commit

**Native interaction acceptance:** **Pass for the recorded content workflows; current Inspector UI acceptance remains in progress**

**Pinned Penpot acceptance targets:** [export index](penpot/README.md)

**Current UI comparison record:** [UI gallery delta matrix](ui-gallery-delta-matrix.md)

This feature-level record covers the Content Browser interaction pass added during M2 completion.
It does not close the milestone or replace the exact-commit, clean-checkout, import/reimport,
forward/Solari, packaging, and independent signoff gates in the M2 plan.

## Native Protocol

The normal debug editor was rebuilt with `cargo build -p editor --bin editor`, launched as
`LD_LIBRARY_PATH=target/debug/deps target/debug/editor --project .`, and exercised in native
Wayland windows at 3426x1384 and 1440x3440. The project stayed live throughout each workflow and
was stopped without saving the temporary placement scene.

| Workflow | Result | Evidence |
|----------|--------|----------|
| Managed storage visibility | Pass | `assets/.index`, `.trash`, `.thumbnails`, `.import-cache`, and generated artifact directories were absent from the tree and content grid; ordinary content folders remained visible. |
| Empty workspace context | Pass | Right-clicking visible unused content space cleared the selected model and opened Import Here, PBR grouping, Create Material Here, New Folder, Open Trash, Paste, Undo, Select All, Refresh, and view actions. |
| Multi-selection | Pass | Ctrl-click selected `assets/build_profiles` and `assets/levels` together; both tiles, the two-item footer, and the multi-selection Details summary stayed synchronized. |
| Batch context menu | Pass | Right-clicking one selected folder preserved both selections and exposed Cut, Copy, Duplicate, and Move Selection To Trash. |
| Item context menu | Pass | Right-clicking the Poly Haven desk model selected it and exposed model actions plus Rename, Cut, Copy, Duplicate, and Move To Trash. |
| Details resizing | Pass | Dragging the Content Browser divider expanded the Details pane; its hover affordance described drag resize and double-click reset. |
| Background/item priority | Pass | The workspace background menu remained available without intercepting folder/model clicks or Ctrl multi-selection. |
| Destination-first bundled import | Pass | Created `assets/Props/Office/` through reviewed folder transactions, chose **Import Here** from genuinely empty space, and imported the Poly Haven `metal_office_desk_2k.gltf` directly there with its `.bin` and three relative textures. No type-specific routing occurred. |
| Import To and final-path review | Pass | Chose `assets/Furniture/Office` without navigating away from the root browser view, opened the native picker, selected `/tmp/m2-live.png`, and confirmed the second review showed the pinned destination plus exact `assets/Furniture/Office/m2-live.png` target. Cancel closed the review and a filesystem assertion confirmed that no target was published. |
| Destination-first skeletal import and placement | Pass | Chose **Import Here** in `assets/Furniture/Office/`, imported `blacksite-m2-robot-expressive.glb` directly there, inspected its stable draw-slot IDs, and placed it as a live `SkinnedMesh` actor with its skeleton hierarchy intact. Registry ID `5b4640dd-fc17-4703-b9ea-e4eb49a8c7fc` and both generated manifests survived the final headless refresh. |
| Editable material extraction | Pass | The review exposed the converted metallic/roughness values, base/normal/packed texture channels, double-sided state, destination path, and stable source provenance before one transactional publication. |
| Re-extraction diff/apply safety | Pass after corrective implementation | Native review found the provenance-matched Material, exposed a source-revision diff, and kept Extract disabled until **Apply Reviewed Update** or **Create New Copy** was chosen. Apply preserved Material ID `647a63d4-64c6-4431-ba46-9efdd515cd8f`, mapped all eight active slots, and **Undo Content** restored the exact prior Material bytes. Pipeline tests additionally prove external-edit rejection and exact-byte rollback when dependent publication fails; editor tests prove undecided and stale reviews cannot publish. |
| Extract-and-map contract | Pass after corrective implementation | Native QA found that extraction initially created the Material without changing model defaults. The transaction now registers the Material and maps all nine draw-slot IDs using source material 0 before publishing either registry document; the generated static-mesh manifest contains the same nine `Project(...)` selections. |
| Guarded create/write undo | Pass | **Undo Content** moved the first extracted Material into one Trash batch while leaving the model bundle untouched. The corrected extraction was then rerun against the same fixture. |
| Forward/Solari DefaultGrid visibility | Pass | The scene's UV-independent neutral grid remained visible in `Lit / Forward` and after applying `GiMode::Solari` in memory; `assets/project.ron` remained configured as `Forward`. |
| Broken project fallback safety | Pass | Selected `new_material_2` as the project fallback, moved that Material to Trash through the reviewed transaction, and confirmed affected primitives and the placed skinned model remained visible with DefaultGrid. Undo restored the Material; the project fallback was then cleared back to the built-in default. |
| Skinned scene-slot override | Pass | The Inspector exposed every imported draw slot and its stable ID. Assigning `new_material_2` to the first slot changed only that scene slot to an explicit project Material while the remaining slots continued to report imported-source fallback. |
| Restart persistence and deterministic refresh | Pass | After a clean close/restart, the registry, runtime catalog, generated static-mesh manifest, and extracted Material retained identical SHA-256 hashes. Registry and manifest each retained nine project slot selections. |
| Editor/headless classification parity | Pass after corrective implementation | `process-assets --check` found that browser sync had collapsed a schema-classified Material Instance back to Material. Browser registry sync now reclassifies the authored file while retaining the unified visible Material family. A steady-state editor restart preserved both document hashes and the following headless check reported zero changes. |
| Reimport and orphan reconciliation | Pass after corrective implementation | Removing source node 8 from the QA glTF and choosing **Reimport** retained eight active `Project(...)` mappings by stable slot ID and exposed the removed mapping as one explicit orphan. **Locate** selected its extracted Material; **Clear Orphan** plus **Apply** published eight active mappings and zero orphans to registry and runtime manifests. The source bundle under `source_assets/` was not modified. |
| Move/rename stable identity | Pass after corrective implementation | A reviewed Cut/Paste moved `assets/Props/Office/` to `assets/Furniture/Office/` while preserving model ID `36c51ff3-29ae-4ab1-aaca-f90f57e27966` and Material ID `647a63d4-64c6-4431-ba46-9efdd515cd8f`. Native QA found stale nested `MaterialRef.source_path` caches on the first attempt; guarded Undo restored the prior bytes, the transaction rewrite was corrected, and the repeated move updated registry, catalog, both model manifests, Material provenance, and texture paths. Reimport at the destination retained all eight mappings. |
| Duplicate identity and internal references | Pass after corrective implementation | Native **Duplicate** assigned fresh IDs to the copied model, Material, and textures; every copied model slot referenced the copied Material and folder, while the original records remained unchanged. Copied models discarded inherited manifest paths and the watcher regenerated static/animation manifests under the copied model ID. |
| Duplicate guarded undo and derived cleanup | Pass after corrective implementation | Native Undo initially exposed orphan generated manifests after removing the copied tree. Trash manifest schema v2 now includes model static/animation artifacts. Rebuilt native Duplicate -> Undo moved the copy plus both generated manifests into one restorable Trash batch, removed copied registry/catalog records, and preserved the original tree and artifacts. |

## Automated and Packaged Acceptance

The same local implementation passed the complete workspace/all-feature test suite, strict
all-target/all-feature Clippy with warnings denied, formatting, both project validators, and a
steady-state headless content check over 78 discovered assets. `validate-levels` audited 152
dependencies with zero blockers; `validate-samples` audited all five samples and 157 dependencies
with zero blockers. Both validators reported the expected eight non-blocking import/platform
findings. The final `process-assets --check` reported zero registry, catalog, model, texture, or
material-artifact changes. The QA package published 125 runtime files (87 written and 38 reused)
with no stale packaged files.

Focused Content Browser regressions also cover guarded undo for created folders and copied content:
an unchanged created result is targeted as one restorable Trash batch, while any external tree
change blocks the undo and preserves the workspace. Ordinary Move to Trash records its exact batch
for guarded Ctrl/Cmd+Z restoration as well. Model trash/restore additionally verifies byte-exact
static/animation manifest removal and restoration, and copy coverage verifies fresh IDs plus
internal-only reference remapping.

The final transaction audit adds focused coverage for exact browser/scene status restoration when
a content review is cancelled, stale-drag cleanup, registry/runtime-catalog drift refusal during an
import review, and reviewed-import rollback that removes newly published bytes without deleting an
in-project adopted asset. Destructive reference discovery reports external authored documents,
project defaults, model mappings, and dependency records while excluding references removed in the
same selected tree. Ambiguous out-of-editor fingerprint moves now leave registry/manifests
unchanged and open an explicit identity-repair review in the editor; the user can preserve one
candidate ID or intentionally register the file as new. Headless processing reports both candidate
paths until that choice is published instead of silently allocating a replacement ID.
Focused repair coverage proves preserved-ID continuity through the next shared scan, intentional
new-ID behavior, duplicate-ID rejection, and one commit that publishes the world registry, registry
file, and stripped runtime catalog together. Content shortcuts are also suppressed while a browser
review or popup owns input, and Cut/Paste keeps the requested name so collisions reach review.
The follow-up QoL audit made right-click targeting consistent for files, folders, and embedded
subassets; distinguishes nested selections covered by a selected parent from genuinely unaffected
built-ins/subassets; and clears selection when search/scope/type filters would hide it. Browser
controls are disabled behind reviews. Startup, manual Refresh, and watcher processing now share the
same pipeline, rediscover unresolved move repairs after restart, and selection-only changes no
longer trigger whole-catalog fingerprint or registry work.
A rebuilt native editor then mapped successfully on the live Wayland session with the shared
startup refresh active. The Details drag strip now uses one stable explicit egui ID across layout
passes; a final bounded launch produced no resize-handle ID warning, and the following headless
check again reported zero content changes.

The architecture audit also moved destination-first import planning/publication, source
fingerprints, static-mesh normalization, animation normalization, and combined model-artifact
publication into `content_pipeline`. The `process-assets` feature's dependency tree no longer
contains the editor crate; the editor retains compatibility re-exports for its UI consumers.

The extraction correction adds focused coverage for dependent registry edits that can see newly
registered asset IDs, byte-restoring rollback when that dependent edit fails, and source-index
mapping across every draw slot that uses an extracted source material. Re-extraction coverage also
requires an explicit decision for provenance matches, rejects stale external edits, preserves the
existing asset ID on Apply, and restores exact prior bytes if dependent publication fails.

Property-block coverage verifies owner/slot-local Standard handles, stable handle counts across
repeated application, strongest precedence above an existing Material Instance on the Surface
path, and whole-block rejection for invalid Standard/schema/type/range/texture input. Invalid
blocks leave the resolved base handle visible and emit one deduplicated diagnostic.

The reviewed promotion transaction has focused coverage for a direct Material and an existing
Material Instance base, exact-slot assignment, one scene-history edit, cancel without mutation,
source/slot/schema/block/target conflicts after review, invalid runtime input, and restoration of
the exact prior file, registry, and runtime-catalog bytes when the scene edit fails. The import
review likewise has focused coverage for non-mutating planning, collision refusal, post-review
source/registry/runtime-catalog changes, complete textual-glTF dependency fingerprints, artifact
processing failure with whole-bundle rollback, adopted-file preservation, and an explicitly pinned
Import To destination that does not change the visible browser location.

The final native promotion scenario used the editor-only BRP reflection boundary to inject a
two-parameter `MaterialPropertyBlock` into Ground's exact `slot:primitive:surface`. The Inspector
reviewed and published one sparse direct-base Material Instance, assigned it to only that primitive
slot through one history edit, and removed the runtime block only after publication succeeded.
BRP readback proved the exact Material Instance reference and absence of the runtime component;
Undo restored the original Pebble Bricks slot. The temporary instance, registry, catalog, scene,
and Pebble fixture were then restored to their pre-scenario bytes. Runtime blocks remain absent
from Add Component and scene/prefab persistence; no QA-only authoring component was added.

The final moved/reimported QA fixture hashes were:

- registry: `e1eb425c2d52be06d7db840df1f14f1d3a96daf2c1876dfc025d861bf0d32b74`
- runtime catalog: `cc296049b656d52e3159cca2e11f52bc4bdef0c922fb0dc00fa5d33f4e3748e7`
- desk static-mesh manifest: `6c69a9f9502fce36cb88e82a7e2e2074e6279ca6da4d49e3e36c1ede35c0577f`
- desk animation manifest: `8a0632bd8b6ea4a5d117214ea168f4020f9399c92bb9da3d7cba1ad371b9e4e8`
- extracted desk Material: `70e498f596d37da5735f5e0d0b1f3834442cd1aefa698232a640fec9d9e422f3`
- skeletal static-mesh manifest: `ee9168b123d89ff57b312a35e54a3a8d5bf5b9dae3ed98e79c7bc24dcb6d6454`
- skeletal animation manifest: `7c14b6ad016db5c09af7f56f50492a05f9bdd89b1bb672e9886bc9a3447e2130`

The latest 2026-07-14 rerun of `cargo package-project --profile development` wrote zero changed
files and reused 74 unchanged files. The packaged
`assets/content.catalog.ron` was byte-identical to the source runtime catalog with SHA-256
`cc296049b656d52e3159cca2e11f52bc4bdef0c922fb0dc00fa5d33f4e3748e7`. A bounded native package
launch hydrated the authored scene and visibly rendered the world-space DefaultGrid across UV-less
primitives and the floor. The corrective binary also mapped a live 3426x1384 Hyprland window as
class `bevy-fps-foundation`, title `Bevy FPS Foundation`, before the bounded QA process stopped.

## Screenshot Captures

The native PNGs are retained locally pending upload as Gitea issue attachments. Repository image
copies are intentionally not required by the evaluation policy.

| Capture | Dimensions | SHA-256 |
|---------|------------|---------|
| `blacksite-m2-managed-hidden.png` | 3426x1384 RGB PNG | `97bb710d569039b54218a556865f366cff3bda2ae6ce3afe4a1933db8e58c36b` |
| `blacksite-m2-empty-clears-selection.png` | 3426x1384 RGB PNG | `e162c865202717cf36eec7a37c3f95a4cc4c3c55dcc5d48243b00fe47be0d136` |
| `blacksite-m2-multiselect-fixed-2.png` | 3426x1384 RGB PNG | `729ae5d2b10ca2d8acdd249f19c35f5e6f3a57d9dbb9550f80c371b703336868` |
| `blacksite-m2-multiselect-context.png` | 3426x1384 RGB PNG | `88232360d592a1e4436e86a17fe1356ea10d7766cfbdb98eb6323767cbc16fe6` |
| `blacksite-m2-item-context.png` | 3426x1384 RGB PNG | `6f01f23ea7ad65dffecd0d0e51e57b6a1fcf26cf06cfe6f536f8376557a6649b` |
| `blacksite-m2-details-resized.png` | 3426x1384 RGB PNG | `407ee7779367f83ca2a9c5d6f4a5803584609e319d881265f9544b194fbd0296` |
| `blacksite-m2-office-empty-context.png` | 3426x1384 RGB PNG | `a4004292f70b8a07f6c0d24e8bc47c1716e7de33b4b627a67c4198afe76a00a3` |
| `blacksite-m2-imported-model-details.png` | 3426x1384 RGB PNG | `cf68b41293b823945fdd17e9752d3733f8d38144504f5fdbfd9be7f793ce0205` |
| `blacksite-m2-extraction-review.png` | 3426x1384 RGB PNG | `f4d567d3353536ca1492d48fe59f72ff5b3e4b37c095ef27eaed51347c6a67ec` |
| `blacksite-m2-extraction-transaction.png` | 3426x1384 RGB PNG | `99944392580a88ec14868dbf5a422bacc89ca3b8d8d0750311f429e65250aef6` |
| `blacksite-m2-details-wide-mapped.png` | 3426x1384 RGB PNG | `6aee075b4006909cde1625d5afd42a8be93b9323de93156bd8a55d27ef07e678` |
| `blacksite-m2-defaultgrid-solari.png` | 3426x1384 RGB PNG | `9f23685f6354029ce772878a3f080c7efb045ccc73fce6c19f8fe7802e378ee8` |
| `blacksite-m2-orphan-controls.png` | 3426x1384 RGB PNG | `afe8f07d92c00ac1294b021682a66ea623b3c22158598119d2617f2e0b1dae72` |
| `blacksite-m2-orphan-locate.png` | 3426x1384 RGB PNG | `d7c841747e1a712da80537846699baa8b2f83f91a65ddc298d1d3e665f2e74b7` |
| `blacksite-m2-orphan-applied.png` | 3426x1384 RGB PNG | `c979981fba5b30692c3f858fd349b582bb204b5720702bd684f3721ed5a76350` |
| `blacksite-m2-root-empty-context.png` | 3426x1384 RGB PNG | `960231dc8661f7b012abf7ddf0cc97ce42548e789e911895e12b8b800b29153d` |
| `blacksite-m2-office-move-review-fixed.png` | 3426x1384 RGB PNG | `aaa06af27049858f2c691b9c251197a19a5330919048b59b2c313d06d2bf4471` |
| `blacksite-m2-office-moved-fixed.png` | 3426x1384 RGB PNG | `0cfdd6206d56355583c55e483959c8c154223acf5142015f9f2329da54baf5ca` |
| `blacksite-m2-moved-model-reimported.png` | 3426x1384 RGB PNG | `a6420434c25f72af94f1f4bdb22c8686bd840aad5fe114c192bdefc6db020aec` |
| `blacksite-m2-duplicate-review.png` | 3426x1384 RGB PNG | `f0fae051ca8c807540779de662a5381b3e9f1ac3faaf1cd5412bb7f1cb2835b0` |
| `blacksite-m2-duplicate-committed.png` | 3426x1384 RGB PNG | `3e3b0fd38f0374c47598307d93f5238028d269753742f4b296536f0fe886c1a5` |
| `blacksite-m2-duplicate-undo-clean-editor.png` | 3426x1384 RGB PNG | `45aaa55e2069de66d9eb86f4157fd143d92afa2de07aaefb0bb2cc960958401a` |
| `blacksite-m2-packaged-live-window.png` | 3426x1384 RGB PNG | `de8c19b2dbc2b488e290b5e4d1bd3e1c279388374a81e25a5684dd2454f58ee2` |
| `blacksite-m2-reextract-modal.png` | 1440x3440 RGB PNG | `b7139a3b6da88755be1c53ef9921450449f207a3082248efd8199bd8abdea515` |
| `blacksite-m2-reextract-selected.png` | 1440x3440 RGB PNG | `bd22912ff3d2e4bfdab6469909344a0b0e94f8309665ad876e98e43fe7c80563` |
| `blacksite-m2-reextract-applied.png` | 1440x3440 RGB PNG | `007164ae995c64f048aac638e20664fc173769945691afc3e6a0a6fbf4704380` |
| `blacksite-m2-reextract-undo.png` | 1440x3440 RGB PNG | `3f95eec702d4df6a55d88132c82282311d96d3bca15e2a8ebe11d06ee408461b` |
| `m2-skeletal-import-review.png` | 1440x3440 RGB PNG | `a08dc1d17848d3089d601c8b31c697c5f22b36dda625baf5fa8f52ee7dad2ea2` |
| `m2-skeletal-selected.png` | 1440x3440 RGB PNG | `2867517356b63a1b7627d5d069e7b45150325065ef247ff20a06c00b53c79581` |
| `m2-skeletal-placed.png` | 1440x3440 RGB PNG | `1936dfcd84d9a6e6cf8f293f71d8a4cb89d1761ad9ee982954ad25682cfd999d` |
| `m2-project-default-selected.png` | 1440x3440 RGB PNG | `ee3d5385ccd7af1d099a042fb02400e1e193374ca475a28b3d7f55d483d64547` |
| `m2-project-default-broken-fallback.png` | 1440x3440 RGB PNG | `5efb715a619456afcbee808d13f7b4c2cf24bb975f6b69671401bba1fed07ba0` |
| `m2-inspector-open.png` | 1440x3440 RGB PNG | `7ae9d456d0924ab50012ecbefb8e42847ccb141793ce89487c0fd2fbd2b8307e` |
| `m2-skeletal-scene-override.png` | 1440x3440 RGB PNG | `3c7ec6fce6a09f9e46402aa64dda77c5508703a11149b30297cee80d9e7907af` |
| `m2-import-to-selected.png` | 1440x3440 RGB PNG | `d9cb38de6fca185dab6887324163698b2ac036e8f5eb19f16fb65415172f9605` |
| `m2-import-review.png` | 1440x3440 RGB PNG | `631e1ac1d3c63d72d7c930c3b4789584e15a178f03425fd972df82da51dcfa05` |
| `property-block-promotion-20260717T130300Z.png` | 3426x1384 RGB PNG | `69cd668618206b1cee25142521127882cc785031e8e7cd21684c1eab7c97befa` |

## Remaining Release Use

The complete deterministic candidate gate passes on the local candidate tree. After assigning its
exact commit, confirm the clean-tree evidence digest, publish the native/candidate evidence on
Gitea, and finish with issue/milestone readback. The local candidate tree covers static and skeletal
import, edit/reimport/orphan resolution, broken project fallback, scene-slot precedence, reviewed
Import To cancellation, runtime-injected property-block promotion, responsive Penpot material UI,
dirty/save/restart behavior, and packaged runtime behavior. Publication readback—not another
destructive native content-authoring repetition—is the remaining release use of this record.
