Skip to content

Editing foundations: roofs on the building's roof level, add_fence shared, corner-window editing and review fixes - #1039

Merged
Aymericr merged 16 commits into
mainfrom
feedback/editing-foundations
Oct 13, 2026
Merged

Aymericr merged 16 commits into
mainfrom
feedback/editing-foundations

Conversation

@Aymericr

@Aymericr Aymericr commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?

One rule for where a roof lives, by hand and by agent, on top of Bundle 2's tools (#1024, now on main), plus add_fence as a shared tool, the corner-window editing that Bundle 2 left out, and the review fixes to the editing code both branches shipped.

  • Roofs live on the building's roof level. One core service, services/roof-level.ts: a roof lives on the level directly above the storey whose walls carry it; over the top storey that is the building's roof level (metadata.role: 'roof', named Roof), kept above every storey, so exploded and solo views lift the roof off the plan.
    • planRoofLevel: a storey built anywhere above takes a lower wing's roof on the storey directly above; else the roof level above every level still to be built and every support level; else the empty level directly above when it is the only one, declared; else a new Roof level on top. A support level is never declared the roof level.
    • Bundle 2's shared create_roof keeps its footprint (the walls' outline with gaps bridged, else floor plates, else rooms), the mitre limit and the fascia, and seats its roof through planRoofLevel, on the storey's wall tops; a roof level it makes is as tall as the roof rises over it, the eave's lift included, and its answer carries ok: true. It refuses roof_exists, storey_above, building_required (several buildings, none named) and storey_without_building. Overlapping floor plates are one footprint. The result names coveredLevelId; no referenceLevelId metadata is written, since the rule derives it.
    • servesAsRoofLevel is the one test of a level's role (a declared support level stays one, roofs on it or not): levelRole (so list_levels and verify_scene), the agents' roof_level refusals and storeyInsertIndex read it. A declared roof level someone walled stays the roof level everywhere.
    • Which storey a roof covers is derived (coveredStoreyId, roofsOver), so a porch roof on the upper storey does not stop create_roof capping it. A lower wing's roof that follows walls follows the walls below only (the upper storey's wall along its edge lifted it to that storey's eaves).
    • add_level and the editor's "add a floor" (level selector, Settings, site tree, command palette) put a new storey under the roof level, which moves up one in the same undo step. The level selector's top "+" reads "Add a floor under the roof" when a roof level tops the stack, inserts between two levels in one step, and refuses a drag that puts a roof level under a storey.
    • The hand Roof tool follows the rule: a room footprint, a rectangle drawn on a walled storey and a cone over a curved wall go on the level above the walls, standing on them; the ghost shows where the roof lands, and the roof's level is selected with it so it stays in view.
    • verify_scene reports roof_level_not_on_top (replacing Bundle 2's roof_level_below_reference), counting an empty storey above; roof_mixed_in_storey only for a roof covering its own storey. Existing scenes are not migrated.
    • Unpainted gables on a roof level take the cladding of the storey below. The site plan draws the roof line from every level and does not count the roof level as a storey.
  • add_fence is one shared contract and operation: a fence through points, one per side, closed for a lot's boundary, privacy by default; it refuses a side under 10 cm and a roof level, and runs in run_batch like add_wall. Bundle 2 kept the hosted chat's own one-segment add_fence; this replaces it (pascalorg/private-editor#903 registers it in the chat).
  • Corner-window editing Bundle 2 left out. The plan draws a corner pair through its corner (mitred, not a wall post) and outlines a waiting window on hover; a window dragged in the plan joins a corner as in 3D; a preset placed at a corner in the plan stays joined; Merge windows says why in the editor's words and units; the HUD's note at a free wall end is a "!" row; an edit a pair shares is one undo step. The corner-snap HUD state moves from core to the editor store (feat: Bundle 2 editing, interior captures and native materials #1024's own list asked for it).
  • Review fixes on the corner windows, rooms and storey heights (Bundle 2's code and this branch's):
    • A corner pair holds only while both sides reach their corner end, on walls of one level meeting there at 10°–170°. Deleting one side, dragging it off its corner or copying it alone leaves the other plain; a duplicated level, or both walls pasted together, keep the copies joined. A frame built for a pair that broke is rebuilt.
    • add_corner_window reads the walls of one level (levelId, default the floor in view): stacked storeys share corner points, so a two-storey house was refused or joined across floors.
    • Only a fixed window's glass fuses (corner_not_fused); the panel offers Fused glass / Corner post for a fixed window only. A side resized to its whole wall keeps its pair. A same-wall merge keeps the union. Wrap the corner refuses a flat or folded angle. A waiting window the join would refuse is a "!" row ("Another opening is in the way at the corner", "The window waiting there is taller than this wall"). Two windows that already wrap their corner get Unwrap the corner.
    • Corner snapping follows the snapping mode, releasing Alt brings it back without moving the pointer, and the window tools' keys (Alt included) ignore typing targets. The panel no longer plans a wrap on every scene update; its Corner buttons have a focus ring and press state.
    • set_zone over a walled room is that room (a retry is a no-op; a room a person named otherwise is refused). A level created in a patch gets the height its outer parentId gives. achieved counts each node once, and only when it changed. verify_scene's corner_unjoined and corner_partner_missing are notes pointing at merge_windows.
  • Comments say why, without bench runs, ticket ids or dates.

Reconciled with Bundle 2

Rebuilt on editor main (c69d1d33: #1024, then #1042, #1043 and #1041) instead of carrying the two merges of main. The one conflict, #1041's light hover on the Corner buttons, takes this branch's token classes, which work in both themes.

  • Dropped, already on main: the port of sill centring, named rooms, storey heights, and corner/merged windows (contracts, operations, snap, fuse, the viewer's corner cut); the MCP's single create_roof registration; the lines that only existed because the old base lacked Bundle 2's additions.
  • Kept: the roof-level service and every user of it (above), with Bundle 2's create_roof rebuilt on it rather than replaced: roofHost gives way to planRoofLevel, and Bundle 2's footprint, mitre limit, fascia and tests stay; add_fence; the corner editing above; the HUD state moved to the editor; this branch's fixes to Bundle 2's corner code (labels, token hover, writeWindowChanges, corner_unjoined as a note).
  • Changed against Bundle 2's decisions, for the owner: add_fence becomes shared (Bundle 2 kept the chat's own); an empty level directly above the storey, alone at the top, becomes the roof level (Bundle 2 roofed above it, leaving the roof a storey up from its walls); roof_level_below_reference becomes roof_level_not_on_top; the result field is coveredLevelId, not referenceLevelId.

Why

The owner's feedback (2026-10-12):

  • G1 "in ai chat often the architect create a roof on the same level as the ground floor, preventing exploded view or obstructing the layout entirely."
  • C19 "…every time we build with an LLM, it actually places the roof system on the same level as its supporting walls. That's not what we have intended for exploded view or obstructions… There should be a roof or a level dedicated for the roof system so we can appropriately see the floor plan without having the roof segments obstructing our view inside the home."
  • G2 "we need to compile and harmonize tools developped by Wassim, Adam (AxiomeCG), and Steven Tibbs from the AI capabilities to avoid conflicts, explosation in complexity, and increase reliability and consistency of outputs."

Decision (plan §3): "Roofs: one rule by hand and by agent — a roof lives on the level directly above the storey whose walls carry it; over the top storey that is the building's 'Roof' level. Existing projects are not migrated; verify_scene reports them."

How to test

  1. One-storey house: Roof tool, "Room" footprint, click inside the room → a Roof level appears above Ground floor and is selected with the roof, which stays in view (3D and 2D); exploded view lifts the roof; solo Ground floor shows no roof. Undo removes both. Site plan: the dashed roof line, "1-STORY RESIDENCE".
  2. Level selector "+" on that house (it reads "Add a floor under the roof") → the new storey goes under Roof; Roof moves up with the roof. One undo removes the floor and moves Roof back. Dragging Roof under a storey snaps back.
  3. Two-storey house, ground-floor wing beside the upper volume: Roof tool on the ground floor, "Room" on the wing → the roof goes on First floor at the wing's eaves, not the upper storey's.
  4. Window tool near a wall end where another window waits → the ghost snaps, the waiting window is outlined, the HUD reads "Join as a corner window"; click → one corner window. Alt, or snapping Off → plain. Drag a window in the plan to that corner → joined. Delete one side → the other is a plain window, framed at the corner.
  5. Two windows selected → Merge windows, or the reason in words and the project's units; a joined pair → Unwrap the corner.
  6. MCP: create_roof on a one-storey scene → roof on a new Roof level; again → roof_exists; add_level → shiftedLevelIds names the roof level; add_fence with five points and closed: true → five fences. add_corner_window on a two-storey house with levelId → both sides on that floor.

Ran, from the hosted worktree with this head pinned (pascalorg/private-editor#903 on hosted main 734519e74):

  • bunx turbo run check-types --filter='./editor/packages/*' (9/9) and the dists' build.
  • bun test by package: core 4,389, MCP 812, nodes 4,805 and viewer 667 pass; editor 2,444 pass, with the 19 lib/history.test.ts subprocess tests failing on THREE's CJS deprecation warning in stderr, as on the base.
  • biome check (editor config) on every changed file.
  • The hosted typecheck, suite (25/25), bun check and ci:contracts with this pin.

Earlier rounds checked steps 1, 2, 4 and 5 in the running app, in both themes (screenshots on #903 and /tmp/feedback-work/foundations/shots); this round changes no UI beyond the "+" title, the Corner section's fixed-only post choice and the Unwrap action.

Screenshots / screen recording

From the verify round, in dark and a light preview: the Room footprint preview and the roof on its Roof level; the ground floor's plan with the roof level above; exploded view; "+" inserting Floor 1 under Roof; the corner snap; the corner window; the Corner section; the merge note; the HUD's "!" row; the plan's mitred corner.

Checklist

  • I've tested this locally with bun dev
  • My code follows the existing code style (run bun check to verify)
  • I've updated relevant documentation (if applicable)
  • This PR targets the main branch

Deployment notes

Review

Reviewed with #903 by an adversarial review and GPT-6 Astra, on the pre-Bundle-2 head 591efd5f; every finding was checked again against this head.

Fixed: stale corner links after delete, drag and copy; add_corner_window across storeys and its widths; roof-only ground levels in storeyInsertIndex; fuseAtCorner swallowing every error and the HUD promising a join that would be refused; the "+" title under a roof level; the two definitions of a roof level; the panel's per-update wrap plan; add_fence's search_materials and raised descriptions; unused corner exports; the Corner buttons' focus ring; incident stories in roof-footprint.ts; the README's create_roof row; patch heights ignoring an outer parentId; a lower-wing roof counted over the upper storey; a lower-wing roof lifted onto the upper storey's walls; a drawn rectangle not following its walls; level and clipboard copies breaking pairs; pairs surviving moved walls; disagreeing corner validity across paths; fused glass offered to framed types; a side resized to its wall breaking its pair; a declared roof level reused under a storey; building_required skipped on empty buildings; set_zone stacking a second zone on a retry; same-wall merges shrinking the union; achieved counting repeats and no-ops; snapping ignoring the snap mode and Alt release; typing targets in the window tools' handlers. The Corner section's segmented control in light mode is fixed on main by #1041 (light classes, aria-pressed); the Corner buttons use tokens.

Bugbot on this head, fixed: the roof level create_roof makes was not tall enough for a lifted eave; create_roof had no ok; a declared support level holding a roof was read as the roof level. Bugbot, not changed: "placement still promises refused corner joins" (the window tool always passes its draft as ignoreId, so the preflight runs on placement too); "corner snap swallows other merge refusals" (after the angle, pair and reach checks before it, mergeWindows can only refuse height_exceeds_wall or opening_overlap there, both mapped); "fascia material skips library check" (main's create_roof already validates it).

Not fixed, and why:

  • The terrace over a walled numbered room becomes that room instead of being refused. That is main's behaviour (Bundle 2) and the intent: a walled courtyard drawn as a terrace is the outdoor room. It is now documented in terrace-draft.ts and space-detection.md.
  • The level selector's refused drop has no message. The selector has no invalid-drop feedback to reuse; the level returns to its place.
  • achieved over the MCP counts the planned changes, not the bridge's settled state. That is Bundle 2's design for every shared tool; reading the settled state is a change to the MCP's apply path, not this PR's.
  • The agent can't roof a lower wing (storey_above); the hand tool can, and now seats it on the wing's walls. Where a lower-wing roof shows in solo views is the owner's call.
  • No repair for existing scenes. Plan §3: not migrated; verify_scene reports them.

Builds on

Adam's #1024 (Bundle 2), now on main; co-authored by Adam NAILI (@AxiomeCG).

🤖 Generated with Claude Code


Note

High Risk
Changes core scene semantics (roof placement, level stacking, corner-window pairing) across agents, MCP, and editor; incorrect edge cases could corrupt multi-storey or multi-building scenes, though fixture tests cover many paths.

Overview
Unifies where roofs live in a shared roof-level service (planRoofLevel, roofsOver, storeyInsertIndex, isRoofLevel / servesAsRoofLevel). create_roof now places roofs on the building’s roof level above the capped storey, seats them on wall tops via elevation resolution, refuses duplicate roofs and non-top storeys, unions overlapping slabs for footprint, and can require building_required on multi-building sites. add_level inserts new storeys under the roof level and shifts roof levels up.

Adds shared add_fence (agent operation + tool + fixture cases). Corner windows gain per-level targeting (targetLevel), casement vs fused-glass rules (fusesAtCorner, corner_not_fused), stale partner link cleanup in merge/wrap/snap, and informational verify_scene hints pointing at merge_windows. Same-wall merge uses the union of spans (nested windows) and clears broken corner links.

achievedChanges counts each node at most once and treats no-op updates as unchanged. level-height-fill respects create parentId when deriving heights. levelRole defers roof classification to servesAsRoofLevel. verify_scene adds roof_level_not_on_top and narrows roof_mixed_in_storey via roofsOver. Extensive declarative agent fixture tables (CREATE_ROOF, ADD_FENCE, corner/merge cases) wire into the shared case runner.

Reviewed by Cursor Bugbot for commit a7ffe05. Bugbot is set up for automated code reviews on this repo. Configure here.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/core/src/agent-operations/create-roof.ts
Comment thread packages/core/src/agent-operations/create-roof.ts
@Aymericr
Aymericr force-pushed the feedback/editing-foundations branch from 591efd5 to a7ffe05 Compare October 13, 2026 16:23
@Aymericr Aymericr changed the title Editing foundations: roofs on the building's roof level, corner and merged windows, named rooms, storey heights, add_fence Editing foundations: roofs on the building's roof level, add_fence shared, corner-window editing and review fixes Oct 13, 2026

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/core/src/agent-operations/create-roof.ts
Comment thread packages/core/src/lib/corner-snap.ts
Aymericr and others added 15 commits October 13, 2026 12:30
The hosted chat had its own add_fence (one segment, the store written
directly) and the MCP had none. add_fence is now one contract and one core
operation: a fence along points on a level, one fence per side, closed back
to the first point for a lot's boundary, privacy panels unless a style is
asked (a fence made with no style came out as open rails). It refuses a side
shorter than 10 cm, a roof level and a finish the library lacks, and it runs
in run_batch like add_wall. The fence style enums move to a dependency-free
schema module so the contract stays zod-only.

From Adam's drafts of #1024, which landed with the hosted
chat's own add_fence kept.

Co-authored-by: Adam NAILI <adam.naili.pro@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A roof made on the ground floor's own level moved with that floor in the
exploded view and covered its plan in solo view (the owner's report on the
Architect chat). The chat, the MCP and the hand roof tool each placed roofs
by a rule of their own. There is now one rule, in one core service
(services/roof-level.ts): a roof lives on the level directly above the
storey whose walls carry it, and over the top storey that is the building's
roof level (metadata.role 'roof', named Roof), kept above every storey.

- planRoofLevel: the level above the storey, or the roof level to reuse,
  declare (an empty level, or one that only holds roofs) or create;
  roofsOver, storeyInsertIndex and isRoofLevel beside it.
- The shared create_roof (editor#1024) puts its roof on the roof level,
  following the storey's wall tops (support 'walls', the eave resolved as
  the editor's follow would). It refuses a second roof over a storey
  (roof_exists) and a storey with another above it (storey_above).
- add_level and the editor's "add a floor" (level selector, settings, site
  tree, command palette) put a new storey under the roof level, which moves
  up one in the same step.
- The hand roof tool follows the same rule: a room footprint, a rectangle
  drawn on a walled storey and a cone over a curved wall go on the level
  above the walls; the ghost is drawn where the roof lands.
- verify_scene reports a roof level below a storey (roof_level_not_on_top,
  replacing roof_level_below_reference); existing scenes are not migrated.
- Unpainted gables on a roof level take the cladding of the storey below.
- RoofType moves to a dependency-free schema module for the contract.

Built on Adam's create_roof from #1024 (Bundle 2).

Co-authored-by: Adam NAILI <adam.naili.pro@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 3D window move snapped a window to a corner and joined it to the one
waiting on the other wall; the plan's move did not, so the same drag gave
two results by view. The 2D move now runs the same corner evaluation:
the edge snaps to the corner, the waiting window is outlined, and the drop
joins them in one undo step. Alt drops it plain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ne footprint; create_roof asks which building

- planRoofLevel declares an empty level the roof level only when nothing stands above it: an empty
  level under a storey is a storey not built yet, and roofing the floor below no longer turns it
  into a Roof level under that storey. A declared roof level someone walled counts as a storey.
- A footprint is the union of its floor plates: two slabs that overlap roof their overlap (it was
  an exclusive-or, which split one house into two wings).
- With no level named and none in view (the MCP), create_roof on a site of several buildings
  answers building_required with their ids, instead of roofing whichever sorts last.
- storey_above no longer tells the agent to roof the top storey instead: a lower wing's roof
  stands on its own walls, so it says the person draws it with the Roof tool.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… roofs on the roof level

- A tool's selectNode selects a node on its own level when that is not the one in view, in 2D and
  3D. A roof drawn over the ground floor's walls lands on the Roof level, which the editor hides
  while the ground floor is selected: it disappeared the moment it was placed.
- The site plan's dashed roof line reads every level of the building, not only the lowest, so a
  roof on the roof level is drawn.
- The level selector inserts a floor between two levels in one undo step (undo also moves the
  levels above back), and refuses a drag that puts a roof level under a storey.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l as a storey

A bungalow under its Roof level read "2-STORY RESIDENCE".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ported comments pointed at things this repository does not hold: bench
learnings by number (L41, L65, L68), bench runs and houses ("run 4", "the
290's Bed 4", "Hawkesbury", "Victor run 5") and who asked when ("the
owner, 8 October"). Each keeps its reason, said plainly. A test's unused
map goes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Selecting two windows that cannot merge showed the agents' refusal under
the button, node ids and metres included ("window_kcwq… and window_9mkd…
are 0.31 m apart on wall wall_fdn2…"), in a project set to feet. The
offer now hands the panel the refusal itself, and the panel words its
code for a person, measures in the editor's units: "They are too far
apart. Windows merge with at most 1'0" between them."

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Near a free wall end the window HUD showed "No wall meets here" beside a
key cap reading "Corner", as if Corner were a key to press. The note now
takes the HUD's notice row, the "!" a placement that does not fit already
uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… one step

Changing a corner window's height in its panel wrote the window and then
its partner as two undo steps, so one undo left the pair on two heights.
The frame's thickness and depth, also shared, were not carried to the
partner at all. Each shared edit (height, frame, sill, type) now writes
both windows in one step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In 3D a corner window opens the corner; the plan still drew each side
square at its wall's end, so the corner square stayed wall, a post that
is not there. Each side of a corner pair now runs to the mitre its wall
meets the other along, so the plan shows one window turning the corner,
the joint mitred as a drafted corner window is.

Dragging a window toward a corner in the plan outlined the window
waiting on the other wall only in 3D: the plan drew no hover on windows.
A hovered window takes the walls' hover stroke, so the cue shows in both
views.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ays joined

A preset placed in the plan is committed by giving its draft a new id.
At a corner the draft had already been joined to its partner, which kept
naming the deleted draft, so the pair came out broken: the 3D view drew
jambs at the corner and verify_scene saw a window whose partner was gone.
The commit now rewrites the nodes the draft names that name it back, so
they name the committed node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ap HUD lives in the editor

This branch's review fixes to the corner-window code Bundle 2 landed:
verify_scene's corner_unjoined is a note pointing at merge_windows (two
windows meeting at a corner may be what the person drew); the window
panel's corner choices read Fused glass and Corner post, its buttons take a
token hover, and its writes go through writeWindowChanges. The corner-snap
HUD state moves from core to the editor, as #1024's own list asked: it is
editor UI state, not scene data.

Co-authored-by: Adam NAILI <adam.naili.pro@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a level's role

Bundle 2 (#1024) landed its own create_roof with roofHost; this branch's
roof-level service now places that operation's roofs, and its review
findings are fixed.

- create_roof keeps Bundle 2's footprint (the walls' outline with its gaps
  bridged, else the floor plates without balconies, else the rooms), the
  mitre limit and the fascia, and seats the roof through planRoofLevel:
  roof_exists, building_required and storey_without_building refuse, and
  it no longer writes referenceLevelId metadata the rule derives.
- planRoofLevel merges both rules. A storey built anywhere above takes a
  lower wing's roof on the storey directly above (create_roof refuses
  storey_above); else the roof level above every level still to be built
  and every support level; the empty level directly above when it is the
  only one, declared; else a new Roof level on top. A support level is
  never declared the roof level.
- servesAsRoofLevel is the one test of a level's role: list_levels,
  verify_scene and the agents' refusals read it through levelRole, so a
  declared roof level someone walled stays the roof level everywhere.
- coveredStoreyId on a roof level passes over levels still to be built,
  and a lower wing's roof that follows walls follows the walls below only,
  not the upper storey's along its edge (it was lifted to their tops).
- verify_scene's roof_level_not_on_top counts an empty storey above, a
  floor still to be built; a lower wing's roof at the upper storey's floor
  is not roof_mixed_in_storey.
- A rectangle drawn onto the roof level stands on the storey's walls,
  following their height as a room's roof does.
- The level selector's top "+" reads "Add a floor under the roof" when a
  roof level tops the stack.

Co-authored-by: Adam NAILI <adam.naili.pro@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review findings on the corner windows, rooms and storey heights this
branch ported and Bundle 2 (#1024) landed.

- A corner pair holds only while each side reaches its corner end, both
  walls end at that corner on one level, and the angle stays between 10°
  and 170° (cornerPair, which the viewer, the plan and every tool read).
  Deleting one side, dragging it off its corner (3D or plan) or copying it
  alone leaves the other a plain window; a level duplicated, or both walls
  copied and pasted, keeps the copies joined to each other. A window whose
  frame was built for a pair that has since broken is rebuilt.
- add_corner_window reads the walls of one level (levelId, default the
  floor in view): stacked storeys share their corners' points, so a
  two-storey house was refused or joined across floors. widths are
  measurements.
- Only a fixed window's glass fuses: other types meet at a post
  (corner_not_fused on post none), and the panel offers Fused glass or
  Corner post for a fixed window only.
- A side resized to its whole wall keeps its pair; merging two windows on
  one wall keeps their union; Wrap the corner refuses a flat or folded
  angle; a waiting window the join would refuse (an opening in the way, a
  window taller than this wall) is a "!" row in the HUD, not a silent
  plain window. fuseAtCorner swallows only refusals.
- Two windows that already wrap their corner get Unwrap the corner in the
  multi-selection panel; Merge windows says what it keeps.
- Corner snapping follows the snapping mode; releasing Alt brings it back
  without moving the pointer; the window tools' keys, Alt included, ignore any typing
  target. The panel no longer plans a corner wrap on every scene update,
  and its Corner buttons have the focus ring and press state.
- set_zone over a walled room is that room: a retry with the same name is
  a no-op, a room a person named otherwise is refused.
- A level created in a patch gets the height its outer parentId gives;
  achieved counts each node once, and only when its data changed.
- add_fence's descriptions name search_materials and say what raised is.
- Unused corner exports leave core's index; verify_scene's
  corner_partner_missing is a note pointing at merge_windows.

Co-authored-by: Adam NAILI <adam.naili.pro@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Aymericr
Aymericr force-pushed the feedback/editing-foundations branch from a7ffe05 to ec6dec5 Compare October 13, 2026 16:36

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

There are 3 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ec6dec5. Configure here.

Comment thread packages/core/src/services/roof-level.ts
Comment thread packages/core/src/lib/corner-snap.ts
…ys ok; a support level stays one

Bugbot on #1039:
- A roof level create_roof makes is as tall as the roof rises over it,
  the eave's lift included: walls taller than their storey lifted the roof
  past the level made for it.
- create_roof's answer carries ok: true, as the other shared writers'.
- A declared support level holding a roof stays a support: it was read as
  the roof level, and planRoofLevel would have redeclared it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Aymericr
Aymericr merged commit cc978fe into main Oct 13, 2026
5 checks passed
Aymericr added a commit that referenced this pull request Oct 13, 2026
Projects built before the roof level, and roofs a tool wrote onto the storey whose walls carry them,
keep hiding that storey's plan in solo and exploded views. Rebuilt on main after #1039 landed: its
roof-level rule (planRoofLevel, coveredStoreyId, roofsOver) stays as it is; this adds the move.

- services/roof-move.ts: isRoofOnStoreyWalls (a roof bearing on the wall tops of the storey holding
  it; a porch roof on its beam and a lower wing's roof on the storey above are not), and
  planRoofsOffStorey, which moves those roofs to planRoofLevel's level in one set of changes, each
  container dropped by the storey's offset so it stands where it stood.
- planRoofLevel returns that storeyOffset, for the plugins that write roofs measured from the
  storey's floor; roofLevelAbove reads its target without planning a level.
- The roof's panel offers "Move to the roof level" ("Move to <storey>" under an upper storey), in
  2D and 3D, as one undo step.
- move_roofs_to_roof_level: the same move for agents, one contract and one core operation, on the
  MCP with its annotation policy, in the README's tool table and the agent guide.
- verify_scene's roof_mixed_in_storey keeps main's reports and names the move for the roofs it
  takes off.
- A roof above the storey it covers at a height of its own keeps its height over that storey when
  the storey is made taller or shorter (roofsKeptOverStorey, in the roof elevation sync).
- A roof level holding a plugin's roof planes is not reported roofless.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Aymericr added a commit that referenced this pull request Oct 13, 2026
Projects built before the roof level, and roofs a tool wrote onto the storey whose walls carry them,
keep hiding that storey's plan in solo and exploded views. Rebuilt on main after #1039 landed: its
roof-level rule (planRoofLevel, coveredStoreyId, roofsOver) stays as it is; this adds the move.

- services/roof-move.ts: isRoofOnStoreyWalls (a roof bearing on the wall tops of the storey holding
  it; a porch roof on its beam and a lower wing's roof on the storey above are not), and
  planRoofsOffStorey, which moves those roofs to planRoofLevel's level in one set of changes, each
  container dropped by the storey's offset so it stands where it stood.
- planRoofLevel returns that storeyOffset, for the plugins that write roofs measured from the
  storey's floor; roofLevelAbove reads its target without planning a level.
- The roof's panel offers "Move to the roof level" ("Move to <storey>" under an upper storey), in
  2D and 3D, as one undo step.
- move_roofs_to_roof_level: the same move for agents, one contract and one core operation, on the
  MCP with its annotation policy, in the README's tool table and the agent guide.
- verify_scene's roof_mixed_in_storey keeps main's reports and names the move for the roofs it
  takes off.
- A roof above the storey it covers at a height of its own keeps its height over that storey when
  the storey is made taller or shorter (roofsKeptOverStorey, in the roof elevation sync).
- A roof level holding a plugin's roof planes is not reported roofless.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Aymericr added a commit that referenced this pull request Oct 13, 2026
… still to be built

roofsKeptOverStorey took the level directly under the roof's level as the storey the roof covers.
With a support level or an empty level between them, which is where planRoofLevel puts the roof
level, it found no storey there. A fixed-height roof standing on the storey's walls then rose off
them when the storey, or the level between, was made taller (Bugbot on #1047).

- The storey is coveredStoreyId's, the one the move itself reads.
- A level put in, taken out or reordered between them still carries the roof with its level, as
  #1039 decided for "+ level". Over levels between, only a roof bearing on the storey's walls is
  kept on them, so a roof that "+ level" lifted stays on top of the new level when it grows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Aymericr added a commit that referenced this pull request Oct 13, 2026
* core: move old roofs to the roof level, by hand or by agent

Projects built before the roof level, and roofs a tool wrote onto the storey whose walls carry them,
keep hiding that storey's plan in solo and exploded views. Rebuilt on main after #1039 landed: its
roof-level rule (planRoofLevel, coveredStoreyId, roofsOver) stays as it is; this adds the move.

- services/roof-move.ts: isRoofOnStoreyWalls (a roof bearing on the wall tops of the storey holding
  it; a porch roof on its beam and a lower wing's roof on the storey above are not), and
  planRoofsOffStorey, which moves those roofs to planRoofLevel's level in one set of changes, each
  container dropped by the storey's offset so it stands where it stood.
- planRoofLevel returns that storeyOffset, for the plugins that write roofs measured from the
  storey's floor; roofLevelAbove reads its target without planning a level.
- The roof's panel offers "Move to the roof level" ("Move to <storey>" under an upper storey), in
  2D and 3D, as one undo step.
- move_roofs_to_roof_level: the same move for agents, one contract and one core operation, on the
  MCP with its annotation policy, in the README's tool table and the agent guide.
- verify_scene's roof_mixed_in_storey keeps main's reports and names the move for the roofs it
  takes off.
- A roof above the storey it covers at a height of its own keeps its height over that storey when
  the storey is made taller or shorter (roofsKeptOverStorey, in the roof elevation sync).
- A roof level holding a plugin's roof planes is not reported roofless.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* core: a moved roof keeps to its walls over a support level or a level still to be built

roofsKeptOverStorey took the level directly under the roof's level as the storey the roof covers.
With a support level or an empty level between them, which is where planRoofLevel puts the roof
level, it found no storey there. A fixed-height roof standing on the storey's walls then rose off
them when the storey, or the level between, was made taller (Bugbot on #1047).

- The storey is coveredStoreyId's, the one the move itself reads.
- A level put in, taken out or reordered between them still carries the roof with its level, as
  #1039 decided for "+ level". Over levels between, only a roof bearing on the storey's walls is
  kept on them, so a roof that "+ level" lifted stays on top of the new level when it grows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* core: a roof level made for the move is as tall as the roofs rise over its floor

planRoofsOffStorey sized a new roof level by the roofs' rise over their eaves alone. Walls taller
than their storey seat the eaves above the new level's floor, so the roofs stood above its height
(Bugbot on #1047). The level now takes the roofs' highest point over its floor, the eaves' lift
included, as create_roof sizes one, and the plan's create says the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* core: a wall-following roof moved over a level between keeps its height

planRoofsOffStorey kept a moved roof on `walls` follow when the follow seated it where it stood.
Over a support level or a level still to be built, the follow reads only the roof level's walls and
the ones directly under it, finds none, and answers the roof's own height, so the roof kept a
follow that could not see its storey. When the storey grew, it rose with the roof level off the
walls (Bugbot on #1047). Such a roof now keeps its height (`support` level), which
roofsKeptOverStorey keeps on the storey's walls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant