Skip to content

feat(editor): preset-system primitives (#340)#341

Open
wass08 wants to merge 1 commit into
mainfrom
editor/preset-system-primitives-340
Open

feat(editor): preset-system primitives (#340)#341
wass08 wants to merge 1 commit into
mainfrom
editor/preset-system-primitives-340

Conversation

@wass08
Copy link
Copy Markdown
Collaborator

@wass08 wass08 commented May 28, 2026

Summary

Editor-side primitives for the unified preset system (pascalorg/private-editor:plans/community-preset-system.md). Per the redesign discussed on #340: single live canvas, capture happens against it via isolate + the existing snapshot pipeline. No <Viewer scene={subtree}> prop, no useScene factory / React context refactor.

Closes #340.

What's in

Core

  • capabilities.presettable on NodeDefinition + isPresettable / isPresettableKind helpers. Explicit false on level / building / site / zone / spawn / guide / scan / item; implicit true for any kind with def.parametrics.
  • sceneApi.getSubtreeSnapshot(rootId) + materializeSubtree(subtree, position, parentId?) for catalog round-trips. Stripping rules from the spec: ids, root parentId, root absolute position, host refs (wallId, wallT). Fresh ids minted at materialize time; child order preserved (FIFO walk).
  • Unit tests for both: round-trip, parent/child preservation, disjoint id minting across multiple materializations.

Viewer

  • <Viewer isolate> prop + ViewerHandle.setIsolated(ids | null). Walks sceneRegistry, hides every registered group not in the isolated set's ancestor + descendant closure. Composite-visibility note in the implementation explains why ancestors must stay visible.

Editor

  • useEditor.captureMode: CaptureMode discriminated union (idle | standard | preset). isCaptureMode stays as a derived boolean (every existing read site continues to work); setCaptureMode accepts both shapes (back-compat).
  • preset capture mode wired into SnapshotCaptureOverlay: drag locked to a square, mode-picker hidden, transparent flag forwarded through camera-controls:generate-thumbnail.
  • Headless exports: Inspector, FloatingMenu, ToolbarLeft / ToolbarRight (aliases), useSelection hook, plus re-exports of useScene / useViewer so consumer shells (community, embedders) only need one import.

Out of scope by design

  • A separate offscreen Viewer rendering an arbitrary NodeSubtree. The new design captures inside the live canvas — see the issue's "Out of scope" section for the rationale.
  • useScene factory + React context. Stays a singleton.
  • In-editor "isolate selection / focus mode" UX toggle. Primitives support it; UX surface is a follow-up.

Test plan

  • bunx tsc --noEmit clean for core / viewer / editor / nodes / apps/editor
  • bun test packages/core/src — 181 pass / 5 pre-existing fails identical to main; new subtree tests pass (6/6)
  • bun test packages/editor/src — 11/11 pass
  • bun test packages/nodes/src packages/viewer/src — 113 pass / 3 pre-existing fails identical to main
  • bun run build succeeds for apps/editor
  • Manual smoke: open the editor, confirm the standard snapshot flow still works end-to-end
  • Manual smoke (consumer-side, follow-up in private-editor): community-app calls viewerRef.setIsolated([...]) + setCaptureMode({ mode: 'preset', isolated: [...] }) against the live editor canvas and gets a square transparent PNG via the existing onThumbnailCapture callback

🤖 Generated with Claude Code

…e round-trip, isolate + setCaptureMode enum, headless exports

Per #340 (redesigned: single live canvas, no Viewer scene prop).

Core
- `capabilities.presettable` on `NodeDefinition` + `isPresettable` /
  `isPresettableKind` helpers. Explicit `false` on level / building /
  site / zone / spawn / guide / scan / item; implicit `true` for any
  kind with `def.parametrics`.
- `sceneApi.getSubtreeSnapshot(rootId)` + `materializeSubtree(subtree,
  position, parentId?)` for round-tripping a node subtree through
  catalog storage. Strips id / parentId / absolute root position /
  host refs (`wallId`, `wallT`); fresh IDs minted at materialize time;
  child ordering preserved (FIFO walk).

Viewer
- `<Viewer isolate>` prop + `ViewerHandle.setIsolated(ids | null)`.
  Walks `sceneRegistry`, hides every registered group not in the
  isolated set's ancestor + descendant closure. Building block for
  preset capture + future focus-mode UX.

Editor
- `useEditor.captureMode: CaptureMode` discriminated union
  (`idle` | `standard` | `preset`). `isCaptureMode` stays as a derived
  boolean for the existing read sites; `setCaptureMode` accepts both
  the boolean shape (back-compat) and the enum.
- `preset` capture mode in `SnapshotCaptureOverlay`: drag locked to a
  square, mode-picker hidden, transparent flag forwarded through the
  `camera-controls:generate-thumbnail` emitter event.
- Headless exports: `Inspector` (alias of `ParametricInspector`),
  `FloatingMenu` (alias of `FloatingActionMenu`), `ToolbarLeft` /
  `ToolbarRight` (aliases of `ViewerToolbarLeft` / `ViewerToolbarRight`),
  `useSelection` hook returning `{selectedIds, selectedNode, building/
  level/zone}`, plus re-exports of `useScene` / `useViewer` from core /
  viewer so consumer shells (community, embedders) need only one import.

Out of scope by design (see issue #340 "Out of scope"): a separate
offscreen Viewer rendering an arbitrary subtree. The unified preset
modal captures inside the live canvas via isolation + the existing
snapshot pipeline — no `useScene` factory / React context refactor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mintlify
Copy link
Copy Markdown

mintlify Bot commented May 28, 2026

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
pascal 🔴 Failed May 28, 2026, 12:44 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

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.

Preset system: editor primitives (headless exports + unified Viewer + scene APIs + def.presettable)

1 participant