Skip to content

Document Model

Jeremiah edited this page Oct 22, 2026 · 1 revision

Document Model

This page describes native documents, editable objects, validated patches, undo history and migration behavior.

packages/chem-core owns ChemDraftDocument, the in-memory source of truth. Its schemas use Zod, a runtime validation library: a TypeScript type helps during development, while a schema checks data arriving from a file, plugin or other untrusted source. CDXML, molecule-editor state and engine formats remain boundary representations.

Document and page structure

The schema identifier is chemdraft.document.v1. A document contains its ID, title, creation and update timestamps, at least one page, selection, style metadata, plugin metadata and compatibility warnings.

Each page has an ID, dimensions, margins, a layout, an ordered object array and crossing overrides. The schema checks that the page dimensions match layout.widthPx and layout.heightPx. Layout carries paper and orientation information; coordinate conversions belong to shared page/viewport helpers rather than ad hoc UI arithmetic.

The document selection records object IDs and an optional page ID. Desktop editing can also keep detailed atom/bond fragment selections outside this coarse object selection. Do not assume an empty document.selection.objectIds means the user has selected no chemical content.

File paths, dirty flags, viewports and tab routing are desktop concerns. Several document tabs can share one window while retaining separate histories and file associations. Those tabs are not additional pages inside one document.

Editable object families

The discriminated object union in packages/chem-core/src/schemas.ts includes:

Types Purpose
molecule, reaction Native atom/bond graphs and reaction components/roles
reaction-arrow Semantic/compatibility arrow representation retained in the model
mechanism-arrow, electron-mark Editable mechanism annotations and electron/charge marks
text, bracket, graphic, plus Page captions, brackets, art and plus signs
group, annotation Object relationships and annotation data
superatom, r-group-label, generic-atom-label Label and compatibility representations
unknown-compatibility-object Preserved imported content outside the supported subset

A molecule has stable atom and bond IDs. Atoms carry positions and formal charge, with optional label and hydrogen-reading information. Bonds reference endpoint IDs and have single, double, triple, aromatic or unknown order. Display details such as a double-bond side or depth weight are separate from bond order.

Labels can encode actual groups. labelBondSide records the attachment side used to read a direction-sensitive label; rotating the drawing must not reinterpret it. Shared label expansion appends real group atoms after drawn atoms so drawn indices remain stable. Do not replace that reading with a string-only formula shortcut.

Tool-drawn reaction arrows currently use art graphics with compatibility tags. The existence of reaction-arrow in the schema does not mean every arrow tool creates that type. Mechanism arrows remain editable native objects rather than opaque SVG.

Patches and transactions

A patch describes a specific operation on a document. The core union includes addObject, removeObject, updateObject, moveObject, page-layout changes, object ordering, crossing overrides, selection changes and annotation insertion/removal.

For example, a workflow that already has a validated object can insert and select it together:

const next = applyPatches(document, [
  { op: "addObject", pageId, object },
  { op: "setSelection", pageId, objectIds: [object.id] }
]);

applyPatch applies one operation; applyPatches applies an ordered atomic batch. Each patch sees preceding results, but a failure throws without applying a partial result to the input. Changed objects and document/page shells are validated.

Results use structural sharing: unchanged objects are reused between snapshots. Never mutate a returned object in place, because an undo snapshot may share it. Tests freeze results to catch this mistake.

Plugins have a narrower direct-write contract than the core patch union. documents.applyPatch accepts insertion operations only, requires document.write, and works only inside that plugin's own active command. Other edits require proposal review; image recognition always requires review.

Undo, serialization and migrations

Core DocumentHistory stores past, present and future snapshots. Applying a patch with history clears the redo branch. Desktop workflows group one user action into one labelled undo entry. The desktop also bounds retained history by count and estimated retained weight, dropping oldest entries for large drawings; it is not an unlimited archive.

serializeDocument produces JSON; deserializeDocument parses and calls migrateDocument. Current migration code in document.ts handles older page layouts and legacy graphic fields, including conversion of arc degrees to signed radians. It also removes unsupported legacy graphic metadata keys. This is a specific migration path, not permission to discard unknown chemistry.

Native .chemdraft saves wrap validated native JSON in a CDXML envelope with hashes. That file envelope belongs to cdx-compat; it does not change the in-memory model. Unsupported or invalid documents fail with details, including a newer-version hint for unrecognized fields. Never assume a future document will open safely in an older build.

See also

Clone this wiki locally