feat(units): apartments and hotel rooms as a zone-referencing overlay under building - #877
Conversation
Adds the `unit` kind (apartment / hotel room / commercial / common): a building child listing manually drawn zones, possibly across levels, with no geometry of its own. Containment, boundary walls, supports and the isolate list are derived (unit-containment.ts); reports sum member areas (unit-report.ts). Zone deletes strip members in the same commit; clones and level duplication remap members. Editor: Units section in the site panel (new unit, member rows, common group, warnings), active unit joins newly drawn zones in one history step, isolate unit through the viewer isolate filter, unit chip beside the level selector, command palette entries, unit inspector, zone inspector unit picker, and member tint + unit chip in 3D and 2D. MCP: create_unit, set_unit_members, list_units. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MpE7N6c5YfaS9V3rfYLnKN
A zone drawn inside a room, short of the wall centerlines, still means that room, so isolate must keep its walls. Spaces whose polygon encloses a member centroid (or whose centroid the member encloses) contribute their wall ids. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MpE7N6c5YfaS9V3rfYLnKN
…et-active button Row click already sets the active unit. Color dots are the identity of units and zones, so the tree no longer desaturates them when unselected. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MpE7N6c5YfaS9V3rfYLnKN
Clicking a unit focuses it: Zones layer on, jump to its lowest level, members highlighted in 3D and tinted in 2D, other zones dimmed, chip near the level selector, unit dot on spanned levels. While focused a plain click on a zone toggles membership (move semantics, one unit per zone in the editor), a double-click selects the zone and ends focus, panel zone rows get a checkbox, and newly drawn zones join in the same history step. Focus ends on Escape, non-zone selection, New unit, or a manual layer switch, restoring the layer and keeping the level. Removed: isolate eye and viewer isolate wiring, set-active button, unit dropdown under the level selector, Common group, per-zone popovers, shared-zone warning. Zone outlines keep the zone color; only the fill takes the unit color. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MpE7N6c5YfaS9V3rfYLnKN
Selecting, renaming or recolouring a zone row while a unit was focused ended focus and restored the Elements layer. Zones live on the layer focus uses, so selecting one now keeps focus; only non-zone selection, Escape, New unit, or a manual layer switch leaves. Double-click on a zone selects it without leaving. The membership checkbox is now a styled primitive instead of a bare input. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MpE7N6c5YfaS9V3rfYLnKN
…lished viewer One ViewerUnitsPanel (kind, zone count, area; click focuses and reveals the unit's level) mounted under the level list in the preview scene header and exported for the community viewer page. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MpE7N6c5YfaS9V3rfYLnKN
|
I hit an error while handling your request (Model unavailable on AI Gateway free tier: Free tier users do not have access to this model. Upgrade to paid credits at https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai%3Fmodal%3Dtop-up for unrestricted…). Please try again, rephrase, or reach out if it keeps failing. Error id: 9f62ffe0-1a9b-479f-8ebc-5a757e99cd1c |
| if (focusedUnitId) { | ||
| const zone = zoneAtLevelPoint(event.localPosition[0], event.localPosition[2]) | ||
| if (zone) paintZoneMembership(focusedUnitId, zone.id) | ||
| return |
There was a problem hiding this comment.
Empty ground click keeps unit focus
Medium Severity
A 3D ground click outside any zone is supposed to end unit focus and leave the unit unselected, but the focused-unit branch always returns after an optional paint. Clicks on empty floor never call leaveUnitFocus, so paint mode stays on until Escape or the chip.
Reviewed by Cursor Bugbot for commit 9dd62da. Configure here.
| paintZoneMembership(focusedUnitId, clickedNode.id) | ||
| swallowNextClick(200) | ||
| return | ||
| } |
There was a problem hiding this comment.
2D double-click cancels zone paint
High Severity
While a unit is focused, a 2D click on a zone schedules paint on pointer-down and swallows the following click. The second pointer-down of a double-click hits paintZoneMembership again, which cancels that paint, and the registry path never selects the zone. The floorplan background handler that treats detail >= 2 as select never sees those clicks.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 9dd62da. Configure here.
| pendingPaints.delete(zoneId) | ||
| toggleZoneMembership(unitId, zoneId) | ||
| }, ZONE_PAINT_DELAY_MS), | ||
| ) |
There was a problem hiding this comment.
Paint delay misses double-click window
Medium Severity
ZONE_PAINT_DELAY_MS is 250ms, shorter than the usual OS double-click interval. A normal 3D double-click whose second click lands after that delay already toggles membership, then the double-click handler selects the zone, so the unit gains or loses the zone as a side effect of selecting it.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 9dd62da. Configure here.
…ventory (46 → 49) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MpE7N6c5YfaS9V3rfYLnKN
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
There are 4 total unresolved issues (including 3 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 e51a352. Configure here.
| toggleZoneMembership(unitId, zoneId) | ||
| }, ZONE_PAINT_DELAY_MS), | ||
| ) | ||
| } |
There was a problem hiding this comment.
Pending paints fire after focus ends
Medium Severity
paintZoneMembership keeps a 250ms timer that still calls toggleZoneMembership after focus ends or moves. leaveUnitFocus and unit switches do not clear pendingPaints; timers are only dropped when useUnitFocusRules unmounts.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit e51a352. Configure here.


