Adjustments pass: three 0.185, undo/cancel semantics, guide & panel fixes#496
Conversation
Right-click over a rotation/move/resize handle started the gesture and stopPropagation()'d, fighting the camera orbit. Guard every gesture starter (shared useHandleDrag, group rotate gizmo, wall endpoint/height/ move, fence move, roof trim) with event.button !== 0 before it swallows the event. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Derive the metric/imperial default from the IANA timezone (US, Liberia, Myanmar zones -> imperial; anything else -> metric), falling back to an explicit locale region subtag only when no timezone resolves. Timezone tracks actual location, unlike navigator.language where en-US is a common default far outside the US. The unit is only persisted once the user explicitly sets it, so an untouched preference keeps tracking location; existing persisted values are treated as explicit and left alone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hadow
Flipping a light's castShadow at runtime crashes three r184's WebGPU
renderer: toggling off disposes the shadow map's GPU texture, but the
node builder cache evicts with the post-toggle key, so the shadows-on
entry survives still referencing the destroyed texture. Re-enabling
reuses that stale state and every frame submit fails with
GPUValidationError ("Invalid CommandBuffer from CommandEncoder").
Keep castShadow static and drive the user-facing toggle through the
Canvas shadows prop (renderer.shadowMap.enabled), which rebuilds
materials without disposing shadow resources.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Undo could step back past the scene load into the pre-load (empty) state, wiping the whole project — which autosave would then persist. Two defects: clearSceneHistory() had zero call sites, so every load left the empty pre-load state in zundo's pastStates; and setScene wrote the store twice, recording a half-normalized intermediate as a second undo target. - applySceneGraphToEditor, JSON import, and reset-to-default now clear history so the loaded scene is the undo floor - setScene collapses to a single tracked write (final state identical) - clearSceneHistory also resumes tracking so a load landing inside a pause window can't strand undo recording off Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
r185 renames directionToColor/colorToDirection to packNormalToRGB/ unpackRGBToNormal and splits SSGI's packed rgba output into separate AO (getAONode, single channel) and GI (getGINode) textures; wind-node's positionLocal reads become positionGeometry. @types/three stays at 0.184.1: the 0.185 typings send tsgo's inference into unbounded allocation (microsoft/typescript-go#2125 class — it ate ~70GB/90s and OOM-killed the machine). viewer/lib/tsl-compat.ts bridges the two renamed TSL exports with 0.184-typed signatures; drop it and the pin together once tsgo copes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
KTX2Loader.load throws before detectSupport has run, and materials created while a standalone capture canvas's renderer was still initializing cached themselves permanently texture-less — fabric slots rendered white in item thumbnails. .ktx2 loads now await whenKtx2Ready() (resolved by the first successful ensureKtx2Support), which is exported so hosts with standalone canvases can arm it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
applyCountryUnitDefault lets the host app feed an authoritative IP-derived country (e.g. Vercel's x-vercel-ip-country) into the unit default. Stronger signal than the timezone heuristic applied at store creation, still never overrides an explicit user choice. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A locked guide's hit-rect swallowed pointer-down via stopPropagation, so marquee selection couldn't start on top of it. Locked guides now let the event bubble to the svg root; click-to-select and the unlock affordance still work because a non-drag release fires onClick as before. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rotating a guide with the 2D handles gave no angle feedback. Reuse the registry layer's RotationAngleOverlay (wedge + degree chip) for guide rotate drags: sweeps from the grabbed corner's bearing at grab to its current snapped bearing, suppressed under ~0.5deg so a fresh grab doesn't flash a sliver. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The real-length field accepts natural measurement text via @pascal-app/lingo — 5'11", 180cm, 1m80, 12ft — parsed in the dropdown's unit (a bare number still means that unit, a typed unit wins). A faint '= 1.80 m' hint previews non-trivial input, unparseable text gets a clear error, and the odd onBlur force-reset to 0.0001 is gone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…doing Undo pressed while the mouse is mid-action (moving, drawing, dragging a handle) used to history-jump under the live pointer — stale carry, half gestures committing against a rewound scene. Now it reads as 'abort this action', exactly like Escape: - the global undo/redo arms first route through the tool:cancel path (covers build drafts, placement ghosts, move tools) and skip the history jump when anything was in flight (consumed, scope-active, or inputDragging); - pointer drags that only knew pointercancel (generic handle drags, group rotate, wall side/height handles, roof trim) gain the same capture-phase Escape/cmd+z keydown the group-move drags already had — fixing Escape for them too; - the existing capture-phase handlers (3D/2D group move, 2D registry move overlay) additionally accept cmd+z as cancel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Follow-up to cd83027, which only let LOCKED guides bubble pointer-down. An unlocked, unselected guide also swallowed the event for nothing (no translate drag starts), so marquee selection could never start on top of it. Now only the one case that uses the event consumes it — selected + unlocked → translate drag — and everything else bubbles to the svg root. Click-to-select still works: a non-drag release never crosses the box-select threshold, so the trailing click fires the guide's onClick. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The preset/item placement flow (useDraftNode + placement coordinator) registers no interaction scope and holds no pointer, so the cmd+z cancel guard from c699d74 saw it as idle and history-jumped mid-placement. Paused scene history is the universal tell — the draft cycle (and every adopted-move session) keeps temporal paused for the whole gesture, and an undo against a paused store lands on a stale baseline anyway. Treat !isTracking as in-flight. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… undo 4ffda72 stopped the history jump during preset/item placement but left the draft alive: the item tool passes no coordinator onCancel — it is Escape's fall-through (switch to select, unmount the tool) that actually destroys the draft. Extract that fall-through and run it from the cmd+z path too whenever a gesture is live and nothing consumed tool:cancel, so cmd+z now behaves exactly like Escape end to end. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Selecting a guide clears the node selection (handleGuideSelect), but the reverse was never wired: clicking a wall with a floorplan reference selected left selectedReferenceId set, and the panel manager's reference-first priority kept showing the floorplan panel until it was closed by hand. PanelManager now drops the stale reference the moment a scene selection (nodes or zone) appears. Also: the inspector's expanded state is shared across panel swaps by design, but it survived close/reopen too — deselecting everything now resets it, so a fresh selection opens the panel collapsed again. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nGeometry The three-0.185 migration renamed positionLocal to positionGeometry in the wind nodes, but positionLocal was never removed in r185 — and the two are not interchangeable here. NodeMaterial.setupPosition applies the instance transform by mutating positionLocal, then overwrites it with positionNode's output; reading raw positionGeometry therefore discarded every instance matrix — leaf cards rendered unscaled at tree-local coordinates (a giant canopy filling the sky) and grass/flower instances collapsed invisibly. Reading positionLocal (instance transform included) restores r184 behavior exactly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 4 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit edc4e2f. Configure here.
| try { | ||
| ;(ktx2Loader as unknown as { detectSupport: (r: unknown) => void }).detectSupport(renderer) | ||
| configuredRenderers.add(key) | ||
| resolveKtx2Ready() |
There was a problem hiding this comment.
KTX2 readiness never resolves
Medium Severity
The whenKtx2Ready promise only resolves on successful KTX2 support detection. If detectSupport consistently fails (e.g., renderer not ready), the promise never settles. This leaves .ktx2 texture loads indefinitely pending, resulting in white materials and no retry.
Reviewed by Cursor Bugbot for commit edc4e2f. Configure here.
| // Escape does — the mode switch is what actually cancels tools that hook | ||
| // their teardown to unmount (preset/item placement). | ||
| exitToSelectAfterUnconsumedCancel() | ||
| return true |
There was a problem hiding this comment.
Paused history blocks undo
Medium Severity
cancelInteractionForHistoryShortcut treats !useScene.temporal.getState().isTracking as an active gesture and calls exitToSelectAfterUnconsumedCancel() instead of running undo. If temporal history stays paused after a leaked or mismatched pause (direct temporal.pause() without a matching resume), ⌘Z clears selection and exits tools rather than stepping history.
Reviewed by Cursor Bugbot for commit edc4e2f. Configure here.
| } else if (e.key === 'Z' && e.shiftKey && (e.metaKey || e.ctrlKey)) { | ||
| if (isVersionPreviewMode) return | ||
| e.preventDefault() | ||
| if (cancelInteractionForHistoryShortcut()) return |
There was a problem hiding this comment.
Palette undo skips cancel
Low Severity
Keyboard undo/redo now calls cancelInteractionForHistoryShortcut() before runUndo / runRedo, but command-palette undo/redo still invokes runUndo / runRedo directly. Mid-drag or mid-placement, palette undo can history-jump under a live pointer—the behavior this PR explicitly removes from ⌘Z.
Reviewed by Cursor Bugbot for commit edc4e2f. Configure here.
| resetDesktopInspectorCollapsed() | ||
| } | ||
| }, [hasAnySelection]) | ||
|
|
There was a problem hiding this comment.
Reference panel wins over nodes
Medium Severity
Node selection clears selectedReferenceId in a useEffect, but desktop and mobile still render ReferencePanel whenever that id is set, before checking scene selection. After picking a wall or item from paths that only call setSelection, both ids can be set for a render, so the reference panel stays visible until the effect clears it.
Reviewed by Cursor Bugbot for commit edc4e2f. Configure here.


