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

> Learn how Pascal Editor achieves viewer isolation with package separation for reusable 3D components. Discover independent 3D presentation via props and callbacks.

- Repository: [Pascal/editor](https://github.com/pascalorg/editor)
- Tags: internals
- Published: 2026-03-25

---

**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`](https://github.com/pascalorg/editor/blob/main/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`](https://github.com/pascalorg/editor/blob/main/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`](https://github.com/pascalorg/editor/blob/main/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`](https://github.com/pascalorg/editor/blob/main/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`](https://github.com/pascalorg/editor/blob/main/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`](https://github.com/pascalorg/editor/blob/main/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`](https://github.com/pascalorg/editor/blob/main/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`](https://github.com/pascalorg/editor/blob/main/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:

```tsx
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`](https://github.com/pascalorg/editor/blob/main/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:

```tsx
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`](https://github.com/pascalorg/editor/blob/main/packages/viewer/README.md) lines 41–52

### Asset CDN Utilities

Viewer-only utilities like `resolveCdnUrl` live in [`packages/viewer/src/lib/asset-url.ts`](https://github.com/pascalorg/editor/blob/main/packages/viewer/src/lib/asset-url.ts) and handle 3D asset loading without editor logic:

```tsx
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`](https://github.com/pascalorg/editor/blob/main/packages/viewer/src/store/use-viewer.ts), managing selection mode, level mode, and wall mode independently from editor stores:

```tsx
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`](https://github.com/pascalorg/editor/blob/main/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:

- **[`packages/viewer/src/index.ts`](https://github.com/pascalorg/editor/blob/main/packages/viewer/src/index.ts)** – The public API entry point that enforces the isolation surface by explicitly re-exporting only `Viewer`, `useViewer`, and asset helpers.
- **[`packages/viewer/src/components/viewer/index.tsx`](https://github.com/pascalorg/editor/blob/main/packages/viewer/src/components/viewer/index.tsx)** – The root `<Viewer>` component implementation that composes the React-Three-Fiber canvas without importing editor UI components.
- **[`packages/viewer/src/store/use-viewer.ts`](https://github.com/pascalorg/editor/blob/main/packages/viewer/src/store/use-viewer.ts)** – The viewer-specific Zustand store managing local rendering state (selection, level mode) separate from the core scene data in `@pascal-app/core`.
- **[`packages/core/README.md`](https://github.com/pascalorg/editor/blob/main/packages/core/README.md)** – Documents the core data model that the viewer consumes as a peer dependency.
- **[`AGENTS.md`](https://github.com/pascalorg/editor/blob/main/AGENTS.md)** – The architectural guide that codifies the “viewer isolation” rule and import bans.
- **[`packages/viewer/README.md`](https://github.com/pascalorg/editor/blob/main/packages/viewer/README.md)** – Consumer-facing documentation demonstrating standalone usage patterns.

## 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`](https://github.com/pascalorg/editor/blob/main/AGENTS.md) prevents the viewer from referencing `apps/editor`, guaranteeing zero coupling.
- The public API in [`packages/viewer/src/index.ts`](https://github.com/pascalorg/editor/blob/main/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`](https://github.com/pascalorg/editor/blob/main/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`](https://github.com/pascalorg/editor/blob/main/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`](https://github.com/pascalorg/editor/blob/main/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`](https://github.com/pascalorg/editor/blob/main/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.