Panel-owned X-ray wall-mode takeover; export the headless engine surface - #2
Open
JonathanNus wants to merge 1 commit into
Open
Panel-owned X-ray wall-mode takeover; export the headless engine surface#2JonathanNus wants to merge 1 commit into
JonathanNus wants to merge 1 commit into
Conversation
…ss engine
Two changes a host needs:
1. The wall-mode takeover ('down' while X-raying) moves from the 3D
renderer to the panel. The renderer lives as long as the bones:framing
node does, so leaving the Bones tab kept the host's walls hidden — and
because wallMode is a persisted viewer preference, the stuck view
survived reloads, and merely OPENING a scene that contained an X-ray
node blanked the walls. The panel unmounts exactly when the user
leaves, which is the lifetime this intent actually has. The state
machine lives in view-takeover.ts (viewer-import-free, unit tested:
restore on leave, never stomp a user-picked mode, no ownership when
walls were already down). Also aligns with the plugin appearance
contract (#625): the renderer no longer writes host presentation state.
2. index.ts exports the pure derivation surface — computeLevel,
computeLevelUncached, computeTakeoff, cutList, the row/member types,
extractLevels/extractRoofs — so a host estimator can compute the same
quantities the panel shows without mounting anything.
computeLevelUncached is exported because a whole-building rollup loop
would otherwise thrash the 1-deep memo the panel and renderer share.
gorkking
pushed a commit
to gorkking/Pascal-Editor-plugin-bones
that referenced
this pull request
Aug 23, 2026
…er condenser, mis-dragged heat-pump override all WARN Condenser-honesty set (prod report pascalorg#2 'I don't see the heat pump'): F1 compute.ts — a level whose zones can't feed the room-derived engines composed ZERO hvac/plumbing output while framing/electrical rendered fine, with zero words (hunt 7a walls+slab+no-zones, 7b garage+hallway, 7c outdoor-only, 7d building-parented zones, 4b/4c two-storey zone/level mismatch, roof levels). One warning covering both systems, keyed off the ACTUAL compose, three honest classes: no indoor zones / all zones outdoor (the day-9 delta that honestly-but-silently removed a previously-drawn unit) / no habitable rooms. F2 compute.ts — each level mints its condenser at LEVEL-LOCAL grade, so an upper-storey X-ray drew the 'outdoor' unit at facade height (hunt 4a). Warning route, not levelId remount: the renderer mounts foreign MEMBERS only (buildGroups builds fixture-less foreign groups) — a remount would orphan the condenser/disconnect fixtures at facade height and the line-set still needs an unmodeled facade riser. Ground-storey compose byte-equal (master-baseline recaptured identical). F3 hvac.ts — a moved heat-pump service point won verbatim with no check (hunt 5a living-room unit, 5f 13 m into the yard). The override still WINS (A4); it now warns when it lands inside an indoor zone (named room; wall-band guard kills the centerline-anchor false positive) or beyond the NEC 210.63 25 ft service reach from every exterior wall — threshold basis stated in-file (the 15 m line-set advisory is a run-length class and would bless the 13 m drag). Suite 1591 green; baseline byte-identical; hunt matrix re-run: every class warns, healthy scenes unchanged. Co-Authored-By: Claude Fable 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.
The bug
Leaving the Bones panel keeps the host stuck in the X-ray view. The wall-mode takeover in
framing/renderer.tsxwritesuseViewer.setWallMode('down')from an effect whose cleanup is tied to the node's lifetime, but the intent ("get the walls out of the way while I'm looking at the X-ray") is tied to the panel's. Sidebar tab switches unmount the panel, not the renderer, so nothing restores — and sincewallModeis a persisted viewer preference, the stuck view survives page reloads. The sharpest edge: merely opening a saved scene that contains abones:framingnode blanks the host's walls before the user touches anything.This also runs against the plugin appearance contract (#625): the renderer was writing host presentation state, which the contract says plugins subscribe to read-only.
The fix
src/view-takeover.ts, viewer-import-free so it tests headlessly. Behaviours pinned by unit tests: restores the previous mode on release; takes no ownership when the user already had walls down; never stomps a mode the user picked while the panel was open; double-engage keeps the original mode.panel.tsxengages it while a live X-ray exists (seeThrough !== false) and releases on unmount — i.e. exactly when the user leaves.framing/renderer.tsxno longer touches host state; it only draws.Long term, a scoped, owner-tokened presentation override on
useViewer(push/pop) would be the contract-sanctioned way to do this — happy to discuss; this PR just makes the current behaviour match the current contract.Also: headless engine exports
index.tsnow exports the pure derivation surface (computeLevel,computeLevelUncached,computeTakeoff,cutList, row/member types,extractLevels,extractRoofs). The pipeline is already pure and fully tested headlessly; the exports let a host estimator compute the same quantities the panel shows — per level, without mounting anything.computeLevelUncachedis exported so a whole-building rollup loop doesn't thrash the 1-deep memo the panel and 3D renderer share. No behaviour change for existing consumers.Tests
499 pass, 0 fail (
bun test);tsc --noEmitclean. New:src/view-takeover.test.ts(6 tests).