feat(cabinet): support exact sizing and run-aware editing - #753
Conversation
Items (e.g. solar panels) can now be placed on sloped roof surfaces. The placement system computes euler rotation from the roof surface normal so items sit flush on the slope instead of going inside. - Add roofStrategy to placement-strategies with enter/move/click/leave - Wire roof:enter/move/click/leave events in the placement coordinator - Add calculateRoofRotation in placement-math using surface normals - Support full 3D cursor rotation for sloped surfaces - Items on roofs are parented to the level with world-space rotation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… github.com:pascalorg/editor
… github.com:pascalorg/editor
| } | ||
| const onKeyUp = (e: KeyboardEvent) => { | ||
| if (e.key === 'Alt') altKey = false | ||
| } |
There was a problem hiding this comment.
Alt resize commits stale modifiers
High Severity
Resize ticks store altKey from key events and never re-run apply when Alt is pressed or released, while commit reuses lastModifiers from the last pointer move. Releasing the handle after toggling Alt without moving writes the opposite of the live preview, so a run can reflow or stay independent incorrectly.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 1d2af0d. Configure here.
| if (resolvedWallPosition) { | ||
| position = resolvedWallPosition.position | ||
| wallLocalX = resolvedWallPosition.wallLocalX | ||
| } |
There was a problem hiding this comment.
Size input snaps cabinets flush
Medium Severity
After a typed width, depth, or height change on a wall-snapped preview, placement is re-resolved with wall-clearance set to 0. Any existing gap to the wall is discarded, so a later size edit jumps the cabinet flush even when the user already entered a clearance or the preview was offset.
Reviewed by Cursor Bugbot for commit 1d2af0d. Configure here.
| useAlignmentGuides.getState().clear() | ||
| setMovingNode(null) | ||
| swallowNextClick() | ||
| return |
There was a problem hiding this comment.
2D drop ignores late Alt
Medium Severity
forcePlace is written only from pointermove altKey, and the drop path reads that stale flag instead of the pointer-up event. Holding Alt to force a cabinet over an opening after the last move still rejects the drop, while releasing Alt after a forced preview can still commit the invalid pose.
Reviewed by Cursor Bugbot for commit 1d2af0d. Configure here.
| !siblingConflict && | ||
| !moduleOverlapsWallOpening(moving, parent as CabinetNodeType, position, nodes) | ||
| ) | ||
| }, |
There was a problem hiding this comment.
3D move skips corner edge shift
Medium Severity
cabinetModuleParentFrame.onCommit calls syncCornerRunsFromSourceModule without previousModule, so the new edge-shift fallback never runs on 3D parent-frame moves. When derived L-run re-layout bails, a moved source module leaves the linked leg behind, unlike the 2D floorplan path that now passes the pre-move module.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 1d2af0d. Configure here.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
There are 5 total unresolved issues (including 4 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 48add69. Configure here.
| }) | ||
| : [] | ||
| const affectedIds = new Set<AnyNodeId>([moduleId, ...(run ? [run.id as AnyNodeId] : [])]) | ||
| for (const [id] of initialCornerPreview) affectedIds.add(id) |
There was a problem hiding this comment.
2D move leaks corner previews
High Severity
affectedIds is frozen from the initial no-op corner preview, so linked legs that only receive overrides after the module actually moves are never cleaned up. Overlay teardown only clears that start-time set, and commit only clears the moved module. After cancel, reject, or undo, L-runs can stay at the dragged pose.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 48add69. Configure here.


What does this PR do?
Wall- and opening-aware cabinet movement
Exact dimensions during placement
Continuous L-shaped cabinet runs
Full-run cabinet insertion
Balanced cabinet widths
Duplicate along a cabinet run
Wall-cabinet height workflow
Cabinet-matched finishes
Live L-corner width-resize preview
How to test
Place or drag cabinets near a wall containing a door or window in both 2D and 3D.
Start placing a cabinet and select the width, depth, height, or wall-clearance dimension.
Enable continuous cabinet placement and draw an L-shaped run.
Resize a cabinet width on a run with an attached L-shaped section in both 2D and 3D.
Place a cabinet into the interior of a full run.
Open the cabinet run settings and use Equalize widths.
Use Duplicate along run with different copy counts, spacing, and directions.
Select a wall cabinet and choose a height preset.
Run the automated checks:
bun run lintbun run checksbun run check-typesbun run build(passes with the existing Turbopack filesystem-tracing warning in the SQLite scene store)bun test packages/nodes/src/cabinet— 479 passing testsScreenshots / screen recording
Not added yet.
Checklist
bun devbun checkto verify)mainbranchNote
Medium Risk
Touches shared editor move/resize/placement infrastructure and complex cabinet run layout; regressions could affect non-cabinet movables or snapping defaults, though behavior is heavily covered by new cabinet tests.
Overview
Extends the movable and handle registries so kinds can veto invalid drops (
isValidPosition), preview linked nodes during parent-frame moves (previewOverrides), and react to Alt during linear resize (HandleDragModifiers). The generic 2D/3D move and resize paths wire those hooks through—green/red footprint feedback, cancel on invalid drop, Alt to force place, and Alt to bypass snap / resize a cabinet module without reflowing neighbors.Cabinet placement and editing gain typed placement dimensions (wall clearance, neighbor gaps, W×D×H) with clickable labels in the 3D placement box, floor-plan preview layer, and new 3D dimension guides; the placement-preview store tracks active dimension input. Snapping now defaults item/group moves to magnetic alignment instead of grid.
On the cabinet side: schema adds
panelReadyandwithFinishedEnds; geometry and run ops add panel-ready fridge fronts, decorative end panels, run insertion/array/equalize-width tooling, structure inheritance when extending runs, wall-opening checks for runs and modules, corner-run live preview on move/resize, and refined width-handle behavior (magnetic gap close, nested wall-cabinet-only resize). Flip hinge moves from floating quick actions into the compartment inspector.Reviewed by Cursor Bugbot for commit 48add69. Bugbot is set up for automated code reviews on this repo. Configure here.