Commit Graph
14509 Commits
Author SHA1 Message Date
Tanish Manku 02b5435884 PageScene: extract Object Panel color picker into actions/setObjectColor
Closes the last UI-layer direct ColorAction construction. Same shape
as the toggleObjectLock / toggleObjectVisibility / transformObject
helpers — the view passes editor + uuid + new value; the helper
reads the before-state from the live engine, applies the change, and
records the undo entry.

Bonus correctness: before-state now comes from obj.userData.color
instead of the panel's local React `color` state. The two diverge if
the user undoes a color change (engine reverts but the local React
ref doesn't), so the engine-read shape produces the right diff
entries even when the panel state is stale.
2026-05-01 04:02:20 -07:00
Tanish Manku 5e8bb32692 PageScene: extract Outliner lock/visibility into actions/ helpers
Outliner.tsx imported LockAction and VisibilityAction directly and
mixed engine + Zustand + history calls in its click handlers,
breaking the layering convention the rest of actions/ follows
(addObject, transformObject, etc. — UI calls one helper; the helper
owns engine + store + history orchestration).

actions/toggleObjectLock and actions/toggleObjectVisibility now wrap
the engine action class + store toggle + history record in one
function. Each reads the before-state from the live engine so the
view doesn't have to plumb it.

Outliner.tsx drops the engine-action imports; handlers shrink to a
single call.

Color picker recording in ControlPanelSceneObject still constructs
ColorAction directly. Same pattern applies (actions/setObjectColor)
— easy follow-up if full layering symmetry is wanted.
2026-05-01 03:59:03 -07:00
Tanish Manku 3873dae52d PageScene: extract panel transform recording into actions/transformObject
The .tsx file shouldn't construct UndoableAction instances or call
HistoryManager directly — those are engine-level concerns that other
action helpers (addObject, addCharacter, addShape) already keep behind
the actions/ layer.

actions/transformObject.ts exposes beginTransformSession(editor, uuid)
which returns a session handle with one method, commit(). Open the
session on first edit, call commit() on selection change / panel
unmount. The session:
- snapshots the before-state in the constructor (via TransformAction)
- on commit, captures after-state and pushes a single undo entry only
  if the transform actually changed
- always re-syncs the Zustand objectPanel slice from the engine on
  commit so currentSceneObject reflects what the user just committed

ControlPanelSceneObject swaps its TransformAction import + manual
record/commit logic for the session helper. The view only owns the
"when does a session begin/end" boundary now; the action layer owns
the recording + store sync.
2026-05-01 03:50:21 -07:00
Tanish Manku 4956313723 PageScene: record Object Panel transform inputs via TransformAction
Closes the last undo/redo feature-parity gap vs. main. Main captured
panel-input transforms via the mouseup-diff in check_scene_for_updates
(only worked when the user happened to fire a subsequent mouseup);
this branch had no equivalent until now.

Implementation:
- A useRef holds a pending TransformAction. beginPanelTransform()
  constructs one on the first detected change for the currently-shown
  object — the constructor captures the before-state of the live
  scene object before the inputsUpdated useEffect dispatches the
  engine write.
- All three change handlers (position / rotation / scale, including
  the uniform-scale path) call beginPanelTransform() when an actual
  diff is detected.
- The selection-change useEffect's cleanup function runs
  commitPanelTransform() — captures the after-state and pushes the
  entry only if the transform actually changed. This fires both on
  selection switch (panel reloads for a new object) and on unmount.

The TransformAction stashes the uuid at construction, so commit
correctly targets the original object even if the panel has already
reloaded for a new selection by the time cleanup runs.
2026-05-01 03:44:38 -07:00
Tanish Manku 3c8fa9010b PageScene: SaveManager takes Deps, no Editor reference
Resolves the longstanding `// TODO Refactor remove editor.` Drops
the Editor import; constructor takes a SaveManagerDeps surface
inline-built by Editor (Phase 2 idiom — same shape as
CameraControllerDeps, GizmoControllerDeps, etc.).

Deps surface (10 fields, single-purpose):
- getVersion / setVersion — engine version (load may upgrade)
- getActiveScene — proxy serialization target
- getRenderer — for save thumbnail
- removeTransformControls — pre-save cleanup
- getCamera / refreshCamObj / changeRenderCameraAspectRatio — camera
- setPositivePrompt — restore prompt on load
- saveSceneState / loadSceneState — backend API

current_scene_media_token moves into SaveManager as private state
(only the load path used it; nothing outside SaveManager read it).
The field on Editor is dropped along with the two `= null` writes
in the constructor and initialize.

CameraController gains refreshCamObj(scene) — re-anchors cam_obj to
the named entity in the scene + sets layers. Replaces the inline
post-load setup that SaveManager used to do directly. Editor.initialize
also routes through the same method now (was inline at line ~410).
2026-05-01 03:40:50 -07:00
Tanish Manku 8780935b62 PageScene: strip dead code from SaveManager and Editor
Pure removals, no behavior change. Sets up the SaveManager Deps
refactor in the next commit.

SaveManager:
- Drop unused saveData (the live caller is getSceneJson + saveScene
  which goes through saveSceneState directly).
- Drop unused computeSceneChecksum + checkSumData + the
  computeHashForBrowser helper. Zero callers anywhere.
- Drop the editor.generating_preview = true/false bracket inside
  saveScene. The field has no readers — all the noise about "stop
  control panel from flipping out" is dead with VICCCCCCCCCCCCCCCTORRRRRRRR.

Editor:
- Drop generating_preview field (no readers anywhere).
- Drop current_scene_glb_media_token field + the two writes (no
  readers anywhere; suspected leftover of an older save format).
2026-05-01 03:38:07 -07:00
Tanish Manku 3681f4faef PageScene: route shape recreate through the geometry key
Shapes redo'd to broken / invisible objects because the snapshot
captured obj.name = "Cube" (the display label addShape sets after
creation), but scene.instantiate's geometry switch only matches the
geometry key ("Box", "Sphere", "PointLight", ...). Calling
instantiate("Cube", pos) fell through with no matching geometry.

Fix: addShape stashes the geometry key in userData.shapeKey before
overriding obj.name. recreateFromSnapshot picks shapeKey when
present (falls back to snap.name for non-shape paths), then restores
obj.name = snap.name after the create returns so the display label
sticks. Non-shape recreations are unaffected — their userData has no
shapeKey, so the fallback to snap.name keeps the existing flow.
2026-05-01 03:26:31 -07:00
Tanish Manku 62094e5a33 PageScene: drop recordLockChange from SelectionBridge deps
After the migration to LockAction, recording is the call site's
responsibility (Outliner.tsx pushes a LockAction directly). Drop
the recordLockChange dep callback and the bridge's auto-record
inside lockUnlockObject — the bridge stays history-agnostic.
2026-05-01 03:03:01 -07:00
Tanish Manku 28b7444697 PageScene: migrate call sites to UndoableAction; strip legacy HistoryContext
Every recording site now constructs an action class and passes it to
record(). HistoryManager loses all per-kind methods, ctx/HistoryContext,
HistoryEntry, the wrap helper, the legacy entry factories, and the
pendingTransform field — down to a generic stack with one external
method (record) plus undo/redo.

editor.ts shrinks dramatically: the ~70-line inline HistoryContext
deps blob is gone, replaced by `new HistoryManager({ capacity: 64 })`
on its own. Gizmo onDraggingChanged now stores a TransformAction
between dragstart and dragend; commits and records via record(...) at
dragend. SelectionBridgeDeps drops recordLockChange — lock recording
moves to the Outliner call site via new LockAction(...).

Migration map:
- actions/addObject, addCharacter, addShape:
    history.recordCreate(obj) → history.record(new CreateAction(editor, obj))
- editor.deleteObject:
    history.recordDelete(obj) → history.record(new DeleteAction(editor, obj))
- editor.ts gizmo onDraggingChanged:
    begin/endTransform → activeTransform: TransformAction with commit()
- keymap.ts paste:
    history.recordCreate(obj) → history.record(new CreateAction(editor, obj))
- comps/Outliner/Outliner.tsx:
    lock — new LockAction(selection, uuid, before, after)
    visibility — new VisibilityAction(editor, uuid, before, after)
- ControlPanelSceneObject color picker:
    history.recordSetColor(...) → history.record(new ColorAction(editor, uuid, before, after))

LockAction and VisibilityAction self-refresh the outliner after
apply/revert, matching the pattern CreateAction/DeleteAction already
use. HistoryManager has no global onReplay refresh hook — actions
that need UI sync handle it themselves.

The "adding a new action" test: a future "duplicate object" action is
just a new file under engine/editor/actions/DuplicateAction.ts
implementing UndoableAction. Zero edits to HistoryManager, zero edits
to editor.ts.
2026-05-01 03:02:08 -07:00
Tanish Manku f4005603f6 PageScene: add concrete UndoableAction classes
Six self-contained classes under engine/editor/actions/, one per
action kind. Each implements UndoableAction, captures its own state
in the constructor, and runs apply/revert against subsystem refs
held privately. Adding a new kind = a new file here, no edits
elsewhere.

- CreateAction: snapshots an obj, recreates on apply (redo),
  removes via utils.deleteObject on revert (undo). Uses the
  non-recording delete path so undo of create can't recurse.
- DeleteAction: mirror — removes on apply, recreates on revert.
- TransformAction: encapsulates begin/end transaction state. Caller
  constructs at dragstart (captures before), calls commit() at
  dragend to capture after + check for a no-op move. records only
  if commit() returns true.
- ColorAction: simple before/after pair, routes through
  activeScene.setColor.
- LockAction: routes through SelectionBridge.setLockState so the
  gizmo attach/detach side effects fire on undo.
- VisibilityAction: writes obj.visible + userData.visible directly.

No call sites use these yet — that's the next commit. HistoryManager
is unchanged; it already accepts UndoableAction via record().
2026-05-01 02:57:28 -07:00
Tanish Manku a4c3be4c99 PageScene: introduce UndoableAction polymorphism + isReplaying guard
The user critique on the original HistoryManager design was sharp:
adding a new action kind required edits to four places (HistoryContext
interface, deps wiring in editor.ts, entry factory, recorder method).
Anything shared between kinds becomes a chokepoint that grows.

Introduce the proper Command pattern:

- UndoableAction interface — single shape every action class
  implements. HistoryManager's external API becomes record(action) +
  undo/redo. Future kinds add a new class file under
  engine/editor/actions/; HistoryManager itself never grows.
- isReplaying flag — record() no-ops while undo/redo is mid-flight,
  so any side-effect chain inside an action's apply/revert (e.g.
  delete-undo reaching editor.deleteObject which calls
  history.recordDelete) can't poison the stack. Belt-and-suspenders
  with the per-kind silent-setter routes the action classes will use.
