Commit Graph
19 Commits
Author SHA1 Message Date
d0ac9fb739 fix(editor): guard GLB export against undefined array-material slots (Sentry MONOREPO-EDITOR-79) (#459)
Co-authored-by: Anton Pascal <anton-pascal@users.noreply.github.com>
2026-07-19 17:40:01 +02:00
088f6c50a4 fix(editor): decompress compressed textures on GLB export (#435)
three r184's GLTFExporter throws 'setTextureUtils() must be called' when a
scene contains KTX2/basis-compressed textures. Wire up WebGPUTextureUtils
(matching the app's WebGPURenderer) so compressed textures are decompressed
during GLB export.

decompress() is called without the live renderer on purpose: it resizes
whatever renderer it is given and never restores it, which would corrupt the
visible canvas. Omitting it lets three create and dispose its own throwaway
renderer for the blit.

Reworked from #435 (anton-pascal); switches WebGL->WebGPU utils and avoids the
live-renderer resize gotcha surfaced in review.

Co-authored-by: Open Pascal <open@pascal.app>
2026-06-24 15:56:30 -04:00
5fe0965297 fix(viewer): graceful fallback when WebGPU/WebGL is unavailable (#403)
Adds a capability check and an UnsupportedGpuViewerFallback so the viewer
renders an informative panel instead of crashing on environments that expose
neither WebGPU nor WebGL. Capability detection runs post-mount to stay
hydration-safe, the renderer-init failure path swaps to the fallback, and the
fallback signals scene-readiness so the host editor loader does not hang.

Reworked from #403 (anton-pascal) onto current main; resolves the import
conflict and the SSR/scene-ready gotchas surfaced in review.

Co-authored-by: Open Pascal <open@pascal.app>
2026-06-24 15:56:26 -04:00
8593c55afe fix(nodes): guard previewRef in fence tool onGridClick (Sentry MONOREPO-EDITOR-BE/CR) (#434)
Co-authored-by: openclaw-agent <agent@pascal.app>
2026-06-24 15:52:25 -04:00
05771270e3 fix(core): coerce unknown material.preset to 'custom' (Sentry MONOREPO-EDITOR-DB) (#400)
Co-authored-by: openclaw-agent <agent@pascal.app>
2026-06-24 15:52:22 -04:00
AntonandGitHub d926dede78 fix(viewer): normalize legacy door nodes against the schema before render (#303)
Legacy/unparsed door nodes can miss schema-defaulted fields (segments, columnRatios, dividerThickness, panelInset/panelDepth …) and crash the door geometry build (EDITOR-AM). `updateDoorMesh` re-applies the Zod defaults once at entry via `DoorNodeSchema.safeParse` (with a drop-bad-segments retry and a full-defaults fallback), so every downstream read sees populated data — including the shaped-top divider path #334 added. Also normalizes schema-valid legacy doors at load in `migrateNodes`, mirroring the existing stair normalizer, so the door panel and window system see defaulted data too.

Reimplemented against main: the original used `segments ?? []` (wrong — legacy doors rendered an empty frame) and missed the #334 `dividerThickness` sites. Verified: `bun run check-types` clean, biome clean on touched files.
2026-06-03 16:07:32 -04:00
AntonandGitHub 2809b016f0 fix(editor): prune only invalid-geometry meshes before export (#313)
Three exporters (STL/OBJ/GLTF) crash when traversing a Mesh with missing/disposed/empty geometry (EDITOR-6H/6G/79). `prepareSceneForExport()` clones the scene and removes ONLY meshes whose position attribute is missing or zero-count — collected in a single traverse and removed afterward (no mutation during traversal). Lines, points, groups, cameras, lights, bones and valid skinned meshes are preserved, so OBJ/GLB output is no longer corrupted. STL/OBJ still throw and GLTF still rejects on other errors — no silent no-op.

Reimplemented against main without the original PR's all-non-mesh pruning, mutate-during-traverse loop, and error-swallowing try/catch. Verified: `tsc -p apps/editor` clean, biome clean.
2026-06-03 15:57:05 -04:00
AntonandGitHub cefcb013d9 fix(editor): null-safe cursor-group access in placement coordinator (#323)
Guards every `cursorGroupRef.current` dereference in the placement coordinator against the null window where mitt listeners are live but the `<group>` is unmounted (mount/teardown race) — the EDITOR-BC/BD crash family.

`getContext()` falls back to the draft's rotation, so the validation/revalidate path (Shift keys, onKeyUp) is safe; only the cursor *writes* are guarded, so Escape/right-click cancel, Shift reset, leave-state cleanup and transition state still run unconditionally. Also guards `wallPreviewRef` inside `WallTool.stopDrafting()` (the double-click/cancel path that the earlier diff missed).

Reimplemented against current main (the original branch conflicted with #366 and used over-broad handler guards that silently dropped cancel/Shift). Verified: `tsc -p apps/editor` clean, biome clean on touched files.
2026-06-03 15:54:21 -04:00
AntonandGitHub 98eeb1da90 fix(nodes): coalesce node.children to [] in container renderers (#333)
Guards building/ceiling/site/wall renderers with `(node.children ?? [])` so a node whose `children` array is missing (legacy/unparsed scene data) no longer crashes the renderer with "Cannot read properties of undefined (reading 'map')" (EDITOR-C0). Matches the existing guard in roof/renderer.tsx and the Array.isArray check in the parametric renderer.

Note: the schema declares `children: z.array(...).default([])`, so this can only be hit by data that bypasses Zod normalization on load. This is a defense-in-depth crash-stopper; the deeper fix is to normalize/parse legacy nodes in migrateNodes (use-scene.ts) so missing arrays are repaired before render — tracked as a follow-up.
2026-06-03 14:30:12 -04:00
AntonandGitHub 1256331aa6 fix(editor): guard onShelfMove against null cursorGroupRef (#342)
Adds the missing `if (!cursorGroupRef.current) return` guard to onShelfMove. mitt listeners are registered for the tool's whole lifetime, so a shelf event can fire before the cursor group mounts or after teardown, leaving the ref null and throwing on `cursorGroupRef.current.rotation.y`.

Partial fix for the EDITOR-BC family: the other placement handlers (onGridMove/onWallMove/onItemMove/onCeilingMove and the keyboard/cancel paths) share the same null-ref exposure and still need the broader, correctly-scoped guard — tracked separately (PR #323 attempts this but needs rework).
2026-06-03 14:28:46 -04:00
f06f2fa1b1 feat: 2D editing — floorplan panel, measurements, command palette (v0.3.0)
* sync: port 2D editing features from monorepo (v0.3.0)

## New Features

### Floorplan Panel (7.5K LOC)
- Full 2D editing interface with wall drawing, measurement, and unit display
- Interactive floorplan view with pan/zoom, grid snapping
- Wall thickness visualization, door/window placement
- Zone and slab polygon editing
- Guide image overlay support
- Metric/imperial unit toggle

### Command Palette Overhaul
- Complete rewrite with new command registry system
- Keyboard shortcuts UI with ShortcutToken component
- Editor-specific commands (floorplan, measurements, camera focus)
- Improved search and action organization

### Wall Measurements
- Real-time wall length labels in 3D view
- Metric/imperial conversion
- Wall measurement UI component

### Enhanced Tools
- Wall drafting utilities (grid snapping, validation)
- Node action menu for quick operations
- Improved polygon editing for zones/slabs/sites
- Roof segment panel for granular roof control

## Package Changes

### @pascal-app/core@0.3.0
- New wall-footprint.ts: 2D wall footprint calculation
- Wall mitering exports for floorplan view
- camera-controls:focus event
- Space detection undo pause/resume
- Mark sibling nodes dirty on deletion (miter recalc)

### @pascal-app/viewer@0.3.0
- ErrorBoundary component for robust item rendering
- Broken item fallback UI
- Wall renderer: mark dirty on mount
- Ground occluder: only lowest level punches through
- Unit state (metric/imperial) in viewer store

### @pascal-app/editor@0.1.0
- 48 file changes (16 new, 32 modified)
- New stores: useCommandRegistry, usePaletteViewRegistry
- Tree node drag-and-drop system
- Level selection utilities
- Enhanced scene graph operations

## Apps/Editor
- GeistPixelSquare font for pixel-perfect UI
- Blueprint icon asset
- Updated layout and globals for font support

## Security
- Zero AI imports or internal package refs
- All monorepo-specific code excluded
- Clean audit: no API keys or secrets

## Testing
- Security audit passed 
- All AI/internal code excluded 
- Version bumps applied 

79 files modified, 16 files added
~2600 insertions, ~1400 deletions

* fix: lint cleanup — suppress intentional dep warnings, fix missing dep, remove stale ignores

- Add biome-ignore for 3 intentional useEffect reset patterns (levelId, selectedGuide, selectedId)
- Fix actual missing dependency: currentBuildingId in handleSiteEditShortcutSelect callback
- Remove 3 stale biome-ignore comments in r3f.d.ts (rule not active)

Build and lint pass clean.

* fix: type errors — polygon area guards, selection cast, readonly keywords, door guard

- ceiling-panel, slab-panel, ceiling-tree-node, slab-tree-node, zone-tree-node:
  guard polygon[i]/polygon[j] array access before arithmetic (TS2532)
- scene.ts: introduce toViewerSelection() helper to cast persisted string IDs
  to branded template literal types expected by useViewer.setSelection (TS2345)
- door-panel: guard early return when node is undefined in setSegmentHeightRatio (TS18048)
- editor-commands: remove 'as const' from inline command object, keywords is mutable string[] (TS2322)

All type checks pass. Build and lint clean.

---------

Co-authored-by: Anton Pascal <anton-pascal@users.noreply.github.com>
2026-03-24 19:48:11 +00:00
09e212ad40 feat: Esc exits build mode back to select mode (#120)
When in build mode, pressing Escape now switches back to select mode
in addition to canceling any in-progress tool operation.

Requested by Julien.

Co-authored-by: Anton Pascal <anton-pascal@users.noreply.github.com>
2026-02-25 18:25:12 -05:00
94e5f37983 fix: create project-thumbnails bucket in instrumentation.ts (#118)
The thumbnail upload (uploadProjectThumbnail) writes to a
'project-thumbnails' storage bucket, but instrumentation.ts only
bootstrapped the 'avatars' bucket.  This caused:

  Upload failed: Bucket not found

Add the missing bucket creation with matching constraints (public,
10 MB limit, image/png only).  Also refactor the bucket-exists check
to use a Set so it scales cleanly as more buckets are added.

Co-authored-by: Anton Pascal <anton-pascal@users.noreply.github.com>
2026-02-25 12:22:10 -05:00
5781e98270 feat: add YouTube social link to user profiles (#114)
* feat: add YouTube social link to user profiles

- Add youtube_url column to auth_users schema
- Add DB migration for the new column
- Update updateProfile action with YouTube URL validation
- Update getUserProfile and getPublicProfile to include youtubeUrl
- Add YouTube input field to settings page
- Display YouTube icon on public profile pages

* fix: use drizzle-kit generate for youtube_url migration

Replaces manually created migration with proper drizzle-kit generated
migration that includes meta snapshot and journal entry.

---------

Co-authored-by: Anton Pascal <anton-pascal@users.noreply.github.com>
2026-02-24 05:38:40 +00:00
c476d30303 feat: enhance feedback form with image upload, user/project context, and scene graph (#113)
* feat: enhance feedback form with image upload, user/project context, and scene graph

- Add invisible drag-and-drop zone that reveals on hover+drag with dashed border overlay
- Multi-image upload (max 5, max 5MB each) to Supabase Storage feedback-images bucket
- Subtle attach button + thumbnail previews with remove on hover
- Auto-capture authenticated user email/name from Better Auth session
- Pass projectId from editor context
- Snapshot scene graph (nodes + rootNodeIds) on submit
- DB migration adds user_email, user_name, project_id, images (jsonb), scene_graph (jsonb) columns
- Storage bucket + RLS policies for public read / service role write

* refactor: use existing user_id FK instead of denormalized email/name columns

Removed user_email and user_name — the user_id already links to the users table.
Simpler schema, no data duplication.

* fix: guard against undefined in removeImage

* refactor: direct Supabase Storage upload via signed URLs

Bypass Vercel's 4.5MB serverless body-size limit by uploading images
directly from the client to Supabase Storage.

- New createImageUploadUrls server action generates signed upload URLs
- Client PUTs files directly to Supabase (no bytes through Vercel)
- submitFeedback now receives only image paths, not FormData with files
- No migration changes needed (existing RLS policies support signed URLs)

* fix: remove relative class that broke dialog centering

twMerge was replacing the Dialog's fixed positioning with relative,
pushing the dialog to the bottom of the viewport.

---------

Co-authored-by: Anton Pascal <anton-pascal@users.noreply.github.com>
2026-02-22 23:52:42 -05:00
88cdbc6806 fix: add Supabase branching safety check for preview deployments (#109)
- Removed 9 stale branch-specific env vars on Vercel that were locked
  to the deleted feat/refactor-supabase-setup branch
- Added runtime warning when a preview deployment detects it's using
  the production Supabase instance (branching skipped/misconfigured)

The root issue: Supabase branching only creates preview branches for
PRs that include migration changes. PRs without migrations get SKIPPED
and fall through to the generic env vars, which include 'preview' in
their targets and point to production Supabase.

To fully fix: configure Supabase branching to always create preview
branches, or remove 'preview' from generic Supabase env var targets
on Vercel (requires branching to be working first).

Co-authored-by: Anton Pascal <anton-pascal@users.noreply.github.com>
2026-02-20 05:34:54 +00:00
557781950d fix: reduce autosave function invocation spam (25x anomaly) (#108)
* fix: reduce autosave function invocation spam (25x anomaly)

Root cause: useProjectScene() was mounted in BOTH editor/index.tsx
AND project-dropdown.tsx, creating duplicate zustand subscriptions
that each independently triggered saveProjectModel() server actions.

Combined with a 2-second debounce and the fact that each server
action also calls getSession() (another function invocation), active
editing was generating ~120+ invocations/minute.

Changes:
- Remove duplicate useProjectScene() from project-dropdown.tsx
- Increase autosave debounce from 2s to 10s
- Skip auto-save on initial scene load from server (was saving the
  scene right back after loading it)
- Add pending-save coalescing: changes during in-flight saves get
  batched into one follow-up save instead of queuing concurrently

* fix: flush unsaved scene to cloud on page exit / unmount

Adds two safety nets so the 10s debounce doesn't lose data:
- beforeunload listener: fires saveProjectModel() when the user
  closes the tab or navigates away externally
- cleanup flush on unmount: fires when navigating within the SPA
  (e.g. switching projects, going to settings)

Both are fire-and-forget — if the browser kills the request,
localStorage still has the data and will sync on next load.

* style: fix biome import ordering in project-dropdown

---------

Co-authored-by: Anton Pascal <anton-pascal@users.noreply.github.com>
2026-02-20 05:01:59 +00:00
2dcb80554c feat: add terms, privacy pages, email notification preferences, and sign-in consent (#107)
- /terms — Terms of Service for editor + pascal.app platform
- /privacy — Privacy Policy (data collection, third parties, user rights)
- Email notification toggle in Settings (new column + migration)
- Sign-in consent text linking to terms/privacy
- Effective date: February 20, 2026

Co-authored-by: Anton Pascal <anton-pascal@users.noreply.github.com>
2026-02-20 04:02:08 +00:00
AntonandGitHub ff85765728 fix: resolve react-doctor errors (hooks order, reduced motion, key prop) (#105)
- Move useCallback hooks before early return in ReferencesDialog
  to fix conditional hook call violation (Rules of Hooks)
- Add useReducedMotion hook and apply to all motion animations
  for WCAG 2.3.3 accessibility compliance
- Replace useEffect state reset with key prop on ItemCatalog
  for proper React reconciliation on category change
- Add global prefers-reduced-motion CSS media query

Fixes 4 react-doctor errors, bringing score from 81 to ~85+.
2026-02-19 19:20:05 +00:00