Repository navigation
Editing foundations: roofs on the building's roof level, add_fence shared, corner-window editing and review fixes - #1039
Merged
Merged
Conversation
4 tasks done
Aymericr
force-pushed
the
feedback/editing-foundations
branch
from
October 13, 2026 16:23
591efd5 to
a7ffe05
Compare
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
force-pushed
the
feedback/editing-foundations
branch
from
October 13, 2026 16:36
a7ffe05 to
ec6dec5
Compare
There was a problem hiding this comment.
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).
❌ 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.
…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>
4 tasks done
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>
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.

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), plusadd_fenceas a shared tool, the corner-window editing that Bundle 2 left out, and the review fixes to the editing code both branches shipped.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.create_roofkeeps its footprint (the walls' outline with gaps bridged, else floor plates, else rooms), the mitre limit and the fascia, and seats its roof throughplanRoofLevel, 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 carriesok: true. It refusesroof_exists,storey_above,building_required(several buildings, none named) andstorey_without_building. Overlapping floor plates are one footprint. The result namescoveredLevelId; noreferenceLevelIdmetadata is written, since the rule derives it.servesAsRoofLevelis the one test of a level's role (a declared support level stays one, roofs on it or not):levelRole(solist_levelsandverify_scene), the agents'roof_levelrefusals andstoreyInsertIndexread it. A declared roof level someone walled stays the roof level everywhere.coveredStoreyId,roofsOver), so a porch roof on the upper storey does not stopcreate_roofcapping 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_leveland 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.verify_scenereportsroof_level_not_on_top(replacing Bundle 2'sroof_level_below_reference), counting an empty storey above;roof_mixed_in_storeyonly for a roof covering its own storey. Existing scenes are not migrated.add_fenceis one shared contract and operation: a fence through points, one per side,closedfor a lot's boundary, privacy by default; it refuses a side under 10 cm and a roof level, and runs inrun_batchlikeadd_wall. Bundle 2 kept the hosted chat's own one-segmentadd_fence; this replaces it (pascalorg/private-editor#903 registers it in the chat).add_corner_windowreads 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.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.set_zoneover 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 outerparentIdgives.achievedcounts each node once, and only when it changed.verify_scene'scorner_unjoinedandcorner_partner_missingare notes pointing atmerge_windows.Reconciled with Bundle 2
Rebuilt on editor
main(c69d1d33: #1024, then #1042, #1043 and #1041) instead of carrying the two merges ofmain. The one conflict, #1041's light hover on the Corner buttons, takes this branch's token classes, which work in both themes.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 singlecreate_roofregistration; the lines that only existed because the old base lacked Bundle 2's additions.create_roofrebuilt on it rather than replaced:roofHostgives way toplanRoofLevel, 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_unjoinedas a note).add_fencebecomes 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_referencebecomesroof_level_not_on_top; the result field iscoveredLevelId, notreferenceLevelId.Why
The owner's feedback (2026-10-12):
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_scenereports them."How to test
create_roofon a one-storey scene → roof on a new Roof level; again →roof_exists;add_level→shiftedLevelIdsnames the roof level;add_fencewith five points andclosed: true→ five fences.add_corner_windowon a two-storey house withlevelId→ both sides on that floor.Ran, from the hosted worktree with this head pinned (pascalorg/private-editor#903 on hosted
main734519e74):bunx turbo run check-types --filter='./editor/packages/*'(9/9) and the dists' build.bun testby package: core 4,389, MCP 812, nodes 4,805 and viewer 667 pass; editor 2,444 pass, with the 19lib/history.test.tssubprocess tests failing on THREE's CJS deprecation warning in stderr, as on the base.biome check(editor config) on every changed file.bun checkandci:contractswith 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
bun devbun checkto verify)mainbranchDeployment notes
add_fencefrom the shared contract and drops its own; its MCP inventory classifiesadd_fence(the package's 82nd tool). A bare bump fails its parity test, so fix(core): fill slab/ceiling hole fields on scene load #903 is the bump. pascalorg/private-editor#915 (the bare bump) is closed.window.corner, already onmainfrom feat: Bundle 2 editing, interior captures and native materials #1024. No new fields.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
cornerlinks after delete, drag and copy;add_corner_windowacross storeys and itswidths; roof-only ground levels instoreyInsertIndex;fuseAtCornerswallowing 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'ssearch_materialsandraiseddescriptions; unused corner exports; the Corner buttons' focus ring; incident stories inroof-footprint.ts; the README'screate_roofrow; patch heights ignoring an outerparentId; 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_requiredskipped on empty buildings;set_zonestacking a second zone on a retry; same-wall merges shrinking the union;achievedcounting 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 onmainby #1041 (light classes,aria-pressed); the Corner buttons use tokens.Bugbot on this head, fixed: the roof level
create_roofmakes was not tall enough for a lifted eave;create_roofhad nook; 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 asignoreId, so the preflight runs on placement too); "corner snap swallows other merge refusals" (after the angle, pair and reach checks before it,mergeWindowscan only refuseheight_exceeds_walloropening_overlapthere, both mapped); "fascia material skips library check" (main'screate_roofalready validates it).Not fixed, and why:
main's behaviour (Bundle 2) and the intent: a walled courtyard drawn as a terrace is the outdoor room. It is now documented interrace-draft.tsandspace-detection.md.achievedover 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.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.verify_scenereports 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-levelservice (planRoofLevel,roofsOver,storeyInsertIndex,isRoofLevel/servesAsRoofLevel).create_roofnow 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 requirebuilding_requiredon multi-building sites.add_levelinserts 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 informationalverify_scenehints pointing atmerge_windows. Same-wall merge uses the union of spans (nested windows) and clears broken corner links.achievedChangescounts each node at most once and treats no-op updates as unchanged.level-height-fillrespects createparentIdwhen deriving heights.levelRoledefers roof classification toservesAsRoofLevel.verify_sceneaddsroof_level_not_on_topand narrowsroof_mixed_in_storeyviaroofsOver. 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.