- engine/editor/actions/snapshots.ts — shared ObjectSnap /
  TransformSnap types, snapshot helpers, transformsEqual,
  writeTransform, and recreateFromSnapshot. Used by the action
  classes added in the next commit.

Legacy recordCreate / recordDelete / begin+endTransform /
recordSetColor / recordSetLocked / recordSetVisible methods stay as
thin wrappers that bind the current HistoryContext into an
UndoableAction via wrap(). Call sites compile unchanged. They're
removed once all sites migrate to record(new XAction(...)).
2026-05-01 02:56:16 -07:00
Tanish Manku d9680ae0a8 PageScene: route lock undo through gizmo side effects
SelectionBridge.lockUnlockObject toggled userData.locked AND
attached/detached the gizmo, but HistoryContext.setLocked only did
the userData write. After Ctrl+Z the lock flag reverted but the
gizmo state stayed wrong.

Extract the side-effect chain (setObjectLocked + gizmo attach/detach
+ updateSelectedUI) into a private SelectionBridge.applyLockState
helper. Both lockUnlockObject (toggle path) and the new
setLockState (history-replay path) call it. Add a SceneUtils
direct-set helper (setObjectLocked) so applyLockState can target a
specific state instead of toggling.

Wire HistoryContext.setLocked through selection.setLockState so undo
of a lock toggle re-runs the gizmo attach/detach. The
recordLockChange dep stays for now; it moves to the call site in a
later commit when the action layer migrates to LockAction.
2026-05-01 02:54:19 -07:00
Tanish Manku 4a837d49a7 PageScene: apply position to non-shape loaders
Four loader paths in scene.ts passed `position` to the placeholder
manager but never copied it onto the actual loaded object — so a
GLB / MMD / Splat / image / video drop showed the placeholder at the
cursor and then jumped to (0, 0, 0) when the asset finished loading.
Shapes were unaffected because Scene.instantiate already does
`obj.position.copy(pos)`.

