isostate Project page

Editor Reference

The editor is a browser visual editor for .isostate.yaml scene documents. It is implemented as its own workspace package, @sebastianwessel/isostate-editor, but this version does not plan to publish that editor package to npm. Treat the API below as the website/editor integration contract unless a future release explicitly publishes it.

Package Exports

ExportUseDescription
@sebastianwessel/isostate-editorNon-React hosts, generic embeddingmountEditor, IsostateEditor, commands, serialization, asset helpers
@sebastianwessel/isostate-editor/reactReact hostsIsostateEditor component and props only
@sebastianwessel/isostate-editor/style.cssAll hostsEditor stylesheet (import once)

Peer dependencies: react >=19 and react-dom >=19.

No Tailwind CSS or shadcn project setup is required.

mountEditor

import { mountEditor } from '@sebastianwessel/isostate-editor';

const editor = mountEditor(target, {
  initialYaml?: string;
  initialWorkspace?: EditorWorkspaceInput;
  assetManifestUrl?: string;
  assetManifestUrls?: string[];
  assetProvider?: EditorAssetProvider;
  theme?: 'light' | 'dark' | 'system';
  readonly?: boolean;
  onChange?: (event: EditorChangeEvent) => void;
  onValidate?: (diagnostics: EditorDiagnostic[]) => void;
  onExport?: (artifact: EditorExportArtifact) => void;
});

mountEditor creates a React root inside target, renders the editor, and returns a MountedEditor handle.

initialYaml and initialWorkspace are mutually exclusive. When neither is provided, the editor starts with a minimal valid scene document.

MountedEditor

interface MountedEditor {
  element: HTMLElement;
  getWorkspace(): EditorWorkspace;
  setYaml(sourceYaml: string): void;
  setTheme(theme: 'light' | 'dark' | 'system'): void;
  validate(): EditorDiagnostic[];
  formatYaml(): boolean;
  exportYaml(): string;
  exportRuntimeBundle(format: 'js' | 'json'): string;
  destroy(): void;
}
  • destroy() unmounts React, removes listeners, and leaves target empty.
  • exportRuntimeBundle() validates and compiles the current YAML before returning canonical JS or JSON. It throws structured editor errors when the document is invalid.

IsostateEditor (React)

import { IsostateEditor } from '@sebastianwessel/isostate-editor/react';
// or from '@sebastianwessel/isostate-editor'
interface IsostateEditorProps {
  value?: string;
  defaultValue?: string;
  assetManifestUrl?: string;
  assetManifestUrls?: string[];
  assetProvider?: EditorAssetProvider;
  theme?: 'light' | 'dark' | 'system';
  readonly?: boolean;
  onChange?: (event: EditorChangeEvent) => void;
  onValidate?: (diagnostics: EditorDiagnostic[]) => void;
  onExport?: (artifact: EditorExportArtifact) => void;
}
  • value makes the component controlled.
  • defaultValue makes it uncontrolled.
  • Controlled mode emits onChange but does not mutate value.
  • The editor always shows canvas, inspector/sidebar, and YAML editor panes from left to right. The canvas pane does not scroll; inspector/sidebar and editor content scroll independently. The scene tree combines scenes, layers, and elements in one collapsible panel.
  • Use assetManifestUrl for one catalog or assetManifestUrls for separate catalogs. The asset browser merges them only in memory for browsing, preserves each manifest’s own assetBaseUrl, and shows manifest groups as collapsible alphabetized categories.
  • The editor opens on the asset tab by default so users can drag assets into the scene without first switching panels.
  • Asset categories are expanded by default, alphabetized, and kept separate by source manifest. The editor may rebase mixed same-origin manifest roots in YAML so SVG assets and sprite sheets can coexist under one assetBaseUrl without broken URLs.

Text Editing

When a selected element uses asset: text, the inspector exposes content, horizontal alignment, placement, font size, and fill controls.

text.placement changes how the text sits in its one-cell canvas:

PlacementUseAuthoring impact
cellstandalone labels, group labels, callouts, message captions, larger assetsGive the text its own nearby at cell so it does not overlap the icon
captionlabels attached to one-cell iconsThe text may share the icon’s at, but verify it does not collide with the icon or routes

Changing placement does not move the element. If a label overlaps after changing between cell and caption, move the text element or the related asset.

Workspace Types

EditorWorkspace

