CharDesk Docs

State and Data Flows

Authority, projection, history, persistence, and synchronization direction.

HEAD a63778e
ScopeAuthorityHistorySync
documentOrdered CellPlane pages in one Yjs documentPage-local local originsWhen collaborative
workspaceTool, local interaction, active viewport, and preferencesNoNo
sessionCanvas identity, presentation mode, saved viewport, and optional external-source bindingNoNo
presenceRemote identity, cursor, transient selectionNoEphemeral
derivedRendering, minimap, preview projectionsNoNo
blackboardVersioned source files in the browser repository or local ReaderRepository revisionsNo

Workspace writes enter through CanvasCommands; EditorState contains data only and Zustand is its read projection, not an alternate command surface. CanvasRuntime.store exposes only getState, getInitialState, and subscribe; its mutable Store is test-internal. Local grid navigation and selection clearing are pure interaction transitions. Drawing previews update the transient scratch layer through the same boundary. Scratch commits, existing-cell area fills, and static range moves are addressed document writes owned by CanvasCommands, not alternate Zustand actions. Selection delete, erase, fill, and style commands use the same owner. Clipboard I/O reads a snapshot, validates its addressed selection after the await boundary, then commits through an explicit mutation port.

interaction.staticGrid is the sole static-grid interaction authority. Its navigate branch owns selection; its text-edit branch owns the input session. Selection and cursor views are derived from that discriminated state, so they cannot represent conflicting modes.

Text mutation enters through the Canvas text command executor. It derives CellPlane writes and the next interaction.staticGrid from one initial snapshot; text actions are not part of EditorState.

Session and Slide lifecycle mutations enter through their Canvas command executors. One activation coordinator orders create, source-open, import, switch, active deletion, and collaboration join. Asynchronous work runs outside the commit boundary, then validates the latest activation intent and publishes a short synchronous commit; no draft spans an await boundary. A stale import still registers its document and descriptor but does not replace the user's newer active Canvas.

CanvasStateCommitCoordinator owns the synchronous command publication boundary. Yjs content projection, local interaction, and history availability update one shared draft; nested commands read that draft, and only the outer command publishes to Zustand subscribers. Remote Yjs transactions outside a local command publish immediately. This boundary provides one observable snapshot, not Yjs rollback.

A projection never writes back to its authority. Only local Yjs origins enter undo history. Remote values pass Canvas decoders before projection; invalid records stay out of editor state and surface as collaboration integrity issues. Every write resolves a CanvasDocumentAddress (documentId, pageId) before opening its transaction. Active-document convenience reads are projections only; commands do not use Registry focus as an implicit write target.

Canonical flows

  • Freeform content: command -> compact CellPlanePatch -> one ordered CellPlane operation in a Yjs transaction -> 128x64 spatial projection -> viewport query. Text runs remain compact through the write boundary; target-background inheritance is operation composition, not a per-cell read/modify/write pass.
  • Session activation: active ViewRuntime checkpoint -> catalog descriptor -> restored mode-authoritative Yjs document -> one editor-state activation patch. Content projection is already current; interaction resets against the newly active document/page address.
  • Slide activation: deck metadata -> matching page in the session Yjs document -> active projection.
  • Slide edit: command -> active Yjs page -> derived active grid.
  • Local restore: IndexedDB catalog -> y-indexeddb document restore -> canvas recovery adapter -> active projection. Startup does not expose editing until the active document is ready.
  • Canvas switch: retain the current frame -> load the target Yjs document -> commit the latest activation request -> release an unpinned least-recently-used document when over budget.
  • Import: document parser -> CanvasImportSnapshot -> canvas session command -> one-shot active-pane camera placement. The placement uses projected content bounds and viewport insets at runtime; it is not part of the document protocol or persistence.
  • Session persistence: metadata projection -> sessions catalog transaction. Canvas content remains in its Yjs document and persists as incremental y-indexeddb updates. A flush visits dirty documents only; routine edits never force workspace-wide snapshots.
  • Viewport: pointer, keyboard, minimap, or zoom command -> active-view port -> live camera -> settled camera -> session saved viewport -> catalog persistence. CanvasViewportRuntime is the only active-camera authority; Zustand does not mirror its offset or zoom. Animation frames never write session metadata.
  • Slide preview: deck metadata + page reader -> explicit grid snapshot -> canonical slide renderer.
  • Selection command: DOM dispatcher -> editor keymap -> command registry -> canvas selection port -> Yjs document.
  • Artifact appearance: stored Cell style -> canonical default-foreground classification -> active Canvas palette for visual projections or default SGR state for ANSI serialization. A stored background makes its foreground explicit; persistence retains literal colors.
  • Block layout text: |||-recognized layout -> per-field normal text renderer -> composed CellPlane rows. A lone --- remains a Markdown thematic break in auto mode.
  • Source-backed Blackboard: source-tree transaction -> shared compiler -> derived Freeform surface or Slide pages. Invalid source remains stored while the last valid surface stays mounted. Human Canvas commands never write back to source files. sourceBinding owns authority; freeform and slide continue to describe presentation only.
  • Blackboard directory import: selected source tree -> shared compiler -> canonical .chardesk envelope -> ordinary editable Freeform or Slide session. This is a detached snapshot: it retains rendered content and Slide page order, not source paths, drafts, or the manifest topology.
  • Blackboard site tool: active page workspace -> protocol-neutral namespaced command -> source repository. Explicit workspace IDs remain a compatibility override. Creation and opening bind the page URL, Canvas session, and IndexedDB workspace before later file calls omit identity. Each hosted top-level document owns its selection; local CLI reader pages expose materials but not browser-persistent CRUD. Workspace reads expose the blackboard.yaml source graph; mutations report whether the compiled Canvas projection updated, stayed unchanged, or became invalid. A global read-only tool embeds the canonical skill materials at build time so web-only agents can load the same authoring context. The document connector selects one host adapter, preferring the complete WebMCP surface over the imperative subset used by ChatGPT Site Tools. Both consume the same protocol-neutral tool definitions; the subset is a capability profile, not a vendor protocol. Bootstrap registration keeps an adaptive handshake for runtimes injected after page load; an unavailable workspace returns a stable result. Standard WebMCP requires an origin-isolated document plus either Chrome's local testing flag or an origin-bound trial token supplied as VITE_WEBMCP_ORIGIN_TRIAL_TOKEN. ?webmcp=polyfill and VITE_WEBMCP_DEV_POLYFILL=1 enable the explicit development-only protocol fallback. ChatGPT discovers only imperative tools registered by JavaScript in the top-level page; declarative and iframe tools are out of scope. Inspect data-webmcp-provider, data-webmcp-capability, and data-webmcp-status when a host cannot discover the tools.