Units — apartments, hotel rooms and commercial lots inside one building
Adds the
unitnode kind: a building child that groups manually drawn zones (possibly across levels, e.g. a duplex) with no geometry of its own. The physical hierarchysite → building → leveland the vertical model are untouched. Mirrors IFC'sIfcSpatialZone(OCCUPANCY).Core
UnitNodeschema (name,kindapartment | hotel-room | commercial | common,members: zoneId[],color);building.childrenadmits units. No migration.unit-containment.ts: derived member levels, contained nodes, boundary walls (including the walls of the detected room enclosing a member zone), supports, and the isolate list.unit-report.ts: member count, level span, gross area.Editor UX (agreed with Wassim after two hands-on passes)
● Unit ×chip near the level selector and a dot on spanned levels.ViewerUnitsPanelunder the level list in the editor Preview (also used by the community viewer).MCP
create_unit,set_unit_members,list_units(+ annotation manifest rows).Verification
unit-focus.spec.ts(private repo) drives the whole flow on :3005 and is green.Plan: private repo
plans/editor-units.md.🤖 Generated with Claude Code
https://claude.ai/code/session_01MpE7N6c5YfaS9V3rfYLnKN
Note
Medium Risk
Touches scene graph deletion/cloning, selection routing, and floor-plan caching across editor and viewer; geometry is derived rather than stored, but membership and focus state must stay consistent with undo and multi-unit zone sharing.
Overview
Introduces
UnitNodeas a building-level overlay that groups zones (including multi-level duplexes) without changing the site → building → level hierarchy. Core addsderiveUnit,buildUnitReport, membership helpers, delete/clone remapping, andUnitEventwiring; zones lose unit membership when deleted.Editor workflow centers on unit focus: select a unit to enter the zones layer, tint/dim zones on the floor plan and in 3D, and paint membership via clicks (with double-click to select a zone). New zones can auto-join the focused unit in one undo step; the site tree, level selector chip, zone checkboxes, inspector panels, and command palette support create/focus/exit.
Presentation: zone floor plan fill uses the owning unit color;
floorplanDependenciesnow receives the full scene so unit membership invalidates zone geometry. MCP exposescreate_unit,set_unit_members, andlist_units. Viewer getsfocusedUnitIdand a sharedViewerUnitsPanelfor read-only browsing/focus in preview.Reviewed by Cursor Bugbot for commit e51a352. Bugbot is set up for automated code reviews on this repo. Configure here.