# 3D history product restore and deleted-layer selection fix

This pass fixes two user-visible regressions in the Babylon 3D workflow.

## Product/element disappears after undo or redo

### Root cause

3D history snapshots intentionally remove heavy GLB data URLs and keep the full product records in the history track `assets` registry. During restore, `hydrate3DHistoryScene()` correctly restored the model from that registry.

`loadView()` then ran a second runtime hydration step which compacted the restored products with the history mode again. That second compaction removed the heavy `model.url` a second time. The issue was most visible with this sequence:

1. add a Product 3D element;
2. undo to an empty scene;
3. redo;
4. Babylon receives the product record without its GLB URL and cannot import the product.

### Fix

- Added `hydrate3DHistorySceneFromRuntime()` in `src/core/history3d.ts`.
- The helper merges missing model/parts from the mounted runtime only when needed.
- It never applies history compaction and therefore never removes an already rehydrated GLB.
- `loadView()` now uses this canonical helper.

## Camera persistence could overwrite a recent scene mutation

Camera events previously patched the camera into the scene currently stored in Zustand. Product/layer mutations are persisted with a debounce, so the stored scene could still be older than the mounted Babylon scene. Moving the camera immediately after adding or modifying a product could therefore temporarily save the old scene without that product.

Camera persistence now schedules an export of the authoritative mounted engine scene. Products, layers and camera are saved together in one canonical scene.

## Store scene after history restore

Undo, redo, reset and direct history navigation now persist both:

- the current history track;
- the scene actually restored by Babylon.

This is handled centrally from `AsukaRoot`, so it applies to history actions triggered by the History module, top toolbar or public API.

## Deleted 3D layer toolbar remains open

`removeObject()` removed the Babylon mesh but previously changed `selectedObjectId` silently. The selection UI therefore had no event telling it to close.

When the removed layer is selected, the engine now calls `setSelectedObject(null, true)` before emitting `removeObject`. This emits a real empty selection and closes the contextual toolbar and any module opened from it.

The behavior applies to both surface/plane decorations and texture print-area objects.

## Verification

- TypeScript `tsc --noEmit`: passed.
- Vitest: 40/40 tests passed across `src` and `pass/src`.
- Added regression tests for heavy GLB rehydration after an empty undo state.
- Engine neutrality audit: passed.
- Canonical 3D contract audit: passed on 578 files.
- TypeScript syntax audit: passed.
- Product Vite build: passed.
