Skip to content

feat(hosting): column tops, block tops, and the block-face blink fix - #895

Open
wass08 wants to merge 3 commits into
mainfrom
feat/hosting-column-block
Open

wass08 wants to merge 3 commits into
mainfrom
feat/hosting-column-block

Conversation

@wass08

@wass08 wass08 commented Sep 19, 2026

Copy link
Copy Markdown
Collaborator

What users get

  • No more blinking on block side faces. A hosted child's preview override entered the block's geometry dependencies, so every pointer move rebuilt the block mesh, fired pointer leave and enter, and destroyed and recreated the draft: 100 rebuilds and 200 floor/face flips per 100 moves. Now 0 and 0. Block tops measured 0 before and after.
  • Column tops host. A vase or a generated design can stand on a column, including an 80 cm pedestal or a column with a capital. It follows the column when it moves, rotates or changes height, survives undo, reload and duplicate, and keeps or leaves the column correctly in the plan view.
  • Generated designs on block tops behave like on any host. They keep the block in 2D, and an L-shaped or notched block no longer accepts an object over empty space.
  • Block edits keep what stands on them. Pushing or pulling a top carries the objects. A split keeps them in place. An edit that would remove or tilt the support under a generated design is refused before anything is written.

How

  • Column renderer mounts children; column becomes an eligible host and stays a floor-only child.
  • The catalog coordinator gains one generic route for eligible hosts no specialised route owns. A rejected generic hit yields silently to the floor. Specialised refusals (sink cutout, occupied surface) keep ownership when the event bubbles to a generic ancestor. Cabinet modules leave their hits to the run, so countertop and bar placements commit byte-identical nodes to main, including nested corner runs and standalone modules.
  • Shared frames apply slab lift once for any level-parented ancestor.
  • 2D plan: main's resolver is kept verbatim for every chain main handled (480 differential cases against a fixture copy of main), and full-frame composition applies as soon as a column or generic host is in the chain, checked against the mounted 3D pose.
  • Block face edits reuse the carry-or-refuse reconciliation extracted from the cabinet (behaviour-identical for cabinets). Catalog face children keep main's storage untouched. Face frames are cached per topology: editing an occupied 1,200-face block went from about 510 ms to under 2 ms.
  • Childless blocks match main's writes, dirty calls and history steps. A refused numeric value keeps the operation open; releasing a drag after a refusal commits the last accepted preview with the display in sync.
  • Duplicate takes the subtree for columns and blocks.

Deliberately not changed

Floor collision around blocks stays main's bounding-box check. A polygon narrow phase was tried and reverted after review reproduced a rotated item passing through a block. A floor item still cannot stand inside the notch of an L-shaped block.

Verification

  • From each package: core 2842, nodes 3315 (one existing skip), editor 1061, viewer 351. Typechecks and Biome pass. The 717 frozen resolver cases and the 15 production plan pins are byte-identical.
  • Eight independent review rounds with reproduced findings, each fixed with a failing-first test using real pointer bubbling where relevant.
  • Two real-browser passes. The first caught three failures no test had (item dropped inside the sink basin, duplicate losing children, a preview over the L notch that could not commit); all three now pass in the browser.

Not in this PR

Generated wall-mounted designs on block sides, a ready-made pedestal preset, exact ornate capital regions (hit-derived today), dangling face ids on side and underside children when their face is deleted (pre-existing).

🤖 Generated with Claude Code

https://claude.ai/code/session_013LKpG6PpZe6Jb4dBBAKtBG


Note

Medium Risk
Changes core 3D placement routing, world-frame composition, and block edit reconciliation; regressions would show up as wrong parenting, dropped items, or refused commits, though coverage is extensive.

Overview
Adds column tops and tighter block-top hosting so catalog and generated items can rest on those surfaces, follow host transforms, and stay consistent in plan view. Block topology edits carry or refuse hosted children atomically, with cached face frames so large occupied blocks reconcile in milliseconds instead of rebuilding geometry every validation pass.

Fixes the block side-face blink by stopping draft preview overrides from invalidating block geometry on every pointer move. Placement now uses a generic node:* route for eligible hosts (via exported canHostSurfaceChild) plus per-pointer surface event ownership so bubbled R3F hits do not fight specialized item/cabinet handlers or detach hosted previews incorrectly.

Shared level framing applies slab floorLift once for level-parented ancestors (including generic floor-placed hosts), and catalog counter-pose / surface-move rejection logic is broadened to registry-driven hosts instead of hard-coded node types.

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

wass08 and others added 2 commits September 18, 2026 15:25
…ock geometry

A hosted child's live preview override entered the block's geometry
dependencies, so every pointer move rebuilt the block mesh. The rebuild fired
pointer leave and enter, which destroyed and recreated the draft: 100 rebuilds
and 200 surface flips per 100 moves on a side face. Block geometry depends only
on topology and slots, so it declares no child dependencies, like the shelf.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013LKpG6PpZe6Jb4dBBAKtBG
…ck edits carry objects

Column tops. The column renderer mounts children, so a column is an eligible
host, and the catalog tool gains one generic route to any eligible host that
no specialised route owns (columns, generic-geometry plugin hosts). A rejected
generic hit yields silently to the floor, specialised refusals (cutout,
occupied) keep ownership under bubbling, and cabinet modules leave their hits
to the run. Column move, rotate and height changes carry what stands on top,
using the carry-or-refuse reconciliation shared with cabinets. Duplicate takes
the subtree for columns and blocks.

Block tops. A generated design keeps its block in the plan view and exits to
the floor like any other host. The centre rule uses the real top polygon, so
an L-shaped block no longer accepts an object over its notch. Face edits carry
children on a translated horizontal top, re-home them across a split with the
world pose preserved, and refuse before writing when an edit would tilt or
remove the support under a generated design. Catalog face children keep main's
storage untouched. A refused numeric value keeps the operation open; releasing
a drag after a refusal commits the last accepted preview. Childless blocks
match main's writes, dirty calls and history steps. Face frames are cached per
topology.

Shared frames apply slab lift once for any level-parented ancestor, and the
2D plan keeps main's resolver verbatim for every chain main handled while
composing full frames through columns and generic hosts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013LKpG6PpZe6Jb4dBBAKtBG
@pascal

pascal Bot commented Sep 19, 2026

Copy link
Copy Markdown

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: bd6b9f96-49c4-4f04-8784-19bfafbfe1d2

@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 1 potential issue.

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 e10bc64. Configure here.

Comment thread packages/editor/src/components/tools/registry/item-surface-move.ts
…ck edit budget test

R/T on a hosted generated design now goes through the shared resolver for any
host, not a fixed list, and keeps the last valid pose when the rotated pose is
refused (an off-centre design could rotate off a column or block top). The
block reconciliation budget asserts work counts instead of wall-clock time,
which timed out on the CI runner, and the childless zero-work test no longer
depends on test order.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013LKpG6PpZe6Jb4dBBAKtBG
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