What does this PR do?
Bug-fix and adjustments pass across viewer + editor, plus the three.js migration:
three.js 184 → 185 (
acb9c55a)directionToColor/colorToDirection→packNormalToRGB/unpackRGBToNormal, SSGI's packed rgba output split into separate AO/GI textures). Note: wind-node keepspositionLocal— it was never removed in r185, and swapping it forpositionGeometrydiscards instance transforms (edc4e2f4).@types/threestays pinned at 0.184.1: the 0.185 typings send tsgo's inference into unbounded allocation (Unbounded memory consumption with recursive conditional types causes compilation OOM microsoft/typescript-go#2125 class — ~70GB/90s, OOM-kills the machine).viewer/lib/tsl-compat.tsbridges the two renamed TSL exports; drop it and the pin together once tsgo copes.Undo vs. gestures
pointercancel(resize/height/rotate handles, wall side handles, roof trim, group rotate) gain capture-phase Escape/⌘Z cancel — fixing Escape for them too.92e7b4ef) — undo can no longer wipe a freshly-loaded project.Viewer fixes
renderer.shadowMap.enabledinstead of per-lightcastShadow(r184 WebGPU cache-eviction crash) (b3283366)..ktx2preset-texture loads gate ondetectSupportviawhenKtx2Ready()— fabric/leather slots no longer capture white in standalone-canvas thumbnails (c9aef1fc).f51cd858), plusapplyCountryUnitDefaultfor hosts with an authoritative IP-derived country (3158ec7a).Editor / 2D floorplan
117c59db).cd830279,3b07955b).daf01b96).5'11",180cm,1m80) with a= 1.80 mhint (84a1c4f5).5488cae5).How to test
bun dev, open a scene — verify it renders on WebGPU with noLightsNode/GPUValidationErrorconsole errors (three 0.185).5'11"→ hint shows= 1.80 m.Screenshots / screen recording
N/A — verified live in the hosting app (Wawa House renders clean on WebGPU post-migration); happy to attach clips on request.
Checklist
bun devbun checkto verify)mainbranchNote
Medium Risk
Touches core scene undo (zundo), WebGPU rendering (three 0.185 + shadows/SSGI), and widespread gesture keyboard handling; changes are targeted but affect critical editor and viewer paths.
Overview
Bumps three.js to 0.185 across apps and packages (lockfile + root override), with viewer/editor post-processing and thumbnails updated for r185 TSL normal packing (
packNormalToRGB/unpackRGBToNormalviatsl-compat) and SSGI’s split AO/GI outputs instead of a single packed texture.Undo and in-flight gestures are reworked: ⌘Z/⌘⇧Z during moves, handle drags, and paused-history placement cancels like Escape (capture-phase handlers + shared
isHistoryShortcut), and global keyboard routing skips history when an interaction is active. Scene load/import/reset now callsclearSceneHistory()aftersetScene/clearScene, withsetScenedoing one tracked write after normalization andclearSceneHistoryresuming temporal tracking so undo is floored at the loaded baseline.Viewer: shadows toggle drives
renderer.shadowMap.enabled(not per-lightcastShadow) to avoid r184 WebGPU validation failures;.ktx2loads wait onwhenKtx2Ready()afterdetectSupport; default metric/imperial follows timezone/locale until the user sets a unit (applyCountryUnitDefaultfor hosts).Editor / floorplan: primary-button-only handle activation; live guide rotation wedge; reference-scale lingo length input; guide pointer-down only consumes events when selected+unlocked (box select on guide images); selecting scene nodes clears stale reference selection and resets inspector collapse; settings import/reset clear undo history.
Reviewed by Cursor Bugbot for commit edc4e2f. Bugbot is set up for automated code reviews on this repo. Configure here.