interface EditorWorkspace {
  name: string;
  sourceYaml: string;
  document?: SceneDocument;
  activeSceneId?: string;
  selection: EditorSelection;
  viewport: EditorViewport;
  editState: EditorEditState;
  uiState: EditorUiState;
  history: EditorCommandResult[];
  diagnostics: EditorDiagnostic[];
  lockedLayers?: string[];
}

EditorSelection

interface EditorSelection {
  sceneId?: string;
  objectIds: string[];
  connectionIds: string[];
  layerNames: string[];
}

EditorViewport

interface EditorViewport {
  pan: { x: number; y: number };
  zoom: number;
  showGrid: boolean;
  showFloor: boolean;
}

EditorEditState

interface EditorEditState {
  readonly: boolean;
  dragging: boolean;
  dragPayload?:
    | { kind: 'asset'; assetId: string }
    | { kind: 'move'; objectId: string };
}

EditorUiState

interface EditorUiState {
  sidebarWidth: number;
  sidebarCollapsed: boolean;
  sidebarTab: 'inspector' | 'scenes' | 'assets';
  theme: 'light' | 'dark' | 'system';
  previewMode: 'edit' | 'runtime';
  assetBrowser: EditorAssetBrowserState;
  hiddenLayers?: string[];
}

EditorWorkspaceInput

interface EditorWorkspaceInput {
  name?: string;
  sourceYaml: string;
  activeSceneId?: string;
}

Host applications cannot initialize persistent editor-only UI state through v1 workspace input.

Command API

import { applyEditorCommand } from '@sebastianwessel/isostate-editor';
function applyEditorCommand(
  workspace: EditorWorkspace,
  command: EditorCommand,
): EditorCommandResult;

applyEditorCommand is the only public helper that mutates workspace state. It returns a new workspace object and never mutates the input workspace.

Commands include:

  • createYamlEditCommand, createYamlFormatCommand
  • createSceneAddCommand, createSceneUpdateCommand, createSceneRemoveCommand, createSceneReorderCommand
  • createObjectAddCommand, createObjectUpdateCommand, createObjectRemoveCommand
  • createConnectionAddCommand, createConnectionUpdateCommand, createConnectionRemoveCommand
  • createLayerAddCommand, createLayerUpdateCommand, createLayerRemoveCommand, createLayerReorderCommand
  • createAssetAddCommand, createAssetUpdateCommand, createAssetRemoveCommand
  • createCameraUpdateCommand, createCameraRemoveCommand

Commands must not access DOM APIs.

Serialization

import {
  serializeEditorWorkspace,
  serializeSceneDocument
} from '@sebastianwessel/isostate-editor';
function serializeSceneDocument(document: SceneDocument): string;
function serializeEditorWorkspace(workspace: EditorWorkspace): string;

serializeSceneDocument() follows canonical editor serialization rules. serializeEditorWorkspace() returns the workspace source YAML when the document is unavailable, otherwise returns canonical serialized scene YAML.

Change Events

interface EditorChangeEvent {
  sourceYaml: string;
  document?: SceneDocument;
  diagnostics: EditorDiagnostic[];
  operation: EditorOperation;
}

Every semantic visual edit emits exactly one EditorChangeEvent.

Export Artifacts

interface EditorExportArtifact {
  kind: 'yaml' | 'runtime-js' | 'runtime-json';
  filename: string;
  content: string;
  diagnostics: EditorDiagnostic[];
}

Export commands never download automatically. They return artifacts to the host app or trigger onExport.

Error Codes

CodeMeaning
EDITOR_INVALID_SOURCECurrent YAML cannot be parsed or validated for the requested operation.
EDITOR_READONLYA mutation was requested while the editor is readonly.
EDITOR_DESTROYEDPublic API was called after destroy().
EDITOR_INVALID_SELECTIONCommand requires a compatible selection.
EDITOR_LOCKED_TARGETCommand targets a locked layer or object.
EDITOR_ASSET_PREVIEW_FAILEDAsset preview provider failed.
EDITOR_ASSET_MANIFEST_INVALIDAsset manifest URL returned invalid manifest data.
EDITOR_ASSET_NOT_IN_MANIFESTYAML declares an external asset not found in the active manifest.

Core parser, validator, compiler, runtime, and CLI error codes are preserved in diagnostics when those subsystems report failures.