CharDesk Docs

Editor Runtime

UI-independent coordination of editor commands, tools, state, and extensions.

HEAD a63778e
Read this page whenYou change editor command execution, host isolation, tools, or extension coordination.
First answerEditorRuntime coordinates execution while Canvas, Slides, Sessions, and Collaboration retain state ownership.
BoundaryRuntime does not own Canvas data, domain validation, or persistence.
Follow-upTrace the public runtime factory and verify the nearest runtime and viewport tests.

EditorRuntime coordinates execution; Canvas, slides, sessions, and collaboration retain their state and business ownership.

createApplicationEditorHost() creates isolated editor, canvas, Yjs registry, and collaboration runtimes. The composition root owns the default host; widgets resolve it through explicit providers.

Production widgets receive canvas, editor, and collaboration runtimes through providers; fallback injection is test-only. Additional hosts disable persistence or use a distinct non-empty storage key.

CanvasWorkspaceRuntime owns one frame scheduler and a registry of canvas view runtimes. Each view binds one session and owns its live and committed camera, size, and imperative renderer managers. CanvasViewportRuntime is the command boundary: a mounted workspace binds its active view, while a headless host uses its single-view fallback. Only committed active-view cameras update the session's saved viewport. Only the active view projects its session into the shared editor and owns managed input; inactive views render their bound session with an empty interaction snapshot. Pane resize preserves the camera's world center. Manager and frame keys are scoped by view ID. Canvas interaction ports are also registered by view ID; capture-phase activation selects the port before the workspace switches session, so the first gesture is routed through the same pane that received it.

Continuous viewport input uses the camera interaction queue. Pan, wheel, and pinch publish at most one transient viewport per scheduled frame; pinch keeps the latest absolute target rather than notifying the live viewport for every touch sample. CanvasRenderActivity marks the same viewport activity window for the renderer. Phone content uses coarse backing DPR during that window and returns to the zoom-based full-resolution policy after settling. The queue and resolution contracts are covered by CanvasCameraManager.dom.test.ts and canvasContentResolution.test.ts.

Canvas interaction is one atomic snapshot scoped by the active Yjs document and page address. Session and page activation reset it; metadata-only changes retain it. The internal Zustand state owns this snapshot without flat field mirrors. CanvasViewportRuntime is the sole active-camera authority; saved descriptor viewports are checkpoints and never live Zustand projections.

Entry direction

Web DOM ─ HTML Host ─┐
           ├→ editor-input / @opentui/keymap → command / tool
TUI bytes ─ OpenTUI ─┘                 │
                            ↓
KeyInput → managed Canvas rules → Intent → Canvas port → CanvasCommands
beforeinput / composition / clipboard → native adapter ────┘

  • Commands own discrete semantic operations; tools own continuous interaction through one hierarchical state tree for freeform and structured drag state.
  • @chardesk/keyboard owns DOM-free keyboard facts. Its browser entry performs the only native-event conversion.
  • Host adapters own focus classification and native event effects. The input integration boundary routes binding contributions through the shared keymap engine.
  • Managed Canvas key rules are pure KeyInput + context -> Intent; the hook executes Intents through its model and editor command ports.
  • Native clipboard events invoke the same commands through adapters.
  • Widgets use provider-bound selectors and commands, never private stores or Yjs maps.

CanvasCommands owns workspace and interaction mutations. Zustand exposes the read projection; reusable interaction rules are pure transitions applied by the command boundary. Static-grid focus, range navigation, edit-mode changes, and selection clearing follow this path. Drawing previews are pure transitions over the interaction-owned scratch layer. Scratch commits, existing-cell area fills, and static-range moves are addressed Yjs document commands owned behind CanvasCommands; they are not Zustand actions. Remaining content commands follow the same document boundary as they migrate. Selection mutations use that document boundary directly. Clipboard adapters receive a read-only Canvas snapshot and an explicit mutation port, so asynchronous I/O cannot invoke Zustand actions.

Extensions

Extensions register before EditorRuntime.start(). Extension, command, tool, and state-scope IDs have one owner; disposal unwinds lifecycle registrations in reverse order.

Device-local key overrides load after registration and before start, persist under chardesk-editor-keymap-v3, and never enter document state, collaboration, or undo history. App chrome, menu navigation, presentation controls, IME, native clipboard events, and hold gestures remain Host adapters.

The contract is application-public without an npm compatibility commitment.

Composition is authoritative in apps/canvas/src/app/compositionRoot.ts; runtime lifecycle is authoritative in apps/canvas/src/domains/editor/core/runtime.ts. Use their nearest tests for isolation, ownership, and disposal evidence.