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:
packages/viewer/src/index.ts– The public API entry point that enforces the isolation surface by explicitly re-exporting onlyViewer,useViewer, and asset helpers.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– 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– Documents the core data model that the viewer consumes as a peer dependency.AGENTS.md– The architectural guide that codifies the “viewer isolation” rule and import bans.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 fromapps/editor. - The viewer consumes data from
@pascal-app/corevia peer dependencies, ensuring the editor provides the scene store without the viewer bundling its own copy. - An explicit import ban in
AGENTS.mdprevents the viewer from referencingapps/editor, guaranteeing zero coupling. - The public API in
packages/viewer/src/index.tsexposes 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →