* fix(editor): only start handle drags on primary button
Right-click over a rotation/move/resize handle started the gesture and
stopPropagation()'d, fighting the camera orbit. Guard every gesture
starter (shared useHandleDrag, group rotate gizmo, wall endpoint/height/
move, fence move, roof trim) with event.button !== 0 before it swallows
the event.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(viewer): default units from timezone/locale until user picks
Derive the metric/imperial default from the IANA timezone (US, Liberia,
Myanmar zones -> imperial; anything else -> metric), falling back to an
explicit locale region subtag only when no timezone resolves. Timezone
tracks actual location, unlike navigator.language where en-US is a common
default far outside the US. The unit is only persisted once the user
explicitly sets it, so an untouched preference keeps tracking location;
existing persisted values are treated as explicit and left alone.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(viewer): toggle shadows via renderer.shadowMap.enabled, not castShadow
Flipping a light's castShadow at runtime crashes three r184's WebGPU
renderer: toggling off disposes the shadow map's GPU texture, but the
node builder cache evicts with the post-toggle key, so the shadows-on
entry survives still referencing the destroyed texture. Re-enabling
reuses that stale state and every frame submit fails with
GPUValidationError ("Invalid CommandBuffer from CommandEncoder").
Keep castShadow static and drive the user-facing toggle through the
Canvas shadows prop (renderer.shadowMap.enabled), which rebuilds
materials without disposing shadow resources.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(core,editor): floor undo history at scene load
Undo could step back past the scene load into the pre-load (empty)
state, wiping the whole project — which autosave would then persist.
Two defects: clearSceneHistory() had zero call sites, so every load left
the empty pre-load state in zundo's pastStates; and setScene wrote the
store twice, recording a half-normalized intermediate as a second undo
target.
- applySceneGraphToEditor, JSON import, and reset-to-default now clear
history so the loaded scene is the undo floor
- setScene collapses to a single tracked write (final state identical)
- clearSceneHistory also resumes tracking so a load landing inside a
pause window can't strand undo recording off
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* build(three): upgrade runtime to 0.185.1, pin @types/three at 0.184
r185 renames directionToColor/colorToDirection to packNormalToRGB/
unpackRGBToNormal and splits SSGI's packed rgba output into separate AO
(getAONode, single channel) and GI (getGINode) textures; wind-node's
positionLocal reads become positionGeometry.
@types/three stays at 0.184.1: the 0.185 typings send tsgo's inference
into unbounded allocation (microsoft/typescript-go#2125 class — it ate
~70GB/90s and OOM-killed the machine). viewer/lib/tsl-compat.ts bridges
the two renamed TSL exports with 0.184-typed signatures; drop it and the
pin together once tsgo copes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(viewer): gate .ktx2 preset-texture loads on detectSupport
KTX2Loader.load throws before detectSupport has run, and materials
created while a standalone capture canvas's renderer was still
initializing cached themselves permanently texture-less — fabric slots
rendered white in item thumbnails. .ktx2 loads now await whenKtx2Ready()
(resolved by the first successful ensureKtx2Support), which is exported
so hosts with standalone canvases can arm it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(viewer): accept host-supplied country for the unit default
applyCountryUnitDefault lets the host app feed an authoritative
IP-derived country (e.g. Vercel's x-vercel-ip-country) into the unit
default. Stronger signal than the timezone heuristic applied at store
creation, still never overrides an explicit user choice.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(editor): box select starts over locked guide images
A locked guide's hit-rect swallowed pointer-down via stopPropagation, so
marquee selection couldn't start on top of it. Locked guides now let the
event bubble to the svg root; click-to-select and the unlock affordance
still work because a non-drag release fires onClick as before.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(editor): live rotation readout while rotating a guide image
Rotating a guide with the 2D handles gave no angle feedback. Reuse the
registry layer's RotationAngleOverlay (wedge + degree chip) for guide
rotate drags: sweeps from the grabbed corner's bearing at grab to its
current snapped bearing, suppressed under ~0.5deg so a fresh grab doesn't
flash a sliver.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(editor): lingo free-text input in the guide set-scale dialog
The real-length field accepts natural measurement text via
@pascal-app/lingo — 5'11", 180cm, 1m80, 12ft — parsed in the dropdown's
unit (a bare number still means that unit, a typed unit wins). A faint
'= 1.80 m' hint previews non-trivial input, unparseable text gets a
clear error, and the odd onBlur force-reset to 0.0001 is gone.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(editor): cmd+z mid-interaction cancels the gesture instead of undoing
Undo pressed while the mouse is mid-action (moving, drawing, dragging a
handle) used to history-jump under the live pointer — stale carry, half
gestures committing against a rewound scene. Now it reads as 'abort this
action', exactly like Escape:
- the global undo/redo arms first route through the tool:cancel path
(covers build drafts, placement ghosts, move tools) and skip the
history jump when anything was in flight (consumed, scope-active, or
inputDragging);
- pointer drags that only knew pointercancel (generic handle drags,
group rotate, wall side/height handles, roof trim) gain the same
capture-phase Escape/cmd+z keydown the group-move drags already had —
fixing Escape for them too;
- the existing capture-phase handlers (3D/2D group move, 2D registry
move overlay) additionally accept cmd+z as cancel.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(editor): box select arms over any guide image that won't drag
Follow-up to cd830279, which only let LOCKED guides bubble pointer-down.
An unlocked, unselected guide also swallowed the event for nothing (no
translate drag starts), so marquee selection could never start on top of
it. Now only the one case that uses the event consumes it — selected +
unlocked → translate drag — and everything else bubbles to the svg root.
Click-to-select still works: a non-drag release never crosses the
box-select threshold, so the trailing click fires the guide's onClick.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(editor): cmd+z during draft placement cancels instead of undoing
The preset/item placement flow (useDraftNode + placement coordinator)
registers no interaction scope and holds no pointer, so the cmd+z cancel
guard from c699d74e saw it as idle and history-jumped mid-placement.
Paused scene history is the universal tell — the draft cycle (and every
adopted-move session) keeps temporal paused for the whole gesture, and
an undo against a paused store lands on a stale baseline anyway. Treat
!isTracking as in-flight.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(editor): cmd+z mid-placement completes the cancel, not just skips undo
4ffda723 stopped the history jump during preset/item placement but left
the draft alive: the item tool passes no coordinator onCancel — it is
Escape's fall-through (switch to select, unmount the tool) that actually
destroys the draft. Extract that fall-through and run it from the cmd+z
path too whenever a gesture is live and nothing consumed tool:cancel,
so cmd+z now behaves exactly like Escape end to end.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(editor): node selection clears a lingering reference selection
Selecting a guide clears the node selection (handleGuideSelect), but the
reverse was never wired: clicking a wall with a floorplan reference
selected left selectedReferenceId set, and the panel manager's
reference-first priority kept showing the floorplan panel until it was
closed by hand. PanelManager now drops the stale reference the moment a
scene selection (nodes or zone) appears.
Also: the inspector's expanded state is shared across panel swaps by
design, but it survived close/reopen too — deselecting everything now
resets it, so a fresh selection opens the panel collapsed again.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(plugin-trees): wind displacement reads positionLocal, not positionGeometry
The three-0.185 migration renamed positionLocal to positionGeometry in
the wind nodes, but positionLocal was never removed in r185 — and the
two are not interchangeable here. NodeMaterial.setupPosition applies the
instance transform by mutating positionLocal, then overwrites it with
positionNode's output; reading raw positionGeometry therefore discarded
every instance matrix — leaf cards rendered unscaled at tree-local
coordinates (a giant canopy filling the sky) and grass/flower instances
collapsed invisibly. Reading positionLocal (instance transform included)
restores r184 behavior exactly.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Make the editor packages compatible with, and benefit from, the native Go
compiler (TypeScript 7 / tsgo) while keeping the npm publish path on stable tsc.
- Add @typescript/native-preview (pinned 7.0.0-dev.20260624.1). tsgo coexists
with typescript@6, which Next typegen and the IDE plugin still need until the
TS 7.1 programmatic API ships.
- Fix two react-three-fiber JSX augmentations the native checker rejects but
tsc tolerated:
- viewer: map only LineBasicNodeMaterial (the one webgpu node material used as
a JSX tag) instead of the whole three/webgpu namespace (TS2320/TS2590).
- plane-box-select-tool: drop the redundant `IntrinsicElements extends
ThreeElements` block — it duplicated R3F's global augmentation and, by
referencing @react-three/fiber's ThreeElements directly, hit a bun peer-dep
variant-directory duplicate under tsgo (TS2320). r3f.d.ts already covers
those JSX intrinsics. Types-only — no runtime change.
- check-types -> tsgo --noEmit (editor, @repo/ui, editor app, ifc-converter app)
- emit-package dev watch -> tsgo --build --watch
- build/publish stay tsc --build (prepublishOnly + release.yml unchanged;
emitted .d.ts is byte-identical to tsc output)
- bump next 16.2.6 -> 16.2.9
tsc remains the source of truth and fallback. Published artifacts unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The v0.9.0 GitHub release declared the trio at 0.9.0 but the npm publish
never ran (npm still serves 0.8.0). Aligning manifests so the release
workflow's bump=patch lands everything at the announced-next 0.9.1
(mcp 0.3.0 -> 0.3.1, ifc-converter 0.1.0 -> 0.1.1 keep their own lines,
matching the per-package version table style of previous releases).
Inter-package peer/dev refs move to ^0.9.0 so the workspace stays
self-linked during the release run; the workflow re-syncs them to the
final versions after bumping.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Range edits:
- next 16.2.1 -> 16.2.7 in apps/editor + apps/ifc-converter. 16.2.5/16.2.6
fixed six high-severity advisories (middleware bypass, DoS, SSRF, XSS,
RSC cache poisoning); 16.2.7 is bugfix backports. No API/config changes.
- typescript 6.0.2 -> 6.0.3 everywhere, including aligning the 5.9.3
stragglers (@pascal-app/mcp, @repo/ui, @repo/eslint-config) that date
from the original scaffold and were never intentionally held back.
- @types/node in @pascal-app/mcp: ^25.5.0 -> ^22.19.20 — mcp was the only
workspace typed against non-LTS node 25; 22 matches the release
workflow's node and every other workspace.
- @number-flow/react ^0.5.14 -> ^0.6.0 (packages/editor, apps/editor);
the only breaking change (removed --number-flow-char-height CSS var)
is unused here.
- agentation ^2.3.2 -> ^3.0.2 (apps/editor devtool; v3 is a drop-in for
the props-less <Agentation /> usage, additions are opt-in).
- Root overrides: @types/react 19.2.14 -> 19.2.17,
@types/three 0.184.0 -> 0.184.1 (types-only fixes).
Lockfile refresh within existing ranges picks up react 19.2.7 (pairs with
next 16.2.7 — 19.2.6 had a server-action FormData regression), motion
12.40.0, three-mesh-bvh 0.9.10, @react-three/uikit-lucide 1.0.73, the
June radix wave, lucide-react 1.17.0, zustand 5.0.14, tailwind-merge
3.6.0, geist 1.7.2 (fixes Geist Mono ligature regression), react-grab
0.1.44, biome 2.4.16 + ultracite 7.8.2, turbo 2.9.17.
Held: three stays 0.184.0 (npm latest; single-instance constraint),
tailwindcss 4.3.0 + lightningcss 1.32.0 already match the pinned
optionalDependencies native binaries, eslint 10 major not taken
(@repo/ui is unconsumed legacy scaffold).
Verified: turbo build, check-types, biome check, and 931 package tests
green; bun.lock stays lockfileVersion 1 (CI bun 1.3.0 compatible);
single next/three resolution confirmed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two bugs in one fix:
1. The 0.8.0 release commit only bumped .version, not the inter-package
peerDependencies / devDependencies. So the published packages still
declared peer constraints like "@pascal-app/viewer": "^0.7.0". Caret
in semver 0.x doesn't allow 0.8.0 to satisfy ^0.7.0, so bun resolves
workspace consumers (and apps/editor's deep import paths) to the
stale npm-published 0.7.0 instead of the workspace 0.8.0 — meaning
local edits never show up in apps/editor or any linked consumer.
2. The release.yml sync step was using $GITHUB_ENV to read back the new
versions in the same step, which doesn't work — env-file writes only
surface in subsequent steps. Switched to a bash associative array
(NEW_VERSIONS) for in-step lookup, kept the $GITHUB_ENV write for the
downstream publish/commit/tag steps. Also added a final "refs after
sync" debug print so this is visible in the workflow log.
After this lands and you bun install, packages/editor/node_modules/@pascal-app/viewer
should symlink to the workspace packages/viewer/, not to .bun/@pascal-app+viewer@0.7.0.
Future releases will sync peerDeps correctly on their own.
Wholesale swap of packages/{core,viewer,editor,mcp} and apps/editor with the
versions from the private editor repo, which is the production source of truth.
Setup changes:
- packages/{core,viewer,editor} versions held at 0.7.0 baseline (matching
the most recent published release) so a bump=minor publishes 0.8.0
- packages/mcp held at 0.1.1 (never published; first publish will go through
the new release.yml flow)
- peerDependencies and devDependencies for inter-package @pascal-app/*
references pinned to ^0.7.0 instead of '*' / 'workspace:*' so they are
valid for npm consumers
- Root package.json: TypeScript bumped to 6.0.2, added overrides for
@types/react, @types/react-dom, @types/three to prevent JSX namespace
fragmentation across the workspace
- release.yml extended to also publish editor and mcp; 'both' option renamed
to 'all'; added a sync step that updates inter-package peerDeps/devDeps to
match the new versions on every bump (so viewer/editor/mcp tarballs always
reference the version of core they were built against)
- Root scripts gained release:editor and release:mcp shortcuts
Verification:
- bun install --frozen-lockfile is consistent
- packages/{core,viewer,mcp} build cleanly, dist/index.d.ts emitted
- packages/editor check-types reports 21 pre-existing errors, identical to
what private-editor currently reports
Open PRs against editor-v2 will need rebasing/conflict resolution.
* feat(community): filter empty projects from public gallery
Add is_empty column to projects table so the public gallery only shows
projects with actual content. Scene graph emptiness is computed on save
and on creation, and a backfill migration marks existing non-empty
projects. Also cleans up import ordering, fixes README migration path,
and removes stale feedback table migration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use ESNext modules and bundler resolution
Update packages/db tsconfig to emit ESNext modules and use 'bundler' moduleResolution. This enables modern ESM output and bundler-style resolution for the db package (keeps outDir and rootDir unchanged).
* feat(editor): add dark mode theme and grid visibility toggle
Add theme state to viewer store with light/dark modes, dark-aware
lighting and background colors, grid show/hide toggle in settings,
and theme toggle buttons in the sidebar.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add ground occluder, animated lighting & UI tweaks
Introduce a ground occluder and smoother animated lighting while adjusting editor UI and icons. Viewer: add GroundOccluder (uses polygon-clipping to union slab/zone polygons) and AnimatedBackground, integrate both into the Canvas; improve Lights to interpolate intensities/colors/ambient for smooth theme transitions. Editor: refactor sidebar header to support inline project title editing and move the theme toggle; replace some lucide icons with image assets and add a settings icon (apps/editor/public/icons/settings.png). Also add polygon-clipping to the viewer package dependencies.
* Update dark mode background color and clean up imports
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Tree: add isLast prop & draw connectors
Introduce an optional isLast prop across all site panel tree node components and TreeNode so children know when they're the last sibling. TreeNodeWrapper now renders vertical and horizontal connector lines (adjusted by depth and isLast) and keeps the toggle button clickable (z-index). Children are passed isLast when mapped, so the UI can stop the vertical line for final siblings. Also bump several deps in apps/editor package.json and update the lockfile accordingly.
* chore: clean up monorepo dependencies and update packages
Move app/library dependencies out of root package.json into their
respective workspaces. Root now only contains monorepo tooling
(turbo, biome, supabase CLI, typescript, portless, ultracite).
Updates:
- next 16.1.0 → 16.1.6
- drizzle-orm ^0.39.0 → ^0.45.1
- drizzle-kit ^0.30.0 → ^0.31.9
- drizzle-zod ^0.5.1 → ^0.8.3
- three-bvh-csg ^0.0.17 → ^0.0.18
- supabase 2.75.3 → 2.76.15
- typescript 5.9.2 → 5.9.3 (all workspaces)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use semantic theme tokens and enable dark mode
Replace hardcoded zinc color classes with semantic design tokens (text-muted-foreground, text-foreground, border, bg-background, bg-border) across the action menu, camera controls, control modes, and view toggles for consistent theming. Update separators and borders to use bg-border/border-border. Add useEffect in the editor root to add/remove the 'dark' class on document.body and apply 'dark text-foreground' to the root container so the UI renders in dark mode; also import useEffect.
* Persistable sidebar width & resizer
Introduce a zustand store to persist sidebar width (key: `sidebar-preferences`) and track dragging state. Default width is 288px (18rem) and is clamped between 288 and 800px. Wire the store into SidebarProvider to drive the CSS variable for width and expose a data-dragging attribute. Add a SidebarResizer component that handles pointer events to resize the sidebar, toggles dragging state, updates cursor/user-select, and disables transitions while dragging. Small class and structure adjustments ensure the resizer is rendered and resizing behaves smoothly.
* Editor: floating action menu, selection/phase tweaks
Add a FloatingActionMenu (move/duplicate/delete) that attaches to selected 3D nodes and integrates with editor/viewer state; includes new cursor SVG and layout wrapper to use the custom cursor. Improve selection and phase behavior: auto-select first building+level on scene load (moved/centralized in project scene hook and editor init), add resolveBuildingId helper, and enhance SelectionManager to auto-switch between structure/furnish based on clicked nodes and to preserve building/level context when changing selection. Update sidebar tree node click handlers to switch phases for structural vs item nodes and stop hiding nodes by phase (keep tree items visible across phases). Add keyboard escape behavior to clear selections while keeping building/level context and reset selected reference. Misc: register unified event listeners for selection, position floating menu over objects using three.js Box3, and various small integration fixes.
* Add SceneLoader, loading states & editor fixes
Add a SceneLoader component and associated CSS loader styles and wire it into Editor, Viewer and page-level flows so a loading UI displays while project/scene data loads. Replace useLayoutEffect in editor page with a client-mounted useEffect guard to avoid hydration issues. Introduce isSceneLoading state and setter in the project store and set/clear it in useProjectScene; include sceneGraph when creating a new project and clear the scene beforehand.
Other changes: type-guard moving/duplicating logic in FloatingActionMenu and remove duplicated id on clones; refactor auto-selection logic for site/building/level and auto-select build/wall for empty levels in editor index and useProjectScene; optimize ZoneSystem to run per-frame (useFrame) and reduce unnecessary updates to visibility/label positions; add animated active background for layer toggle (motion) in site panel; add keyboard shortcuts to jump between levels (Ctrl/Cmd+ArrowUp/Down); simplify community-hub project-created handler to navigate straight to editor. These changes improve UX during load, enforce safer node operations, and optimize runtime rendering.
* Improve selection logic and refine UI components
Multiple editor and viewer updates:
- Selection: enhance selection-manager to support modifier (Meta/Ctrl) multi-select, compute next selection logic, track modifier keys, prevent grid deselect races, and add debug logs. Adapted selection handlers throughout scene/editor components to pass native events and modifier state.
- Sidebar / Site panel: refactor site panel to inline a LevelReferences UI (replacing the removed references dialog). Add upload flow for scan (.glb/.gltf) and guide images, handling file size/type checks, project asset upload/delete calls, and per-level reference lists. Add MultiSelectionBadge for showing/clearing multi-selection and animated expand/collapse for levels. Several tree-node components now use a shared handleTreeSelection helper and propagate click events properly.
- UI polish & icons: change dark theme color tokens for better contrast; increase button sizes and icon sizes across action menus; swap some lucide icons for image assets (rotate, topview, select, build, mesh, floorplan, level, etc.) and add new public icons.
- PascalRadio: replace popover with a framer-motion expandable panel, add outside-click handling, animated transitions, and minor UI tweaks.
- Other: minor viewer hook/selection-manager updates and tweaks to control modes and view toggles to support image icons and consistent sizing.
These changes improve multi-select reliability, user interactions in the sidebar, and overall visual polish with new icons and animations.
* Fix TypeScript build errors in selection-manager and scene-loader
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>