Technical Briefing – editor (Pascal Editor) Clone: https://github.com/moses-y/editor

Overview

This Turborepo monorepo (664 files) provides a browser‑based 3D building editor. The core stack is React Three Fiber + Next.js (apps/editor) with three packages: @pascal-app/core (schemas, Zustand scene store, spatial queries), @pascal-app/viewer (3D rendering), and apps/editor (UI, tools, behaviours). State is managed via three Zustand stores (useScene, useViewer, useEditor). Nodes are flat dictionary entries with parentId links; a scene‑registry map (Map<id, Object3D>) lets renderers pull refs without graph traversal.

The Problem

The codebase suffers from high‑radius instability and circular import chains that make safe modifications risky. The hub module packages/editor/src/store/use-editor is imported by 44 other modules (instability 0), and 31 files participate in circular dependencies (e.g., tree-node.tsx, use-scene.ts, node-renderer.tsx). Adding or refactoring a node type can ripple through these edges without compile‑time protection, and there are no test files to verify behaviour after change.

What This Does

  • Schema & state – packages/core/src/schema/index.ts defines node types (Wall, Item, Zone, etc.) that extend BaseNode; packages/core/src/store/use-scene.ts provides CRUD middleware (persist, undo/redo).
  • Rendering – packages/viewer/src/components/renderers/node-renderer.tsx registers refs via useRegistry; renderers for walls, slabs, roofs etc. live under packages/viewer/src/components/renderers/.
  • Editor UI – packages/editor/src/components/editor/index.tsx composes the main toolbar; packages/editor/src/components/ui/action-menu/index.tsx and command-palette/index.tsx provide tool selection. The oversized site-panel/index.tsx (1413 loc) and item-catalog/catalog-items.tsx house bulk of the UI logic.
  • Import graph – 237 internal modules, 596 edges; 31 modules in cycles; hub use-editor (Ca 44, Ce 0) and utility lib/utils.ts (Ca 43, Ce 0) are the most connected points.

How It Is Wired

  1. Entry point – apps/editor/app/page.tsx renders the <Editor /> component.
  2. State bootstrap – useEditor (packages/editor/src/store/use-editor.tsx) is initialized; UI components read activeTool, panel visibility, etc.
  3. Node creation – Calling useEditor.getState().createNode(...) flows into packages/core/src/store/use-scene.ts which updates the flat node dictionary and triggers the persist middleware.
  4. Render sync – The scene registry (packages/core/src/scene-registry.ts) maps node IDs to Three.js Mesh refs; when a node is updated, the registry is refreshed and the corresponding renderer re‑renders.
  5. Cycle cost – Any change that touches a circular‑import member can silently break another branch; the 31‑member cycle includes tree-node.tsx, use-scene.ts, and node-renderer.tsx.

How To Use It

StepCommand / File
Clonegit clone https://github.com/moses-y/editor
Installpnpm install (root package.json + per‑package package.jsons; no lockfile is present, so a fresh pnpm install will generate one)
EnvCopy env.example → .env; required vars are documented there (currently none beyond default Next.js vars).
Dev serverpnpm dev -w apps/editor (starts Next.js on http://localhost:3000)
Buildpnpm build -w apps/editor (produces static output)

Real‑World Use

A developer wants to add a new “window” item type. They:

  1. Define a Window node type in packages/core/src/schema/types.ts (extends BaseNode).
  2. Register a renderer in packages/viewer/src/components/renderers/window-renderer.tsx using useRegistry(windowId, 'item', ref).
  3. Export the type from packages/core/src/schema/index.ts so the scene store recognises it.
  4. Add a UI button in packages/editor/src/components/ui/item-catalog/catalog-items.tsx that calls useEditor.getState().createNode({type: 'window', ...}, 'building_1').
  5. Because use-editor is the hub (44 importers), the new node instantly appears in the scene, the registry updates, and the window renders—no compile‑time breakage as long as no circular import is introduced.

Code Health & Issues

Static analysis (67 findings, 37 high, 30 medium, 0 low)

  • [HIGH/clarity] Hub module x8 – packages/editor/src/store/use-editor.tsx, packages/editor/src/lib/utils.ts, packages/editor/src/lib/sfx-bus.ts; 44 modules depend on use-editor; churn radius is high.
  • [HIGH/soundness] Import cycle member x31 – files include tree-node.tsx, use-scene.ts, node-renderer.tsx; mutually reachable imports.
  • [HIGH/cognitive_load] Oversized file x6 – site-panel/index.tsx (1413 loc), catalog-items.tsx, use-placement-coordinator.tsx.
  • [HIGH/cognitive_load] Deep nesting x9 – collections-popover.tsx, command-palette/index.tsx, item-catalog/item-catalog.tsx (max indent 9).
  • [MEDIUM/cognitive_load] High branching density x5 – spatial-grid-manager.ts, spatial-grid-sync.ts, scene.ts (169 branch points over 555 loc).
  • [HIGH/clarity] Duplicated code blocks – 971 repeated 6‑line blocks across 74 files (e.g., apps/editor/app/privacy/page.tsx, apps/editor/app/terms/page.tsx, packages/editor/src/lib/utils.ts).

SDLC observations (code‑health audit, 8 findings)

  • Pin third‑party GitHub Actions to a commit SHA (.github/workflows – oven-sh/setup-bun@v2).
  • Add a test suite; repository has no test files.
  • Open a pull request instead of pushing directly to the default branch (.github/workflows/release.yml uses git push --follow-tags).
  • Enable Dependabot/Renovate (9 manifests, no update bot configured).

The Bottom Line

The editor is a well‑structured React‑Three‑Fiber monorepo with a clear separation of core schemas, viewer rendering, and editor UI. Its strength lies in the node‑registry pattern and Zustand stores that keep scene state predictable. However, the lack of test coverage, 31 circular import cycles, and several oversized, deeply‑nested files create a high risk for any change that touches the hub use-editor module or adds new node types. Teams comfortable with React Three Fiber who are prepared to invest in test infrastructure and cycle‑breaking refactors can benefit from the platform; others may find the initial ramp‑up costly.