Decouple action toolbar state from entity selection (#1898) - #1900
Merged
Conversation
Three interlocking editor selection/gizmo state fixes: - viewport: remove the stale inspector.cursor.isPlaying read in the transformmodechange handler. When the mode change came from a keyboard shortcut, this handler ran before the ActionBar listener that re-emits showcursor, so the cursor still read as paused and the transform gizmo silently failed to attach — leaving the toolbar showing translate/rotate with no gizmo. - inspector: re-emit objectselect when re-selecting the already-selected entity. select() dedupes on the same object3D, so once a selection got into a gizmo-less state, clicking the same entity could never repair it; only selecting a different object (or reloading) recovered. Reselect now rebuilds the selection box and transform-controls state. - shortcuts: emit showcursor before transformmodechange for t/e/l (and their legacy w/s aliases), matching the ActionBar button path so no mode-change listener can observe a paused cursor. - ActionBar: only light the hand-tool button when the hand tool is actually engaged. Selecting a data-no-transform entity (e.g. an autocreated clone from street-generated-clones) also painted the hand button active, which reads as the editor being wedged in a phantom "hand" state with no transform gizmo and no visible way out. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wjM3aCMUpQ4d78XihJtgw
Core fix for the stale-canvas-state class of #1898, refocusing on the selection box painted in the viewport: - OrientedBoxHelper: union the cached _batchLocalBbox of EVERY descendant, not just the tracked root. Batching strips member mesh trees and stashes a per-member local AABB, but the helper only consulted the root's own cache — so containers of batched members (a managed street, a buildings container) sized their box from whatever unstripped geometry remained. Late repacking strips more meshes as a session goes on, so the same selection could compute a shrinking — eventually empty — box an hour in. - OrientedBoxHelper: when the computed box is empty, collapse the vertex buffer to a degenerate point instead of returning early. The early return left the PREVIOUS selection's box vertices in the geometry while the helper was repositioned to the new object's transform and shown — painting a wrong-size, wrong-place selection box. - viewport objectselect: the deferred model-loaded selection-box paint now checks the entity is still the current selection before drawing. - raycaster: emit 'raycastermouseleave' when the raycaster is disabled (hand tool, viewer mode). The hover box was only cleared from the polling wrapper, which stops on disable, freezing the last hover highlight on screen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wjM3aCMUpQ4d78XihJtgw
Mode is tool state, not per-object state. Selecting a data-no-transform entity previously disabled the translate/rotate buttons outright, locking the user out of switching modes until they selected something else. The buttons now stay clickable and keep showing the current mode's active highlight; they render dimmed (new .inapplicable style) while the current selection can't be transformed. The gizmo layer independently refuses to attach to no-transform entities, so allowing the mode switch is safe. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wjM3aCMUpQ4d78XihJtgw
…coupling (#1898) Re-review of the original report showed the perceived selection-box misalignment was not a defect: the box correctly represented the bounds of the selected object's hidden layers (ground/buildings), so it only appeared wrong. The speculative fixes to OrientedBoxHelper, the raycaster hover clearing, the shortcut showcursor ordering, and the reselect re-emit are reverted since they cannot be verified against a real error. What remains is the confirmed pareto-superior toolbar behavior: selection never changes the action bar state. The hand button lights only when the hand tool is engaged, and translate/rotate stay clickable — dimmed, not disabled — when the selected entity carries data-no-transform. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wjM3aCMUpQ4d78XihJtgw
kfarr
marked this pull request as ready for review
August 1, 2026 16:30
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Narrowed follow-up to #1898. Re-review of the original report showed the perceived selection-box misalignment in the 3D canvas was not a defect: the box correctly represented the bounds of the selected object including its hidden layers (ground/buildings), so it only appeared wrong. Earlier commits on this branch that speculatively changed
OrientedBoxHelper, raycaster hover clearing, shortcut event ordering, and reselect behavior have been reverted — they could not be verified against a real error and current behavior is correct.What remains is the confirmed improvement: selection never changes the action toolbar state. Mode is tool state, not per-object state.
Changes (
ActionBar.component.jsx,ActionBar.module.scssonly)data-no-transform(e.g. autocreated clones fromstreet-generated-clones, which aren't listed in the layers panel) painted the hand button as active — reading as the editor being wedged in a phantom "hand" state.disabledboth buttons, locking the user out of switching modes until they selected something else. They now keep the current mode's active highlight and remain clickable; while the selection can't be transformed they render dimmed (new.inapplicablestyle) to signal the tool won't act on this selection. Allowing the switch is safe because the gizmo layer independently refuses to attach todata-no-transformentities.Behavior matrix (with a
data-no-transformentity selected)Manual test
Click a street-spawned car twice (first click selects the lane, second the car — an autocreated
data-no-transformclone). Production: the hand button lights as if the hand tool were engaged and translate/rotate go dead-disabled. This branch: the toolbar keeps showing the user's chosen mode (dimmed), all mode switches still work, and selecting any movable object restores normal styling and the gizmo.Testing
npm run test:modern— 756 passing;npm run test:core— 189 passing🤖 Generated with Claude Code
https://claude.ai/code/session_019wjM3aCMUpQ4d78XihJtgw