Mirror the existing loadObjectFromUrl template (line 553-555):
- loadObject image/video branch — copy position onto the result of
  instantiate("Image::"+url)
- loadMMDWithPlaceholder — copy onto mmd before scene.add
- loadSplatWithPlaceholder — copy onto splat before scene.add
- loadGlbWithPlaceholder — copy onto child_result before scene.add
2026-05-01 02:53:04 -07:00
Tanish Manku 3f3da24ab6 PageScene: strip legacy diff-based undo machinery from SceneManager
Now that every mutation site records explicitly through HistoryManager,
SceneManager no longer needs to own an undo stack or reverse-engineer
edits from per-mouseup scene-state diffs.

Removed from SceneManager:
- undoStack, undoIndex, lastSceneState, copiedObject is kept
- undo, redo, dropRedoHistory methods
- getSceneState, is_creation, is_deletion, is_userdata, is_transformation
- add_creation_undostack, check_scene_for_updates
- onMouseMove/onMouseClick/onKeyDown/onMouseDown/onMouseUp wrappers
  (no callers — pointer events now go canvas → useViewportPointer →
  mouse_controls directly)
- attachEventListeners/detachEventListeners (already empty stubs)
- Constructor params: devMode, updateOutliner, is_character (unused
  after the gut)
- updateOutliner field — paste() refresh moved to keymap's paste handler

Also:
- engine/Commands.ts deleted — its CreationSceneItem / DeletionSceneItem
  / TransformSceneItem / UserDataSceneItem and supporting types are
  obsolete (HistoryManager owns its own entry factories).
- useViewportPointer.onPointerUp reverts to mouse_controls.onMouseUp
  directly (the sceneManager indirection only existed to call
  check_scene_for_updates).
