Pascal Editor Viewer Isolation: How Package Separation Enables Reusable 3D Components

The Pascal Editor achieves complete viewer isolation by housing its 3D presentation layer in the independent @pascal-app/viewer package, which consumes scene data exclusively through props and callbacks while maintaining a strict architectural ban against importing any code from apps/editor, as documented in AGENTS.md lines 78–79.

The Pascal Editor (pascalorg/editor) is architected as a monorepo that enforces strict boundaries between its editing interface and rendering engine. This examination of Pascal Editor viewer isolation reveals how the development team maintains clean package separation by treating the 3D canvas as a black-box component that depends only on @pascal-app/core for data and React-Three-Fiber for rendering. This architectural decision allows the viewer to function as a standalone, versioned package usable in read-only previews or external applications without dragging in editor-specific logic.

Why Viewer Isolation Matters

The Pascal Editor’s monorepo structure separates concerns across three distinct packages: apps/editor for editing logic, @pascal-app/core for the data model, and @pascal-app/viewer for 3D presentation. This separation serves four critical architectural goals.

Zero Coupling to Editor UI. The viewer must render standalone in read-only routes or external sites without pulling in editor chrome, toolbars, or editing logic. The architectural rule in AGENTS.md lines 78–79 explicitly states that the package never imports from apps/editor, ensuring true isolation.

Reusability Through Props and Callbacks. External applications embed the viewer as a black-box component via a controlled public API. The Viewer component in packages/viewer/src/index.ts exposes only onSelect, onExport, and selectionManager props as its interaction surface, preventing consumers from reaching into internal implementation details.

Clear Separation of Concerns. Core scene data and business logic remain in @pascal-app/core, accessed through the Zustand store (useScene), while all rendering concerns stay in @pascal-app/viewer. The viewer package lists @pascal-app/core as a peer dependency in its package.json, not a regular dependency, enforcing that the consumer provides the data layer.

Independent Versioning. Because the viewer and editor reside in separate packages with distinct package.json files, the team can upgrade React-Three-Fiber or modify rendering algorithms without forcing updates to the editing application.

How Isolation Is Enforced in the Source Code

The Pascal Editor enforces viewer isolation through three concrete mechanisms that prevent architectural drift.

Peer Dependency Architecture

In packages/viewer/package.json, @pascal-app/core is declared as a peer dependency rather than a bundled dependency. This forces the consuming application (whether apps/editor or an external site) to provide the core data model, ensuring the viewer never dictates which version of the core runs and preventing circular dependency chains.

Controlled Export Surface

The entry point at packages/viewer/src/index.ts re-exports only the public API: the Viewer component, useViewer hook, and asset helpers like resolveCdnUrl. Internal modules, store implementations, and renderer internals remain hidden from consumers, creating a strict isolation surface that prevents accidental coupling.

The Import Ban Rule

As stated in AGENTS.md lines 44–45: “The viewer accepts external props and callbacks to expose control points. It must not import anything from apps/editor.” A grep for "apps/editor" inside packages/viewer/ returns zero results, confirming the rule is observed in practice. This import ban guarantees that the 3D rendering layer remains agnostic to the editing interface’s existence.

Implementing the Isolated Viewer

The following examples demonstrate how to consume the viewer package without touching editor code, illustrating the clean separation between rendering and editing concerns.

Standalone Viewer Usage

The simplest implementation imports only from @pascal-app/viewer and renders the 3D canvas without any editor imports:

import { Viewer } from '@pascal-app/viewer'
import { useScene } from '@pascal-app/core'

export default function App() {
  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <Viewer />
    </div>
  )
}

This component renders the 3-D canvas without any editor code. – packages/viewer/README.md lines 27–35

Injecting Custom Camera Controls

Editor-specific UI stays outside the viewer; only the camera behavior is injected through the selectionManager prop or children:

import { Viewer } from '@pascal-app/viewer'
import { CameraControls } from '@react-three/drei'

export default function App() {
  return (
    <Viewer selectionManager="custom">
      <CameraControls />
    </Viewer>
  )
}

All editor-specific UI stays outside the viewer; only the camera hook is injected. – packages/viewer/README.md lines 41–52

Asset CDN Utilities

Viewer-only utilities like resolveCdnUrl live in packages/viewer/src/lib/asset-url.ts and handle 3D asset loading without editor logic:

import { resolveCdnUrl } from '@pascal-app/viewer'

const modelUrl = resolveCdnUrl('/items/chair/model.glb')
// → https://pascal-cdn.wawasensei.dev/items/chair/model.glb

Accessing Viewer-Specific State

The viewer maintains its own Zustand store in packages/viewer/src/store/use-viewer.ts, managing selection mode, level mode, and wall mode independently from editor stores:

import { useViewer } from '@pascal-app/viewer'

function Toolbar() {
  const levelMode = useViewer(s => s.levelMode)
  const setLevelMode = useViewer(s => s.setLevelMode)

  return (
    <button onClick={() => setLevelMode('stacked')}>Stacked</button>
  )
}

State is managed by the viewer’s own store and never reaches into editor stores. – packages/viewer/src/store/use-viewer.ts

Key Files Driving the Separation

Understanding the following source files reveals how the Pascal Editor maintains its strict package boundaries:

Summary

  • Pascal Editor viewer isolation is achieved by housing the 3D layer in @pascal-app/viewer, completely separate from apps/editor.
  • The viewer consumes data from @pascal-app/core via peer dependencies, ensuring the editor provides the scene store without the viewer bundling its own copy.
  • An explicit import ban in AGENTS.md prevents the viewer from referencing apps/editor, guaranteeing zero coupling.
  • The public API in packages/viewer/src/index.ts exposes only props and callbacks (onSelect, onExport), making the component reusable in external applications.
  • Viewer-specific state (level mode, selection) lives in packages/viewer/src/store/use-viewer.ts, while scene data remains in @pascal-app/core, maintaining clear separation of concerns.

Frequently Asked Questions

What is viewer isolation in the Pascal Editor?

Viewer isolation is an architectural pattern in the pascalorg/editor monorepo where the 3D rendering package (@pascal-app/viewer) operates as a completely independent module that cannot import code from the editing application (apps/editor). This separation allows the viewer to function as a standalone, reusable component for read-only previews or external embedding without dragging in editor-specific UI logic or dependencies.

How does the viewer access scene data without importing editor code?

The viewer consumes scene data through peer dependencies and props. It declares @pascal-app/core as a peer dependency in packages/viewer/package.json, requiring the consuming application to provide the Zustand store (useScene). The Viewer component accepts scene data and interaction callbacks (onSelect, onExport) via its props interface defined in packages/viewer/src/index.ts, maintaining a strict boundary where the viewer renders what it is given without knowing the editor exists.

Can the Pascal Editor viewer be used in external applications?

Yes. Because the viewer package enforces zero coupling to the editor through its import ban and exposes only a props-based API, external React applications can install @pascal-app/viewer and embed the <Viewer /> component by providing their own scene data from @pascal-app/core or mock data. The package’s README documents standalone usage patterns, and its peer dependency architecture ensures it does not force specific versions of the core data model on consumers.

Where is the viewer isolation rule documented?

The isolation rule is explicitly documented in AGENTS.md at the repository root, specifically lines 44–45 and 78–79, which state that the viewer must accept external props and callbacks to expose control points and must not import anything from apps/editor. This architectural guide serves as the enforceable contract that prevents developers from accidentally coupling the rendering layer to editing logic during feature development.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →