Three more signal files retired in favour of the Zustand store.
error.ts (deleted):
- ErrorDialog (in ~/components/signaled): reads showErrorDialog /
errorDialogTitle / errorDialogMessage from the store, calls
setShowErrorDialog
- SceneTitleInput (in TopBar): handleShowErrorDialog uses the store's
setErrorDialog action
myMovies.ts (deleted):
- MyMoviesButton (in TopBar): writes via setGenerateMovieId /
setViewMyMovies
- GenerateModals: useEffect-on-store replaces useSignalEffect; reads
generateMovieId from store
- MyMovies and Sharing: read viewMyMovies from store, close via
setViewMyMovies
- Sharing also reads generateMovieId from store
editorOverlays.ts (deleted):
- PageEditor's LoadingDots: reads editorLoader from store
- SceneContainer: reads editorLetterBox from store and passes through
to Letterbox
- PreviewEngineCamera: reads editorLetterBox + toggleEditorLetterBox
from store
- save_manager: showEditorLoader/hideEditorLoader become thin wrappers
over the store actions, keeping the same call sites
signals/index.ts no longer re-exports any of the three deleted files.
Migrates components and engine code from reading signals/engine.ts
(cameraAspectRatio, editorState, gridVisibility, previewSrc) to reading
the Zustand store.
Components migrated to usePageEnigmaStore reads:
- AspectRatioMenu (also dropped useSignals + collapsed two
duplicated lookup ladders into ICON_BY_RATIO / LABEL_BY_RATIO)
- PreviewFrameImage
- Letterbox (no longer reactive via signals; reads camAspect from store)
- StyleButtons (editorState, previewSrc, setEditorState)
- Outliner (cameraAspectRatio)
- SidePanelMenu (editorState + setEditorState)
- ControlPanelSceneObject (editorState)
- PreviewEngineCamera (cameraAspectRatio + editorState)
- PageEditor (cameraAspectRatio for getScale; the signal version still
flows into PromptBox3D as a prop)
Engine code migrated to usePageEnigmaStore.getState():
- editor.ts: snapShotOfCurrentFrame reads cameraAspectRatio + grid;
switchPreview / switchEdit set editorState
- helper.ts: switchCameraView writes editorState
- video_generation.ts: generateFrame writes previewSrc + editorState
- SceneMetadata.ts: cameraAspectRatio for snapshot metadata
- scene.ts: subscribes to store gridVisible (not the signal)
signals/engine.ts becomes a thin compatibility layer for PromptBox3D's
signal-typed props. Its setters dual-write into the store so any
PromptBox3D-driven changes (grid toggle, aspect ratio dropdown) keep
the store authoritative. editorState / previewSrc / setEditorState
removed entirely -- nothing outside the store needs them anymore.
Net effect on typecheck: 148 -> 147 errors in PageEnigma. Zero new
errors introduced.
The auto-success useEffect in UploadModalSplat / UploadModalImage /
UploadModal3D was firing on UploaderStates.success and immediately
calling onClose(), which short-circuited the UploaderModalContent
switch before the UploaderStates.success branch (UploadSuccess) could
render. The OK button on UploadSuccess was effectively dead code.
Move the success-side actions (open the gallery, fire onSuccess, close
the modal) into the UploadSuccess onOk handler. The user now sees the
"upload successful" dialog and can dismiss it intentionally; the
gallery opens at dismissal so they can drag the freshly uploaded asset
onto the canvas.
Pre-refactor the splat upload modal had two paths: the (developer-only)
local-bytes path that handed the in-memory buffer to a now-deleted
editor.timeline.addLocalSplat, and a real upload path that registered
the file in Storyteller's media library but did nothing else with it.
The local-bytes branch was the only thing letting a freshly uploaded
splat reach the canvas. With it gone, splat upload was a dead end --
the file landed in the user's media library but they had no path back
to the 3D editor.
Align with the existing image / 3D-model upload convention: on
UploaderStates.success, set galleryModalVisibleViewMode and call
onSuccess(selectedCategory). The user can then drag the freshly
uploaded splat from the auto-opened gallery onto the canvas, which
flows through PageEditor's onImageDrop handler and into
actions/addObject.
Other changes:
- onSuccess signature: (ArrayBuffer, boolean) => (FilterEngineCategories)
to match UploadModalImage / UploadModal3D
- Drop the unused onLocalBytes prop and plumbing in UploadFilesSplat;
no callsite invoked it. The two existing consumers
(PageEnigma/Controls3D, GlobalFileDropHandler) ignore the category
argument, which is fine -- () => void is assignable.
Replaces the four-hop signal-stub pattern (drop -> store-only update,
engine never told) with explicit action helpers that mutate the engine
and the store in one step.
actions/:
- addObject / addCharacter / addShape: call editor.sceneManager.create
to load the THREE.js object, then update the Zustand store. addCharacter
also stamps userData.isCharacter on the loaded object
- deleteObject: thin wrapper over editor.deleteObject (which already
goes through SceneUtils to update the store)
- selectObject / deselectObject: invoke sceneManager.select_object;
Editor.publishSelect already writes the selection to the store
- setTransformMode: drives editor.change_mode + store + the legacy
selectedMode signal during the migration window
- setCameraAspect: drives editor.changeRenderCameraAspectRatio + store +
the legacy cameraAspectRatio signal
- index.ts barrel
Wiring updates:
- PageEditor drop handler (gallery -> 3D) now calls actions/addObject
and actions/addCharacter with the editor and world position
- DragAndDrop/DndAsset takes the editor at pointerdown time and calls
actions/* on drop. ItemElement passes the editor in via EngineContext
- AspectRatioMenu calls actions/setCameraAspect; PageEditor's bottom-bar
aspect-ratio select uses the action too
- Controls3D's mode toggle (move/rotate/scale) goes through
actions/setTransformMode and the four little redundant handlers are
collapsed into one
- ImageTo3DExperience's "Open in 3D Editor" button uses getActiveEditor
to pick up the live editor across pages
Plumbing:
- New module-level activeEditor handle in EngineContext (with
getActiveEditor / setActiveEditor) so non-React callers and cross-page
actions can reach the engine; EngineProvider sets it on init and
clears on unmount
- Drop the now-orphaned signals/objectGroup, signals/characterGroups,
signals/shape entirely. Their job (store update + engine call) lives
in actions/. signals/index.ts loses the corresponding re-exports
- Remove the placeholder addLocalSplat onSuccess in Controls3D (a
developer-test path for direct splat loading; the real splat upload
goes through GlobalFileDropHandler) and delete the now-confirmed-dead
recordScene method in editor.ts
- video_generation.ts zipBlob: type as Blob | null (matches the
UploadStudioShot signature; pre-existing TS error)
Net effect on typecheck: 144 -> 147 errors in PageEnigma + experiences;
zero errors introduced in actions/, PageEnigmaStore, or DragAndDrop. All
new errors are pre-existing issues in adjacent files surfaced by the
edits, not regressions.
editor.ts surgery (1864 -> 1665 lines):
- Drop imports + field declarations + constructor instantiations for
AudioEngine, TransformEngine, EmotionEngine, LipSyncEngine,
CharacterAnimationEngine, and TimeLine; drop the
Queue/QueueNames/fromEngineActions/toEngineActions imports
- Drop timeline state fields: can_playback, playback_location,
current_frame, last_scrub. generating_preview stays (it tracks the
stylize preview render, not the timeline)
- Replace Queue.publish in changeRenderCameraAspectRatio, newScene,
loadScene, and publishSelect with direct store actions
(usePageEnigmaStore.getState().setSelectedObject / resetScene)
- Replace timeline.characters lookups (used by SceneManager outliner)
with a getCharactersByUuid helper that reads the store
- Replace timeline.isCharacter (passed into SceneManager) with an
isCharacterUuid helper that checks the store's character list
- recordScene becomes a no-op stub: timeline-driven frame capture is
gone, video generation needs to drive its own frame loop. TODO marker
for the rewiring
- generateVideo and togglePlayback drop their timeline.is_playing /
scrubber gating; renderSingleFrame drops the timeline.update branch
- Bonus: kill the meraAspectRatio typo (a pre-existing bug in the
recordScene branch I deleted)
Cascading fixes:
- save_manager.ts loadScene/loadCache: drop calls to the deleted
timeline.scrub and timeline.checkEditorCanPlay
- video_generation.ts videoProcessingPipeline: stub the
engineFrameBuffers.payloadZip path with a TODO; the video upload now
always errors via the existing handleError. The full rewiring is the
same TODO as recordScene
- Controls3D UploadModalSplat onSuccess: drop the
editor.timeline.addLocalSplat call
- SidePanelTabs: drop a long-broken import of useMouseEventsSidePanel
(the Timeline/utils path it pointed to was already deleted before
this refactor); resize handle gets a TODO marker for Phase 6
Net effect on typecheck: 162 -> 144 errors in PageEnigma. Every
remaining error in PageEnigma predates this branch; no new TypeScript
errors introduced by the refactor.
Removes references to the deleted Queue/timeline subsystem from:
- Engine helpers: scene_manager_api, video_generation, helper. Their
Queue.publish calls (UPDATE_CHARACTER_ID/ADD_OBJECT/POP_A_TOAST/
DELETE_OBJECT) now write to the new Zustand store / addToast directly.
- Editor/EditorState: drop TimelineState class and its JSON variant; trim
EditorState/EditorStateJson down to just sceneState and metadata.
- Editor/scene.ts: drop the timeline animation-clip seeding path; restore
placeholder_manager (incorrectly classified as dead in the audit).
- Signals: collapse signals/objectGroup, signals/characterGroups, and
signals/shape to non-timeline scaffolds that update the store directly
with a TODO marker for the engine-side rewiring. Drop
loadObjectData/loadCharacterData/updateCharacters/updateObjectGroup
files (all timeline-only). Strip dndTimelineHeight + overTimeline from
signals/dragAndDrop. Add setEditorState helper to signals/engine.
- Comps: drop Queue.publish calls. Aspect-ratio menu and stylize-tab
state transitions go through signal setters; TOGGLE_CAMERA_STATE,
REFRESH_PREVIEW, GENERATE_VIDEO are stubbed with TODOs pending the
editor.ts rewiring. DragComponent simplified (timeline-drop branch was
unreachable). PageEditor drops timelineHeight/dndTimelineHeight layout
math.
- PageEnigma drops the useQueueHandler call.
The branch still does not compile: editor.ts (1864 lines) is the next
target -- it instantiates TimeLine + the five timeline engines and
publishes select/aspect-ratio events through the queue. Cleaning it up
is the focused work for the next commit.
Phase 1-3 of the 3D page refactor:
- Delete the timeline + event-queue subsystem (Queue/, useQueueHandler,
Editor/timeline.ts, Editor/Engines/, clips/, and the timeline-only
signal slices: timeline, timing, audioGroup, cameraGroup,
promptTravelGroup, utils/publishClip)
- Delete dead-code files with no live importers: lipsync.js,
retargeting.ts, preview_video_generation.ts, shaders.js,
placeholder_manager.ts, both composable_engines/*.tsx stubs,
contexts/TrackContext/, comps/TimerGrid, comps/ControlsVideo
- Add PageEnigmaStore.ts with the full state shape that will replace
preact-signals (scene objects, cameras, transform mode, layout, error
overlays, generation/stylize state)
- Strip signals/index.ts of deleted slice exports and reduce
signals/sizing.ts to just sidePanelWidth
Branch does not yet compile -- subsequent commits will strip
Queue/timeline references from editor.ts, the remaining signal files,
and component consumers, then build out actions/ and event hooks.
- 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
- Ctrl+Z/Cmd+Z was calling redo() instead of undo(); fixed and added
missing Ctrl+Shift+Z/Cmd+Shift+Z and Ctrl+Y redo bindings
- Wrap all async keybind handlers in try/finally so isProcessing is
always cleared even if undo/redo/copy/paste throws
- Fix === typo in disableHotkeyInput (comparison instead of assignment),
and use full value replacement so the Preact signal update is reactive