- Editor.initialize drops attachEventListeners/detachEventListeners
  calls and the three trailing constructor args.

scene_manager_api.ts: 477 → 265 LOC. SceneManagerAPI interface trims
to what it actually does — object lifecycle (create/retrieve/delete/
update), selection, outliner rendering. Object Panel transform-input
recording is the one remaining gap from the old diff path; tracked
as a follow-up.
2026-05-01 01:53:41 -07:00
Tanish Manku 39f4c84241 PageScene: record color changes via HistoryManager
Color picker is the third interactive surface (after gizmo drag and
outliner toggles) that mutates an object. Wire it the same way:
record in the onChange handler (input type="color" fires once per
committed pick, so one undo entry per user choice).

Object Panel transform inputs (position/rotation/scale text fields)
are still on the legacy diff path. Wiring them is one more pass —
they need a coalescer because each keystroke flushes to the engine.
Tracking that as a follow-up.
2026-05-01 01:49:11 -07:00
Tanish Manku bab0f62973 PageScene: record gizmo transforms via HistoryManager
The gizmo's onDraggingChanged fires once with dragging=true at
dragstart and once with dragging=false at dragend. Wire those
boundaries to history.beginTransform / endTransform so each drag
becomes one coalesced undo entry. No-op moves (mouse-down + drag-up
without movement) are dropped automatically by HistoryManager.

Object Panel inputs and color edits still ride the legacy
check_scene_for_updates diff path; those are migrated separately
before the diff machinery is removed.
2026-05-01 01:47:05 -07:00
Tanish Manku 6632dd34cf PageScene: route create/delete/lock/visible/paste through HistoryManager
Wires creation, deletion, lock, visibility, and paste through the new
HistoryManager. Mutator-recorded — every external call site keeps
the same signature it had before; recording happens inside the mutator
or its dep callback, so callers don't grow new responsibilities.

Recordings added:
- actions/addObject, addCharacter, addShape: editor.history.recordCreate(obj)
  replaces the legacy add_creation_undostack call.
- editor.deleteObject: snapshots the target before delegating to
  utils.deleteObject so undo can fully reconstruct it.
- SelectionBridge.lockUnlockObject: takes a recordLockChange dep
  callback, records before/after locked state inline. No coupling
  back to HistoryManager from the bridge.
- Outliner.handleToggleVisibility: records visible before/after at the
  call site (hideObject lives on SceneManager which has no history dep).
- SceneManager.paste: now returns the new Object3D so keymap's paste
  handler can record it via editor.history.recordCreate. The legacy
  add_creation_undostack call inside paste is removed.

keymap.ts: undo/redo target editor.history; deleteSelected routes
through editor.deleteObject (so history records); paste captures the
returned object and records.

Transform recording (gizmo drag, panel inputs) and color stay on the
old check_scene_for_updates diff path until follow-up commits. Old
SceneManager undo machinery (undoStack, undo/redo methods,
add_creation_undostack, the diff helpers) is still in place but only
the diff path drives transforms now.
2026-05-01 01:46:21 -07:00
Tanish Manku 94430a8817 PageScene: introduce HistoryManager subsystem
New peer subsystem under engine/editor/ that owns the undo/redo stack.
Lands the foundation only — old SceneManager-based undo machinery
still drives undo/redo for now. A follow-up commit migrates every
mutation site over and removes the legacy diffing.

Design:
- Pure-data HistoryEntry: { label, apply(ctx), revert(ctx) }. Entries
  receive a HistoryContext when replayed; they don't hold subsystem
  references. Easy to test in isolation, immune to subsystem
  re-creation.
- The external API is the recordX methods (recordCreate, recordDelete,
  begin/endTransform, recordSetColor/Locked/Visible). Mutation sites
  call ONE method — entry construction stays in the manager. Adding
  a future action is "add an entry factory + a recorder method"; no
  churn at action sites.
- Async-serialized replay via a Promise chain: a Ctrl+Z mash during
  an in-flight asset reload can't interleave entry replays.
- onChange callback hook for a future store-bridge to publish
  canUndo/canRedo into PageSceneStore.