The application module and Canvas restore load independently. App chrome mounts as soon as the module is available, while CanvasPersistenceStatus.restore gates the workspace, onboarding, collaboration, and editor surfaces. Restore failure opens an editable in-memory workspace instead of an empty editor. Retry prepares persisted documents off-screen, then atomically activates them; temporary edits are retained in a distinct recovered session. The mounted workspace becomes inert during that commit and is never replaced by a second loading screen.

Every editable local session persists one Yjs document. A Freeform document has one page; a Slide document has one page per slide. The runtime session list contains CanvasSessionDescriptor values only. The Canvas document owns page order, names, sizes, and content; the IndexedDB catalog stores the restore metadata needed before a document is resident. Collaborative sessions persist only their catalog shell and descriptor; room content is restored through collaboration persistence and never copied into local session persistence. The catalog records whether a collaborating session is the local host or a guest. A guest with no cached room content remains in joining and cannot edit until encrypted transport sync and a document-ready peer handshake both arrive. Collaboration V7 encrypts Yjs sync and awareness in the browser. The memory-only relay sees room metadata and ciphertext, owns no Yjs document, and deletes a room when its last connection leaves. Managed links resolve the build-configured endpoint; an optional endpoint exists only for protocol-level self-hosting. V6 remains a legacy y-websocket link format. Collaboration derives one shared document and main-page identity from the room id; local session ids remain catalog-only and never name shared Yjs pages. Starting or joining a room rehomes the single local page to that shared identity before transport connects. Source-backed sessions persist their binding and presentation metadata beside the source repository. Their runtime Yjs shells are empty and non-persistent; compiled surfaces have no undo or synchronization history. Closing a session closes only the view, never its source.

The V5 localStorage envelope is a migration input only. Migration seeds and verifies all Yjs documents, commits the catalog, then removes the old key. A failure keeps the old value for retry. Restore probes existing Yjs channels before seeding. Intermediate page descriptors are normalized into a clean document generation. Documents with excessive Yjs struct history use the same generation rotation: write and verify the next generation, commit its catalog pointer, and reclaim older generations after a later verified startup. The active generation is always catalog-owned. If a bootstrap catalog replaced legacy metadata, restore reattaches sessions only when their local Yjs document databases still exist. Historical descriptors restore names and mode; an orphan without metadata receives a recovered shell. Deleted sessions are not resurrected. Canvas content never falls back to localStorage. Every browser tab remains editable. Loaded local Yjs documents exchange updates over a same-origin channel and persist them independently; an exclusive coordinator owns checkpoint rotation, while catalog writes use short exclusive sections and merge changes against the latest snapshot. Undo history remains runtime-only per tab.

The catalog is the startup index; it does not make every Canvas resident. The active Canvas and both visible Split panes are pinned. The document repository retains at most four Canvas documents, using most-recently-used order for the remaining slots. Releasing a document flushes dirty local updates and disposes its runtime projections without deleting IndexedDB content. User deletion is a separate lifecycle operation: a durable catalog intent records its tombstone before the Session disappears from runtime state, then runtime resources are released. Recovery databases remain available but tombstoned Sessions are never restored. Reopening an evicted Canvas restores content but not its undo stack.

Canvas pages contain only ordered cell-plane-operations. Canvas rendering queries occupied spans inside the viewport, and Minimap caches its reader projection by document revision; camera movement never materializes a grid snapshot. A legacy local main-grid is converted once into a bootstrap operation and then cleared. CanvasSurfaceReader is the read contract for rendering, interaction, selection, and export. CanvasState.contentSurface carries the active Reader plus a projection revision; it never carries a live GridMap. Freeform and Slide read their CellPlane indexes directly. A GridMap is created only at an explicit snapshot boundary such as serialization, clipboard payload construction, or a transient drag preview. Chunk indexes and projections are never synchronized.

Freeform and Slide session descriptors contain no content at runtime. Imports use CanvasImportSnapshot; persistence uses CanvasSessionSnapshot, while Slide export and playback use SlideDeckSnapshot. Switching sessions restores content from the matching Yjs document; the projector never writes content back into the descriptor. Retired Structured session, checkpoint, and .chardesk inputs are decoded only at migration boundaries and flattened into CellPlane content; old Structured collaboration links are rejected as retired. Slide runtime state uses SlideDeckDescriptor; preview, playback, resize, duplicate, persistence, and export materialize page content only at their explicit boundary.

On this page