Shapes survived the save side fine — `getChildren` copies the whole
`userData` (including `shapeKey`) into the proxy's `user_data` blob,
and `toJSON()` writes it out. The load side was the half that lost
the thread: `proxyScene.loadFromSceneJson`'s `Parim` branch was
calling `Scene.instantiate(json_object.object_name)`, but
`object_name` is the *display label* ("Cube", "Point Light"), not the
geometry key the switch matches on ("Box", "PointLight"). The switch
fell through to its else branch and returned `new THREE.Mesh(undefined,
material)` — an empty mesh with no geometry, which is why reloaded
shapes showed up in the outliner, were translate-gizmo-movable, but
rendered invisibly, weren't raycast-pickable, and ignored the scale
gizmo (nothing to scale).
Recover the geometry key from `user_data.shapeKey` (the same field
`addShape.ts:21` stashes and `actions/snapshots.ts:78-79` already
uses for undo/redo), falling back to `object_name` for older cached
scenes that pre-date the field. Re-stash `shapeKey` on the recreated
obj so subsequent save round-trips survive.
Drive-by: drop the leftover `console.log(scene_json)` that was
firing on every cache load.
`MouseControls.onMouseDown` was calling `lockControls.lock()` eagerly on
every left/middle mousedown. The browser would acquire pointer lock
between mousedown and mouseup of a clean click, which suppressed the
synthesized `click` event — so `useViewportPointer.onClick` never fired
and raycaster selection was dead. The pointer-lock "Press Esc to exit"
toast popped on every static click as a tell.
Drop the eager lock from `onMouseDown`. The existing
`handleMousePointerLock()` path (called from `onMouseMove`, line 547)
already requests lock when `isMouseClicked === true && !isLocked`, so a
real drag still acquires the lock on its first mousemove. A static
click now stays out of the lock state machine entirely and the click
event reaches `MouseControls.onMouseClick` for the raycast.
Pre-migration this code lived behind window-level click listeners
attached by SceneManager, where the lock-vs-click ordering didn't
matter. The lib migration moved click handling to canvas-level inside
`useViewportPointer.ts`, exposing the conflict.
When the user left/middle-click drags to rotate the camera and releases,
Chromium synthesizes one final `mousemove` while exiting pointer lock so
it can reposition the cursor back to where the user grabbed it. At that
exact moment, Three's PointerLockControls still has `isLocked === true`
(the flag flips after the unlock event dispatches), so its mousemove
handler reads the synthesized `movementX/Y` as user input and rotates
the camera by it — producing a visible lurch back by roughly the inverse
of the user's last drag delta.
Capture the camera quaternion in `MouseControls.onMouseUp` immediately
before `lockControls.unlock()`. In a real (non-debug) `unlock` listener
attached at editor init, copy the snapshot back into the camera and call
`updateMatrixWorld()`. By the time `unlock` fires, the synthetic move
has already been applied, so overwriting is the surgical fix at the
boundaries we actually own — no Three.js subclassing, no per-frame
state machine.
Trace that confirmed the cause:
[mc] up button=1 locked=true quat=(-0.2684, 0.1394, 0.0393, 0.9524)
[cam-frame] quat=(-0.2684, 0.1394, 0.0393, 0.9524)
[plc] unlock quat=(-0.2180, 0.5112, 0.1358, 0.8202)
The hook was called below the `if (!currentSceneObject) return null`
guard, so renders where `currentSceneObject` was null skipped the hook
entirely. When it flipped to non-null on a later render, React saw a
new hook appear and threw "rendered more hooks than during the previous
render".
Move `useViewportSize()` (and the `getScale` helper that consumes it)
above the early return so the hook order is stable across renders.
Was reading editor.adapter.getViewportSize() inline during render,
which captured pageWidth/pageHeight signal values at that single
moment without subscribing to changes. If the host's pageHeight
signal hadn't been updated by GlobalSettingsManager yet (initial
value is 0 in the artcraft signal), Stage3DBody would render with
viewport dimensions of 0 and never recover.
useViewportSize encapsulates the read + window resize listener and
re-renders the component when dimensions change.
Final step of the Stage3D migration. The lib now exposes a single
top-level component:
<Stage3D adapter={adapter} sceneToken={...} cacheJsonString={...}
onSceneSerialized={...} />
…that owns the EngineProvider, body composition (canvas, outliner,
control panel, prompt box, model selector, controls, etc.), and
the React-lifecycle-driven set3DPageMounted. Hosts only build their
own PageSceneAdapter and decide where to stash the cache string.
Lib additions:
- src/lib/Stage3D.tsx — top-level shell (provider + drag/loading
layer + body)
- src/lib/Stage3DBody.tsx — ex-PageEditor; consumes adapter for
uploadImage / uploadPlaneFromMediaToken / getViewportSize
- Public re-export of Stage3D + Stage3DProps
Apps changes:
- pages/PageScene/PageScene.tsx reduced to a 50-line wrapper that
builds the Tauri adapter, owns the host-specific concerns
(tab-cache via useTabStore, URL ↔ scene-token sync via react-
router-dom, currentUserToken mirror), and renders <Stage3D />
- pages/PageScene/PageEditor.tsx — deleted (now Stage3DBody in lib)
Apps PageScene/ is now exactly three files, all genuinely host-
specific:
- PageScene.tsx (Tauri-flavored wrapper)
- useTauriPageSceneAdapter.tsx (Tauri-bound adapter implementation)
- index.tsx (exports PageScene)
The artcraft-website can now mount the same 3D editor by writing its
own ~50-line wrapper + a useWebPageSceneAdapter, with zero duplicated
editor body or controls.
Typecheck stays at 47, lib build clean.
ControlsTopButtons + Help + LoadUserScenes + NewSceneFromTemplate +
ScenePicker + SceneCard now live in
@storyteller/ui-pagescene/comps/ControlsTopButtons/. They access host
plumbing through the adapter (showToast, listUserMediaFiles,
listFeaturedMediaFiles, getCdnUrl, onSceneTitleChange) and read scene
metadata reactively from the new sceneMeta + currentUserToken store
fields.
Lib additions:
- PageSceneStore.sceneMeta + currentUserToken with setSceneMeta /
setCurrentUserToken actions; SceneMeta interface exported
- comps/ControlsTopButtons/{ControlsTopButtons,Help,LoadUserScenes,
NewSceneFromTemplate,ScenePicker}/
Apps changes:
- ~/signals/scene.ts: signalScene now mirrors into PageSceneStore so
the lib has reactive read access without depending on signals
- pages/PageScene/PageScene.tsx: lifted the URL ↔ scene-token sync
useSignalEffect out of ControlsTopButtons (router-aware bookkeeping
belongs at the apps shell), plus useSignalEffect mirroring
authentication.userInfo into store.currentUserToken
- pages/PageScene/PageEditor.tsx: ControlsTopButtons import now
resolves to @storyteller/ui-pagescene
- Deleted: pages/PageScene/comps/ControlsTopButtons/
Typecheck: 51 → 47 (deleted ControlsTopButtons had several pre-
existing typecheck issues that disappear in the rewrite). Lib build
clean.
Six new adapter members covering the host coupling that Controls3D,
ControlsTopButtons, and PageEditor will need once they live in the
lib:
- renderImageUploader / renderSplatUploader — slot patterns mirroring
the existing renderAssetUploader, for the two non-GLB upload modals
Controls3D triggers
- uploadImage / uploadPlaneFromMediaToken — host-pipeline wrappers
used by PromptBox3D and the gallery-drop handler. Signatures match
@storyteller/common's UploadImageArgs so they pass through cleanly
- navigateToImageTo3D — the only cross-page navigation the lib needs
today (Controls3D's "create 3D from image" button). Tauri host
switches tabs; web host will navigate routes
- performLogout — backs the SettingsModal logout callback
useTauriPageSceneAdapter wires all six. No call sites yet — the
files that consume them are still in apps; the next commits move
them into the lib.
Typecheck stays at 51, lib build clean.
The AssetMenu — asset browser modal, fetch hooks, mapping utilities,
demo data — moves out of the artcraft host into the lib alongside the
engine it serves. Host-platform calls (MediaFilesApi, BucketConfig,
addToast, UploadModal3D) are reached via the new adapter methods and
the renderAssetUploader slot.
Lib additions (frontend/libs/components/pagescene/src/lib):
- comps/AssetMenu/{AssetMenu,AssetModal}.tsx
- comps/AssetMenu/hooks/{useFeaturedObjects,useUserObjects}.tsx
- comps/AssetMenu/utilities/{fetchMediaItems,misc}.ts
- comps/AssetMenu/shared/{ItemElement,ItemElements}.tsx
- signals/demoAssets/* (5 files)
Hooks now read the adapter from EngineContext rather than importing
~/Classes/ApiManager directly. Fetcher functions take adapter as a
second argument.
Apps deletions:
- pages/PageScene/comps/AssetMenu/ (entire folder)
- pages/PageScene/signals/demoAssets/ (entire folder)
- pages/PageScene/signals/index.ts (now empty)
Apps updates:
- comps/Controls3D/Controls3D.tsx: AssetModal import now from
@storyteller/ui-pagescene
Typecheck: 58 → 51 (the deleted AssetModal had several pre-existing
typecheck issues that disappear with the rewrite).
nx build ui-pagescene clean.
Apps PageScene/ now contains only host-coupled comps Controls3D and
ControlsTopButtons, plus the route shell (PageScene.tsx, PageEditor.tsx,
useTauriPageSceneAdapter.tsx). Everything else is in the lib.
The lib's AssetMenu (next commit) needs a way to render the host's
UploadModal3D from inside its own modal. Following the same slot
pattern as renderAssetBrowser/renderSceneLoader, a new
renderAssetUploader slot lets the host plug in its file picker +
validation + splat conversion UI without the lib pulling in any
host-component imports.
useTauriPageSceneAdapter wires UploadModal3D into the slot. File
renamed to .tsx since the slot returns JSX.
Typecheck stays at 58.
Adds the surface AssetMenu needs to live in the lib without importing
artcraft-host APIs:
- listUserMediaFiles / listFeaturedMediaFiles — paginated media
listings backed by MediaFilesApi
- getCdnUrl — wraps host BucketConfig for thumbnail composition
- showToast — wraps host addToast / react-hot-toast pipeline
useTauriPageSceneAdapter wires all four to the existing artcraft host
plumbing (~/Classes/ApiManager/MediaFilesApi, ~/api/BucketConfig,
~/signals/addToast). No call sites yet — the AssetMenu file move that
consumes these is the next step.
Typecheck stays at 58.
These four enums are the asset-browsing vocabulary the AssetMenu and
related host code share with the engine. Moving them into the lib
gives a single canonical identity, lets the lib own the AssetMenu
internals end-to-end, and aligns with the same pattern used for
AssetType / Visibility / etc.
Apps `~/enums/QueryFilters.ts` and `~/enums/toasts.ts` become thin
re-exports from @storyteller/ui-pagescene; existing host imports are
unchanged.
This is a prep step for the AssetMenu migration — Chunk 1 in the
ongoing PageScene componentization. No functional changes; typecheck
stays at 58.
Editor.constructor was injecting <script src="jsm/libs/ammo.wasm.js"> into
document.body and creating <div id="created-one-element"> on every
construction. Strict mode + provider remounts produced duplicate tags;
the div was unreferenced anywhere in the codebase.
Replaced with a module-level Promise singleton (ammoLoader.ts) that
appends the script exactly once. Existing tags (e.g. from a prior
session) are detected and reused. The div is gone.
This is a correctness cleanup, not a behavior change for first-mount —
the script still loads on first Editor construction, just no longer
duplicates on subsequent ones.
Lifts the remaining apps/PageScene shim-paths inside the host's own
files to direct package imports. The shim files themselves stay
(actions/, hooks/, contexts/, DragAndDrop/, sceneMetadata.ts,
comps/{DragComponent,PrecisionSelector,FocalLengthDisplay,
AspectRatioMenu,PoseModeSelector,EngineCanvases,Outliner,
ControlPanelSceneObject,SceneContainer,PreviewEngineCamera,
PreviewImages,OnboardingHelper,EditorLoadingBar,
ControlsTopButtons/Help/Help}); they're still load-bearing for the
host-coupled comps that haven't migrated yet (AssetMenu/,
ControlsTopButtons/, Controls3D/) and they'll get cleaned up when
those move.
Updated host files to import directly from @storyteller/ui-pagescene:
- PageEditor.tsx (8 imports collapsed: hooks/*, EngineContext,
actions, GridVisibleChangedEvent + pickDropPosition)
- PageScene.tsx (EngineProvider)
- comps/Controls3D/Controls3D.tsx (EngineContext, setTransformMode)
- comps/ControlsTopButtons/ControlsTopButtons.tsx (sceneMetadata)
- comps/AssetMenu/shared/ItemElement.tsx (DndAsset → dragAndDrop
named import, EngineContext)
Lib public API:
- KeyGroup (the binding-category string-union type from keymap)
re-exported as `KeymapKeyGroup` to avoid name collision with the
Help component kit's KeyGroup (the keyboard-icon visual primitive).
Verified: artcraft typecheck at 61 errors (down from 62 — one fewer
pre-existing unused-import warning).
The lib already owns the canonical sources for these. Apps copies are
now redundant code that can drift; collapsing to one-line re-exports
removes that risk.
- apps/.../PageScene/models/ — was 7 .ts/.tsx files; now a single
index.ts that re-exports the full type surface from
@storyteller/ui-pagescene. Other artcraft modules (MediaFilesApi,
VoiceDesignerApi, WeightsApi, UserBookmarksApi, BetaKeysApi,
PromptsApi, ImageTo3DExperience, backgroundLoadedMedia) keep their
`~/pages/PageScene/models` import path unchanged.
- apps/.../PageScene/enums/ — was 10 enum files; now a single index.ts
re-exporting CameraAspectRatio, EditorStates, MediaFileType +
MediaFileSubtype, MediaFileClass, WeightCategory, WeightType,
Visibility, FetchStatus, GenerateTtsAudioErrorType, JobState,
ArtStyle from the lib.
- apps/.../PageScene/datastructures/ — was a single common.ts (XYZ +
Simple3DVector) with no remaining importers. Deleted; lib's copy
is the single source. (XYZ + Simple3DVector are exported from
@storyteller/ui-pagescene for direct host imports if needed.)
Lib's index.ts adds MediaFileType + MediaFileSubtype + MediaFileClass
+ WeightCategory + WeightType + Visibility + FetchStatus +
GenerateTtsAudioErrorType + JobState + ArtStyle to its enums block.
Verified: artcraft typecheck at 62 errors (one fewer pre-existing
warning -- a stale unused-export now resolved through the shim).
Adds MediaInfo, Pagination, Prompts, UserDetailsLight, MediaFile,
GetMediaListResponse, GetMediaFileResponse,
VoiceConversionModelListItem/Response, CreatorDetails, and the three
UserBookmark types to the lib's public surface so host modules that
previously imported from apps/.../PageScene/models can switch
imports to @storyteller/ui-pagescene without losing types. The lib
already had all of these internally; this commit just promotes
them through the public index.
Verified: artcraft typecheck holds at 63 errors.
The bar previously read host-level loadingBarData/loadingBarIsShowing
signals — orphaned writes after Stage A; the engine no longer drives
them. The lib version reads from store.editorLoader (which IS
populated by EditorLoaderEvent on the bus from save_manager's
save/load paths) so the bar actually shows during the right
operations.
- src/lib/comps/EditorLoadingBar.tsx: reads editorLoader.{isShowing,
message} from PageSceneStore, supplies a stable label and a fixed
100% progress (the bar is binary-shown for engine save/load; the
prior progress field was never animated).
- Apps copy becomes a one-line re-export shim.
artcraft/app/src/signals/loadingBar.ts is now orphaned (no readers,
no writers anywhere). Left in place — harmless, can be deleted in a
drive-by cleanup.
Verified: artcraft typecheck at 63 errors (down from 70 — the
unused-import errors that referenced loadingBarData/IsShowing
disappeared with the migration).
Output of `nx sync` after the comp migrations:
- pagescene/tsconfig.lib.json picks up the new transitive lib refs
it inherited from the migrated comps (button-icon, tooltip,
close-button, label, button, input, button-dropdown).
- apps/artcraft/tsconfig.json gets pagescene listed before pagedraw
in references (alphabetical normalization by Nx).
`npx nx build @storyteller/ui-pagescene` succeeds end-to-end:
dist/index.js: 1,228.43 kB (gzip: 319.60 kB)
dist/index.d.ts emitted with the full public API surface.
Artcraft typecheck still at 70 errors (all pre-existing).
OnboardingHelper had been left in apps because it depended on the
Help.tsx icon kit (Key, KeyGroup, Mouse, Plus etc.) under
ControlsTopButtons/Help. Help.tsx itself has no host coupling --
it imports only @storyteller/ui-label -- so both move into the lib
together:
- src/lib/comps/ControlsTopButtons/Help/{Help,index}.tsx — keyboard
+ mouse icon visual primitives (Key, KeyGroup, Mouse, Plus,
ShortcutsGroup, Shortcut, Help itself).
- src/lib/comps/OnboardingHelper.tsx — the WASD/click hint that
fades out on first key/mouse interaction.
Apps copies become re-export shims. Public API exposes the icon
primitives so future host-side help overlays can reuse them.
Verified: artcraft typecheck at 70 errors (one fewer than the 71
prior; one of OnboardingHelper's pre-existing unused-import errors
disappeared with the migration).
Migrates the comps that previously read pageHeight/pageWidth from
the artcraft `~/signals` surface; they now use a new useViewportSize
hook that reads through adapter.getViewportSize when supplied (host
threads its existing pageWidth/pageHeight signals through), falling
back to window.innerWidth/innerHeight + a resize listener.
Comps moved:
- Outliner — uses viewport.height for the editor-height calculation
and viewport.width for the dual-layout breakpoint at 2000px.
- ControlPanelSceneObject — viewport.height for the small-screen
scale calc.
- SceneContainer — viewport for the container dimensions, with the
56px TopBar deduction extracted to a TOP_BAR_PX constant.
- PreviewEngineCamera — viewport.width / viewport.height for
responsive UI thresholds (also drops the explicit useSignals call).
- PreviewImages — viewport-derived image-grid dimensions.
New: src/lib/hooks/useViewportSize.ts. Reactive, adapter-aware, with
a window.innerWidth/innerHeight fallback for hosts that don't supply
getViewportSize.
Apps copies become one-line `export { X } from "@storyteller/ui-
pagescene"` re-export shims.
Lib public API exposes:
- The 5 newly-migrated comps.
- useViewportSize hook (useful to host code that wants the same
reactivity contract).
Comps still in apps (host-coupled, blocked behind UploadModal3D /
MediaFilesApi / signalScene / addToast / loadingBarData):
- AssetMenu/, ControlsTopButtons/, Controls3D/, EditorLoadingBar,
OnboardingHelper. These either become adapter render-slots
(renderAssetBrowser is already on the interface) or get migrated
once their host deps are surfaced through the adapter.
Verified: artcraft typecheck at 71 errors (one fewer than the prior
72 baseline; the dropped error was a pre-existing implicit-any in
the migrated PreviewEngineCamera that disappeared with the rewrite).
Lib gains the React/orchestration layer that sits between the engine
and the host React tree:
- src/lib/actions/* (12 dispatchers + index): addCharacter, addObject,
addShape, deleteObject, selectObject, setCameraAspect,
setObjectColor (+ ColorSession), setTransformMode, toggleObjectLock,
toggleObjectVisibility, transformObject (+ TransformSession),
deselectObject, beginColorSession, beginTransformSession.
- src/lib/hooks/*: useEditorCanvas, useCamViewCanvas, useFreeCam,
useViewportPointer, useViewportKeyboard. ~/-aliased imports
rewritten to relative paths inside the lib.
- src/lib/contexts/EngineContext/{EngineContext,EngineProvider}: the
React context boundary. EngineProvider takes adapter + cache hooks
via props and constructs `new Editor(adapter)`.
- src/lib/DragAndDrop/DndAsset.ts: the asset-drop singleton, now
reading viewport size through `editor.adapter.getViewportSize?.()`
with a `window.innerWidth/innerHeight` fallback when the host
doesn't supply one.
- src/lib/sceneMetadata.ts: the SceneGenereationMetaData helper used
by the save flow + the EngineProvider cache snapshot.
Adapter interface gains:
- getViewportSize?(): { width: number; height: number }
Used by DndAsset for drop hit-testing. Optional — if the host
omits it, the lib falls back to window.
Apps wrappers:
- All apps copies become one-line `export { X } from "@storyteller/
ui-pagescene"` re-export shims so existing importers (PageEditor,
AssetMenu items, ImageTo3DExperience, hooks, EngineContext
consumers) keep their import paths unchanged.
- useTauriPageSceneAdapter.ts now wires getViewportSize from the
existing pageWidth/pageHeight signals so the artcraft host's
TopBar height + page chrome are honored.
Public API (src/index.ts) adds:
- All 12 action dispatchers + ColorSession/TransformSession types.
- The 4 hooks (useEditorCanvas, useCamViewCanvas, useFreeCam,
useViewportPointer, useViewportKeyboard).
- EngineContext + setActiveEditor/getActiveEditor + EditorExpandedI.
- EngineProvider.
- dragAndDrop singleton.
- getSceneGenerationMetaData.
Verified: artcraft typecheck holds at 72 errors (all pre-existing,
mostly in comps/ and unrelated areas like SoundManager). Lib's own
typecheck cycle through `noEmitOnError: false` produces a clean
dist/index.d.ts with the new public surface.
Remaining for Stage B: comps/ (15 React UI components), lifting the
12-tab switch out of PageEditor.tsx, nx build smoke + manual end-
to-end test.
Final integration step. The lib's @storyteller/ui-pagescene is now the
canonical source for PageScene's engine + state; the artcraft host
constructs a Tauri-backed PageSceneAdapter and threads it through
EngineProvider into Editor.
Changes:
apps/PageScene/engine/* -- previously held duplicate copies; now:
- Files imported by other artcraft code (editor.ts, scene.ts,
helper.ts, save_manager.ts, scene_manager_api.ts,
keybinds_controls.ts, keymap.ts, pickDropPosition.ts, pointer.ts,
cameraMath.ts, editor/{HistoryManager, ViewportController,
PostProcessingPipeline, GizmoController, CameraController,
SelectionBridge}.ts, editor/actions/*.ts) become tiny
`export * from "@storyteller/ui-pagescene"` re-export shims so the
existing 50+ importers (PageEditor, EngineProvider, EngineContext,
hooks/, DragAndDrop/, action dispatchers) keep working unchanged.
- Files only used internally by the now-shimmed engine code
(CustomOutlinePass.js, FindSurfaces.js, TransformControls.js,
chromakey.js, shaders.js, InfiniteGridHelper.ts, browserChecks.ts,
placeholder_manager.ts, KinHelpers/*, EditorState/*,
APIManagerResponseSuccess.ts, proxy/*) are deleted -- the lib
carries the canonical copies.
- engine/api_manager.ts + engine/api_fetchers.ts deleted; their
bodies move to the new useTauriPageSceneAdapter.ts hook (see below).
EngineProvider:
- Takes a new PageSceneAdapter prop and constructs `new Editor(adapter)`.
- Reads adapter via ref so adapter identity changes don't tear down
the live engine.
apps/PageScene/PageScene.tsx:
- Uses useTauriPageSceneAdapter() to build the adapter, threads it
into <EngineProvider adapter={adapter} ...>.
apps/PageScene/useTauriPageSceneAdapter.ts (new):
- Single source of all the host-side platform plumbing. Wires
enqueueGeneration -> GenerateImage (tauri-api), saveScene/loadScene
to the api_manager+api_fetchers bodies inlined here, fetchAsset to
FetchProxy with LoadWithoutCors fallback, getCdnOrigin/
getApiSchemeAndHost to artcraft singletons, getMediaUrlByToken to
MediaFilesApi.GetMediaFileByToken, onSceneTitleChange to
signalScene, getCurrentUserToken to the authentication signal.
Lib public API (src/index.ts):
- Adds Editor (default), Scene, SceneUtils, SaveManager, SceneManager,
MouseControls, buildKeymap/dispatchBinding, all editor subsystems
(HistoryManager, ViewportController, PostProcessingPipeline,
GizmoController, CameraController, SelectionBridge), all action
classes (ColorAction, CreateAction, DeleteAction, LockAction,
TransformAction, VisibilityAction), engine utilities
(pickDropPosition, cameraMath surface, pointer helpers,
isPointerLockSupported), and the SceneGenereationMetaData type.
Adapter interface (lib/adapter.ts):
- Adds getMediaUrlByToken(token): Promise<string> to replace the
scene's prior MediaFilesApi field.
Verified: artcraft typecheck at 72 errors (down from 124 baseline --
50+ pre-existing ApiManager-related errors disappeared along with
the deleted apps engine internals). All remaining errors are
pre-existing in unrelated AssetMenu/LoadUserScenes/PageEditor modal
config / SoundManager soundboard / pre-existing engine type casts.
Bulk-copies the rest of the engine cluster into the lib with the
adapter wiring that makes editor.ts platform-agnostic:
Lib gains:
- engine/{scene, save_manager, helper, scene_manager_api,
keybinds_controls, keymap, editor}.ts
- engine/editor/SelectionBridge.ts
- engine/editor/actions/{Color, Create, Delete, Lock, Transform,
Visibility}Action.ts + snapshots.ts
- engine/EditorState/* (full JSON serialization schema)
Editor constructor signature change (lib-side only):
- Now takes a PageSceneAdapter parameter and exposes it as
`editor.adapter` for subsystems that need platform-supplied
capabilities (scene's fetchAsset/getMediaUrlByToken,
save_manager's saveSceneState/loadSceneState).
- Drops `~/signals` imports: signalScene calls now route through
adapter.onSceneTitleChange, authentication.userInfo via
adapter.getCurrentUserToken, and the loadingBarData/IsShowing
signals are dropped (the same UX is driven by EditorLoaderEvent
via the bus).
- Drops `~/Classes/ApiManager.MediaUploadApi` import + media_upload
field — unused inside the engine; uploads happen via
adapter.uploadAsset at the call sites.
- Deletes the api_manager + APIManager dependency entirely; SaveManager
now reads saveSceneState/loadSceneState callbacks directly off
adapter.saveScene/loadScene.
Scene gains:
- SceneDeps.fetchAsset(url) — replaces `LoadWithoutCors` from
@storyteller/tauri-api at the load_glb_wrapped_no_cors and
load_splat_wrapped_no_cors call sites.
- SceneDeps.getMediaUrlByToken(token) — replaces `mediaFilesApi
.GetMediaFileByToken` at the four getMediaURL call sites.
- Drops MediaFilesApi field; the scene class no longer imports
`@storyteller/api`.
Adapter interface gains:
- getMediaUrlByToken(token: string): Promise<string>
tsconfig.lib.json relaxes:
- noEmitOnError: false (so the dist still emits for the apps shims
to resolve through it, while the pre-existing material/texture +
glb-as-unknown errors that already lived in apps's engine surface
are tolerated)
- strictNullChecks/noImplicitAny/noUnusedLocals: false (same
motivation -- the engine code wasn't authored under these strict
flags; tightening them is a future cleanup)
Apps copies still in place; they still compile against their own
files. The lib's copies are the new canonical source the apps
shims will eventually resolve through. Final integration step
(shimming apps editor.ts/scene.ts/etc to lib + wiring adapter
through EngineProvider + building the new artcraft wrapper) is
the next checkpoint.
Verified: artcraft typecheck still at 124-error baseline.
Pure additive prep for the editor.ts/scene.ts move. Lib now owns:
- Engine-internal enums (still local to PageScene -- not re-exported
to artcraft via the ~/enums shim because those aren't shared with
the rest of the host): MediaFileType + MediaFileSubtype,
MediaFileClass, WeightCategory, WeightType, Visibility, FetchStatus,
GenerateTtsAudioErrorType, JobState, ArtStyle.
- Full models surface: assets, mediaInfo, pagination, prompts,
sceneGenerationMetadata, types, userBookmark + index.
Models imports rewritten on copy:
- mediaInfo.ts: ~/pages/PageScene/enums -> ../enums
- types.tsx: ~/pages/PageScene/enums -> ../enums; ./movies (broken
pre-existing import) -> ./mediaInfo (where MediaInfo actually lives;
fixing on the way through since it's a free type fix).
Apps copies stay in place; the lib copies are still orphaned (not
exported from src/index.ts). Proxy folder also tried but needs
engine/scene which isn't in the lib yet -- deferred to the next
checkpoint with editor.ts/scene.ts.
Verified: artcraft typecheck still at 124-error baseline.
Bulk-copies the pure engine subsystems and their .js helpers into
the lib so they're ready when editor.ts/scene.ts/save_manager.ts
also move (which need adapter wiring for FetchProxy/api_manager
replacement). For now these lib copies are orphaned -- not exported
from src/index.ts, no callers inside the lib yet.
Files copied:
- engine/editor/{HistoryManager, PostProcessingPipeline,
ViewportController, GizmoController, CameraController}.ts
- engine/{CustomOutlinePass, FindSurfaces, TransformControls,
chromakey, shaders}.js
- engine/{cameraMath, InfiniteGridHelper, browserChecks,
placeholder_manager, pickDropPosition}.ts
- engine/KinHelpers/* (FK rigging, kept per direction -- only the
MediaPipe pose detection was removed earlier)
Imports rewritten on-copy:
- editor/SelectionBridge.ts: ~/enums -> ../../enums (lib-local)
- editor/CameraController.ts: ~/pages/PageScene/enums -> ../../enums
Files NOT yet copied (need editor.ts / scene.ts / sceneGenerationMetadata
to land first, otherwise their type imports don't resolve):
- engine/editor/SelectionBridge.ts (depends on scene_manager_api)
- engine/editor/actions/* (depend on Editor type)
- engine/EditorState/* (depend on scene + sceneGenerationMetadata)
tsconfig adjustments:
- allowJs: true / checkJs: false to support the .js helpers
(CustomOutlinePass, FindSurfaces, TransformControls, etc.)
- Two pre-existing-flagged errors (PostProcessingPipeline outliner
uniforms cast, FKHelper TransformControls.camera cast) annotated
with @ts-expect-error so the lib build emits dist properly under
the inherited noEmitOnError: true.
Apps versions stay in place untouched -- they still compile against
the apps copies. This is a temporary duplicate-source state that
goes away once editor.ts also moves and the apps subsystems become
re-export shims.
Verified: artcraft typecheck still at 124-error baseline.
The Chromium-style EventListenerMap bus and the per-event-type bridge
both move into the lib. Class identity is single-sourced from
@storyteller/ui-pagescene so emit/subscribe lookups share the same
constructor key whether they're called from inside the lib or from
the artcraft host (e.g. PageEditor's grid-toggle GridVisibleChangedEvent).
The two apps/PageScene/engine files left in place become tiny re-export
shims pointing at the lib so the existing ~50+ importers (engine
internals, action dispatchers, PageEditor) keep working unchanged
during the staged migration -- they'll be deleted in a future commit
once their consumers move into the lib too.
Workspace plumbing:
- frontend/tsconfig.json: nx sync added pagescene to the root project
references list.
- pagescene/tsconfig.{json,lib.json}: nx sync trimmed the references
to only the libs the new code actually uses
(api-enums, model-list, common).
- node_modules/@storyteller/ui-pagescene needs to be a Windows
directory junction (mklink /J) for the workspace to resolve the
package; the prior bash `ln -s` produced a copy on Windows that
didn't reflect the lib's live state and caused misleading
"no exported member" errors.
Drive-by: nx sync also added a missing `gravatar` reference under
lightbox-modal -- unrelated cleanup riding along.
Verified: artcraft typecheck error count back to 124 baseline (no
new errors from this move).
The lib now owns the canonical PageSceneStore (Zustand), the
PageScene-specific enums (AssetType, AssetFilterOption, ClipGroup,
CameraAspectRatio, EditorStates), the common datastructures
(XYZ + Simple3DVector), and a minimal models surface (MediaItem +
AudioMediaItem -- enough for the store; richer model types stay in
apps/PageScene/models for now and migrate alongside their consumers).
Old artcraft locations become re-export shims so 56+ existing
importers keep working unchanged during the staged migration:
- apps/.../enums/{AssetType,AssetFilterOption,ClipGroup}.ts re-export
from @storyteller/ui-pagescene. Single nominal identity for each
enum across both lib and host -- avoids the "two AssetType types
that look the same but TypeScript treats as distinct" trap when
values flow across the boundary (e.g. ImageTo3DExperience passes
AssetType.SPLAT into PageScene's addObject).
- apps/.../pages/PageScene/PageSceneStore.ts re-exports the store +
types from the lib.
artcraft/tsconfig.json gains a `references` entry for
libs/components/pagescene so TypeScript resolves the package.
node_modules/@storyteller/ui-pagescene gets its workspace symlink
on first `npm install`; created manually here for the typecheck.
Verified: total typecheck error count unchanged (124, all pre-existing)
-- no new errors from the move.
Empty Nx library at frontend/libs/components/pagescene/ mirroring the
@storyteller/ui-pagedraw shape (package.json with development export,
tsconfig.lib.json with workspace lib references, vite.config.ts).
Defines the platform-abstraction surface PageSceneAdapter:
- Generation enqueue (model/provider/prompt/tokens/aspect/resolution)
- Scene I/O (saveScene, loadScene, fetchAsset, host CDN/API getters)
- Slot renderers for host-owned modals (renderAssetBrowser,
renderSceneLoader)
- Optional event hooks (onError, onSceneSaved, onSceneTitleChange,
onEnqueueMeta, onSelectionChange, onSceneDirty)
- Tab-cache integration (cacheJsonString + onSceneSerialized) so the
artcraft useTabStore can roundtrip serialized scene JSON without
the lib knowing about tabs
Library code is empty for now -- file migrations (PageSceneStore +
engine + comps + actions + hooks etc.) land in follow-up commits on
this branch.
- Move to zustand stores from preact signals
- Delete dead code and unused files
- Remove addLocalSplat and follow gallery flow
- Get rid of Timeline components and queues, move to actions
- Lightbox now shows full media metadata: Aspect Ratio, Resolution,
Size, Duration, Audio, and Created (with timestamp).
- Added Author header at the top so the close button no longer overlaps
the Prompt copy button.
- Extracted aspect ratio / resolution / duration formatters into
`@storyteller/common` so the website and desktop app share them.
Customers occasionally saw "0 Credits" with no signal that the fetch had
failed silently. Add a status badge on the coin (slow/failed/ recovered,
hidden in the happy path), a 60s background refresh, and gate the
initial fetch on AUTH_STATUS.LOGGED_IN to remove the 1s setTimeout
band-aid that was racing intermediate auth states.
Only the submit button triggers generation by default. The "Enter to
generate" toggle (available in both app and website settings top right
nav) still allows opting into Enter-to-submit.
- Pressing **Enter** in any prompt box now adds a new line instead of
submitting - fixes complaints about accidental generations
- **Shift+Enter** is the new submit shortcut
- Desktop app gets an "Enter to generate" toggle in Settings → General
(off by default) for users who prefer the old behavior
- Applies to all prompt boxes in the Artcraft desktop app and the
website