- Capacity 64 (vs. main's 16); transform/userdata no-ops are dropped.

Editor wires the HistoryContext inline in its constructor (Phase 2
deps idiom — same shape as CameraControllerDeps etc.). Closures over
`this` resolve sceneManager/activeScene lazily so construction order
inside the editor constructor doesn't matter.
2026-05-01 01:41:36 -07:00
Tanish Manku 51e9f046e2 PageScene: push creation entries to undo stack from add* actions
Pre-refactor, the queue handler (timeline.addShape/addObject/addCharacter)
called sceneManager.add_creation_undostack(obj) after creating the
THREE.Object3D. The actions/ extraction collapsed the queue path but
forgot to carry the undo push along, so dragging a new asset onto the
3D canvas left the undo stack untouched.

Push a CreationSceneItem from each of addObject, addCharacter, and
addShape after the engine call succeeds. check_scene_for_updates only
detects transforms / deletions / userdata diffs on mouseup; it does
not record creations, which is why this has to be an explicit call.
2026-05-01 01:22:24 -07:00
Tanish Manku 6721d2ded8 PageScene: route viewport pointerup through sceneManager
useViewportPointer.onPointerUp called editor.mouse_controls.onMouseUp
directly. That ran the interaction state machine (gizmo end, FK toggle,
selection click) but skipped editor.sceneManager.onMouseUp, which is
the only thing that calls check_scene_for_updates. Result: transform,
deletion, and userdata edits (color, lock, visibility) never made it
into the undo stack, so Ctrl+Z found nothing to undo.

Pre-refactor, SceneManager.attachEventListeners attached a window-level
mouseup that went through SceneManager.onMouseUp; the canvas-scoped
hook lost the same pass-through. Calling sceneManager.onMouseUp here
preserves all the mouse_controls behavior (it forwards internally) and
restores the post-mouseup undo capture.
2026-05-01 01:22:14 -07:00
Tanish Manku aff4a5616e PageScene: read camera/freeCamState/focalLengthToFov from CameraController
Phase 1 moved camera, freeCamState, and focalLengthToFov off Editor and
onto CameraController, but two camera-update blocks in PageEditor
(handleSelectCamera and handleAddCamera) still read editorEngine.camera,
editorEngine.focalLengthToFov, and editorEngine.freeCamState. Those
fields are undefined now, so the cam-update guards short-circuited and
the reset/random-camera buttons silently did nothing.

Sweep both blocks to read from editorEngine.cameraController.* and
collapse the chain into a local `cam` const.
2026-05-01 01:11:40 -07:00
Tanish Manku b95f6fdf91 PageScene: restore raycaster-based drop position
DndAsset.onPointerUp captured event.pageX/Y but threw them away; both
add* actions then defaulted position to origin. PageEditor's gallery
onImageDrop tried an NDC unproject but gated on editorEngine.camera,
which Phase 1's CameraController extraction left undefined — the guard
short-circuited and worldPosition stayed undefined.

Add pickDropPosition: a deps-pattern helper that mirrors the
pre-refactor timeline.getPos() — raycasts from the camera through the
cursor against scene children and returns the first hit (or origin if
the ray misses or the camera/canvas isn't ready). Same idiom as
CameraControllerDeps / GizmoControllerDeps from Phase 2; callers build
the deps inline so the helper stays decoupled from Editor and any
subsystem's import surface.

Both drop sites — DndAsset.onPointerUp and PageEditor's 3D gallery
onImageDrop — now call the helper and forward worldPosition to
addCharacter / addObject / addShape.
2026-05-01 01:11:11 -07:00
Tanish Manku 3af5291150 PageScene: drop dead create_parim helper
create_parim was a one-line passthrough to activeScene.instantiate
that the pre-refactor queue path used. After actions/addShape was
rewired to call sceneManager.create with the "Parim" sentinel,
create_parim has zero callers anywhere in the artcraft frontend.
2026-05-01 01:10:20 -07:00
Tanish Manku 2710e875ec PageScene: route addShape through "Parim" sentinel so shapes appear
actions/addShape passed item.media_id ("Box", "Sphere", ...) as the
routing token to SceneManager.create. That hits the
loadObject(media_token, ...) branch and tries to fetch a media URL
for "Box", which fails — the shape never lands in the scene.

Pass "Parim" as the routing sentinel and item.media_id as the
geometry name so create() forwards to scene.instantiate(name, pos),
which has the geometry switch ("Box" -> BoxGeometry, etc.). Then
overwrite obj.name with the display label so the outliner reads
"Cube" instead of "Box".

Restores the pre-Phase-4 behavior of editorEngine.create_parim(media_id, pos)
that the queue path used.
2026-05-01 01:10:14 -07:00
Tanish Manku 5e05877619 3D editor.ts cleanup (#1461) 2026-05-01 00:10:50 -07:00
Tanish Manku fec717057e Re-add shaders.js used by chromakey.js 2026-04-30 14:32:53 -07:00
Tanish Manku 9ad3b86524 Audio: Move sound system into SoundManager, fix bootstrap + master toggle (#1455)
- Move sound catalog registration off PageScene mount and into a single
SoundManager.install() call in GlobalSettingsManager so sounds exist at
app boot (previously silent until PageScene mounted).
- Honor the play_sounds master toggle in all five event playback sites
via SoundManager.playEvent helpers. Settings preview keeps firing
regardless (handled by SoundManager.playPreview).
- Replace per-callsite GetAppPreferences + SoundRegistry boilerplate in
the four generation event hooks and the file-deleted handler with
one-line SoundManager.* calls.
- Dedupe SOUND_OPTIONS: AudioSettingsPane now reads
SoundManager.OPTIONS, derived from a single declarative SOUNDS array
(label-bearing entries are user-selectable; others are still registered
for code-driven use).
- Narrow the soundboard public barrel to SoundManager + SoundOption
only; SoundRegistry and SoundEffect are now lib-internal.
2026-04-30 09:07:28 -07:00
Tanish Manku 2b5e509a29 PageScene: move engine lifecycle flags + canvas refs into PageSceneStore
The last batch of PageScene-owned signals — `is3DPageMounted`,
`is3DEditorInitialized`, `is3DSceneLoaded`, and the three canvas-ref
signals (sceneContainer, editorCanvas, camViewCanvas) — now live on
the Zustand store. EngineProvider, the canvas components, the
useEditorCanvas hook, and the cross-app TopBar consumer all read from
the store instead. The helper signals in editor.ts are gone; the
`set3DPageMounted` / `setIs3DEditorInitialized` / `setIs3DSceneLoaded`
exports stay as thin wrappers around the store actions for existing
callers.
2026-04-29 15:16:29 -07:00
Tanish Manku 28db87c1b2 PageScene + libs: drop camera/promptBox signals, switch shared libs to plain props
PromptBox3D and CameraSettingsModal now accept plain values + setters
instead of `Signal<T>` props. The camera/focalLengthDragging/
isPromptBoxFocused signals are gone — store is the single source of
truth. Engine code (editor/save_manager/scene) reads/writes via
`usePageSceneStore.getState()` and store actions. Demo asset signals
are now plain constants (they were never reactive).
2026-04-29 15:12:13 -07:00
Tanish Manku 292cf33f57 PageScene: route setCameraAspect + save_manager directly through PageSceneStore 2026-04-29 15:00:48 -07:00
Tanish Manku 58b7cd0cd4 PageScene: route useFreeCam through PageSceneStore for prompt-box focus check 2026-04-29 14:59:54 -07:00
Tanish Manku 101b735c35 PageScene: migrate outliner signals to PageSceneStore 2026-04-29 14:59:06 -07:00
Tanish Manku 32291ce080 PageScene: migrate objectPanel signals to PageSceneStore 2026-04-29 14:56:05 -07:00
Tanish Manku cd9c06d2ce PageScene: migrate precisionSelector signals to PageSceneStore 2026-04-29 14:54:24 -07:00
Tanish Manku a780779f7a PageScene: migrate dragAndDrop signals to PageSceneStore 2026-04-29 14:53:10 -07:00
Tanish Manku 076b7fe4a4 PageScene: extend PageSceneStore with drag/objectPanel/outliner/precisionSelector fields 2026-04-29 14:52:05 -07:00
Tanish Manku 7eef728d22 PageScene: trim api_manager + drop dead model/error files
api_manager.ts goes from 460 lines to ~135. The video-render
pipeline (uploadMediaFrameGeneration, stylizeVideo,
StylizeVideoParams, getMediaBatch, getMediaFile, uploadMedia,
gltfExport) was unreachable after videoGeneration was deleted.
Surviving public methods:

- saveSceneState (called by save_manager)
- loadSceneState (called by save_manager)
- uploadMediaSceneThumbnail (used internally by saveSceneState)

Also drops the local ArtStyle / Visibility / MediaFile types —
they only fed the deleted methods. The ArtStyle enum has a
separate copy in ~/enums/ArtStyle.ts that other code uses.

Delete two files that became unreferenced:
- engine/APIManagerResponseError.ts (duplicate of the local class
  in api_manager.ts; nothing imported it)
- models/generationOptions.ts (IGenerationOptions/GenerationOptions
  fed editor.generation_options, deleted last commit)

Net: -1 typecheck error, -326 lines.
2026-04-29 14:12:03 -07:00
Tanish Manku d09c7320fd PageScene: delete misc dead files surfaced by stylize cleanup
Files with zero importers across the artcraft app:

- constants/RandomTexts.ts — RandomTextsPositive/Negative seeded
  prompt suggestions in the deleted StylizeTab.
- composable_engines/scene_format.tsx — empty (1 line).
- constants/page.ts and the matching currentPage signal in
  signals/global.ts — Pages enum + signal, neither read.
- enums/StylizeTabPages.ts and enums/AudioTabPages.ts — re-exported
  via enums/index.ts but no consumer.
- models/track.ts, models/message.ts, models/voice.ts, models/tts.ts
  — timeline / TTS / audio-panel types from deleted features.
- styleList.ts — used to seed selectedArtStyle's default; that
  store field is gone.
- models/types.tsx had a duplicate AudioTabPages enum (separate
  from the deleted one in enums/) — drop the duplicate too.

models/index.ts and enums/index.ts trimmed to match. Error count
unchanged (151) — none of these had broken type errors anyway.
2026-04-29 14:08:08 -07:00
Tanish Manku 0ca91e29b7 PageScene: drop orphaned stylize state
Strip the stylize fields that nothing reads anymore now that the
StyleMoreOptions / StyleSelection UI is gone. From PageSceneStore:
selectedArtStyle, upscale, faceDetail, styleStrength, lipSync,
cinematic, enginePreProcessing, globalIPAMediaToken, adapterImage,
positivePrompt, negativePrompt, isUserInputPositive,
isUserInputNegative, showNegativePrompt — and the matching setters
+ resetSceneGenerationMetadata. Drop the styleList / ArtStyle
imports; both go unused here.

From editor.ts: drop negative_prompt, art_style,
engine_preprocessing, generation_options, globalIpAdapterImage.
Keep positive_prompt — PromptBox3D writes it via setEnginePrompt
(PageEditor.tsx) and save_manager round-trips it.

Trim save_manager.loadFromJson to the prompt-only path (legacy
saves with stylize keys load fine; the now-removed if-guards just
silently ignored them anyway). Trim SceneMetadata to
{ positivePrompt, cameraAspectRatio } and the matching
SceneGenereationMetaData interface.

ControlsTopButtons.handleResetScene drops its now-removed store
call.
2026-04-29 14:07:03 -07:00
Tanish Manku 8af4657393 PageScene: delete orphan StyleMoreOptions and StyleSelection UI
Both component trees survived earlier cleanup but never render
from PageEditor or PageScene. They were the last UI consumers
of the stylize store fields; deleting them sets up a follow-up
that drops those store fields entirely.

Net: -17 typecheck errors (the orphan files were the source of
many missing-import errors).
2026-04-29 14:04:02 -07:00
Tanish Manku f8ee94e538 Revert moodboard-html files committed by mistake on this branch
cardLayout.ts, MoodboardHtmlColorPicker.tsx, findDropTarget.ts, and
CardNodeHtml.tsx were swept into the PageEnigma -> PageScene rename
commit (76add858c9) on this 3D-refactor branch. They belong on
moodboard-page-html.

The file content is preserved on the local-only moodboard-rescue
branch (pointing at 01e0e27037). To carry them back over:

  git checkout moodboard-page-html
  git checkout moodboard-rescue --     frontend/apps/artcraft/app/src/pages/PageMoodboard/cardLayout.ts     frontend/apps/artcraft/app/src/pages/PageMoodboardHtml/MoodboardHtmlColorPicker.tsx     frontend/apps/artcraft/app/src/pages/PageMoodboardHtml/interactions/findDropTarget.ts     frontend/apps/artcraft/app/src/pages/PageMoodboardHtml/nodes/CardNodeHtml.tsx
  git commit
2026-04-29 12:29:58 -07:00
Tanish Manku 01e0e27037 PageScene: route PoseModeSelector through getActiveEditor()
The Enter Pose Mode button reached through a window.__mouseControls
global handle that MouseControls' constructor set up. With
getActiveEditor() already exposing the live editor instance for
non-React callers (used in the camera-view toggle, the keymap),
the global is unnecessary.

Drop the (window as any).__mouseControls = this assignment in
MouseControls and the matching Window augmentation in
PoseModeSelector. Replace the click handler's reach with
getActiveEditor()?.mouse_controls?.toggleFKMode() — same shape
the keymap entry already uses.
2026-04-29 11:33:30 -07:00
Tanish Manku 794f65b517 PageScene: delete unused models/sidePanel.ts
The Tab interface in this file was the type for the deleted
SidePanel/tabList.tsx tab list. Grep across app/src/ confirms no
consumer remains; AssetModal uses its own local AssetTab type.
Drop the file and the matching re-export in models/index.ts.
2026-04-29 11:32:48 -07:00
Tanish Manku c53b407952 PageScene: drop unused imports surfaced by deletions
EditorStates is no longer referenced from editor.ts now that
PREVIEW is gone from the enum and switchPreview/switchEdit are
deleted. EngineCanvases.tsx never used useContext/EngineContext;
the imports were leftover from earlier work.

Net: -3 typecheck errors.
2026-04-29 10:03:23 -07:00
Tanish Manku e0611e0147 PageScene: drop document.mouseover gate + dead selectedCanvas
The mouseOverEventHandler / enableFreeCamControls /
disableFreeCamControls trio existed to track when the cursor was on
the viewport canvas and toggle FreeCam.enabled. With useFreeCam
gating itself on editorState === CAMERA_VIEW, that gate is
redundant; the only remaining consumer of selectedCanvas was
MouseControls.onkeydown's space-bar branch, which is unreachable
after the previous keyboard rewrite.

Removes the last document.addEventListener call from PageScene.
2026-04-29 10:02:09 -07:00
Tanish Manku 8a03449128 PageScene: keymap + useViewportKeyboard hook
Replace MouseControls.onkeydown's giant if-else chain with a
declarative keymap:

- engine/keymap.ts: KeyBinding type + buildKeymap() returning every
  one-shot viewport shortcut as one entry. Each carries label /
  group metadata so a future Ctrl-hold cheatsheet overlay can read
  the same array. dispatchBinding() does the modifier match
  (treating Ctrl and Meta as equivalent for macOS).
- hooks/useViewportKeyboard.ts: attaches a single keydown listener
  that gates on hotkeyStatus + editable-element checks, then walks
  the keymap. Held movement keys (W/A/S/D etc.) live in useFreeCam
  since they're continuous; this hook is for one-shots only.

The listener is on document, not the canvas, deliberately: canvas
focus is finicky and would require the user to click the viewport
before T/R/G work. The editable-element guard prevents the keymap
from firing while inputs are focused.

MouseControls.onkeydown is now unreachable (SceneManager's window
listeners are no-op since the previous commit, and useViewportKeyboard
goes straight to keymap dispatch). The class itself stays for now —
it still implements selection / FK / pointer-lock state.
2026-04-29 10:00:56 -07:00
Tanish Manku e4af73c9ac PageScene: useViewportPointer hook (canvas-scoped)
The old SceneManager.attachEventListeners() attached window-level
mousemove / click / mousedown / mouseup / keydown handlers in dev
mode and dispatched them into MouseControls. Replace with two pieces:

- hooks/useViewportPointer.ts: canvas-scoped pointer listeners
  that call into editor.mouse_controls. NDC pointer coords are
  computed via the new engine/pointer.ts helpers (ndcFromClient,
  applyNdcToVector2). Selection clicks suppress while the
  free-cam owns input (CAMERA_VIEW state).
- engine/pointer.ts: pure NDC coordinate translation, no DOM
  side-effects.

SceneManager.attachEventListeners / detachEventListeners are now
no-ops; kept for back-compat with the editor lifecycle so future
follow-ups can carve them out cleanly.

MouseControls itself still owns selection / FK / drag state — its
dispatch methods (onMouseMove, onMouseClick, etc.) are now invoked
by the React hook instead of window listeners. A future PR can
dissolve MouseControls further; the goal here is "no window
listeners" + "React owns lifecycle".
2026-04-29 09:58:24 -07:00
Tanish Manku efa9a54bee PageScene: useFreeCam hook + delete free_cam.ts
The 446-line FreeCam class is gone. Its responsibilities split:

- Math (move/rotate vectors, pan/zoom from inputs, lookAt) lives in
  cameraMath.ts as pure functions, plus a freeCamFrameTick() that
  integrates the per-frame state.
- Listener attachment + drag/key state ownership moves into
  hooks/useFreeCam.ts. Listeners are scoped to the canvas (no
  window/document handlers); held-key state is reset on
  CAMERA_VIEW exit so a stale W keypress can't drift the camera.
- The shared FreeCamControlState struct flows through Editor via
  editor.setFreeCamState(state); the render loop reads it and
  calls freeCamFrameTick() each frame.

Side effects: keybinds_controls.ts now imports
isPointerLockSupported from browserChecks; helper.ts and
PageEditor's camera-select handler stop poking
.cameraViewControls and reset velocity via freeCamState instead.
PreviewEngineCamera's camera-view toggle now just flips the store
state — useFreeCam picks it up on its own (no more
enableFreeCamControls/disableFreeCamControls call site).
2026-04-29 09:55:40 -07:00
Tanish Manku 718b8261fd PageScene: extract cameraMath + browserChecks modules
Pure-function helpers carved out of FreeCam in preparation for the
hook rewrite:

- engine/cameraMath.ts: HeldMoveKeys / HeldRotateKeys types,
  moveVectorFromKeys, rotationVectorFromKeys, panFromDrag,
  zoomFromWheel, lookAtFromCamera, lerpVelocity. No DOM access, no
  signals.
- engine/browserChecks.ts: isSafariOrWebKit, hasApplePay, isSafari,
  isPointerLockSupported. Unrelated to camera math; were just
  hitchhiking in free_cam.ts.

free_cam.ts re-exports the browser checks for back-compat with
keybinds_controls.ts; both consumers will switch to the direct
imports when free_cam.ts is deleted in the next commit.
2026-04-29 09:50:16 -07:00
Tanish Manku 61c4936c29 PageScene: useEditorCanvas hook + tabIndex on viewport canvas
The Phase 6 hooks (useFreeCam, useViewportPointer, useViewportKeyboard)
need a React-shaped accessor for the canvas DOM node so their
useEffects re-run when the canvas mounts/unmounts. Promote the
existing editorCanvasSignal / camViewCanvasSignal into useEditorCanvas
/ useCamViewCanvas hooks that bridge signal -> React state via a
subscribe.

Add tabIndex={0} and outline:none to the editor canvas so it can
receive keyboard focus without showing a focus ring.
2026-04-29 09:48:59 -07:00