# Text and lossless round trips

Mermaid-compatible text is the human-readable body of a diagram; a Grafloria sidecar carries the document data that Mermaid cannot express, so text and the live canvas can reconcile in both directions.

## How the parts fit together

```mermaid
flowchart LR
  S["Mermaid-compatible body"] --> I["importDiagramText()"]
  C["Grafloria sidecar"] --> I
  I --> M["DiagramModel"]
  M --> R["render()"]
  R --> D["live DiagramInstance"]
  D --> E["exportText() / exportDiagramText()"]
  E --> S
  E --> C
```

The body contains structure and labels that other Mermaid renderers can read. The `%%grafloria:document` comment contains the serialized document, and `%%grafloria:body-hash` records which body produced it. Mermaid ignores both comments.

With the default lossless export, importing an unchanged file uses the sidecar. Positions, sizes, styles, ports, groups, and viewport data therefore survive the round trip. Transient selection state and derived link routing are not committed to the sidecar.

## Import Mermaid text

Use [`importDiagramText`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-engine-serialization#importdiagramtext) when text is the input. It returns a [`DiagramModel`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-engine-models-diagrammodel#diagrammodel), the source that produced it, and flags that describe reconciliation.

```ts title="import-mermaid.ts"
import { importDiagramText } from '@grafloria/engine';

const sourceText = `flowchart
start[Start] --> finish[Finish]`;
const result = importDiagramText(sourceText);

if (result.unsupported) {
  console.error(`Unsupported Mermaid type: ${result.unsupported}`);
} else {
  console.log(result.source);
  console.log(result.diagram.getNodes().length);
}
```

Pure Mermaid text takes the DSL path. It is best effort because Mermaid syntax does not contain every model property. Imported nodes still become typed model nodes, so the rendered diagram uses the diagram type's semantics.

Do not guess an unsupported type or render it as another type. Inspect `result.unsupported`; Grafloria reports the recognised but unsupported diagram type and returns an empty diagram rather than a plausible wrong graph.

## Export a diagram for humans and machines

Use [`exportDiagramText`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-engine-serialization#exportdiagramtext) with a model when you need a text file outside a mounted renderer. The [`DiagramSerializer`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-engine-serialization#diagramserializer) in this example compares the document before and after the text round trip. The default is lossless:

```ts title="export-mermaid.ts"
import { DiagramSerializer, exportDiagramText, importDiagramText } from '@grafloria/engine';
import { render } from '@grafloria/element';

const host = document.createElement('div');
host.style.height = '400px';
document.body.append(host);

const instance = render({
  nodes: [
    { id: 'plan', position: { x: 60, y: 90 }, size: { width: 150, height: 66 }, data: { label: 'Plan' } },
    { id: 'ship', position: { x: 320, y: 90 }, size: { width: 150, height: 66 }, data: { label: 'Ship' } },
  ],
  edges: [{ id: 'plan-to-ship', source: 'plan', target: 'ship' }],
}, host);

const before = new DiagramSerializer().serialize(instance.getModel());
const text = exportDiagramText(instance.getModel());
const imported = importDiagramText(text);
const after = new DiagramSerializer().serialize(imported.diagram);

console.assert(imported.source === 'sidecar');
console.assert(JSON.stringify(before) === JSON.stringify(after));
```

The returned text remains valid Mermaid for external viewers. Pass `{ lossless: false }` when you need only the portable Mermaid body; that crosses the lossy boundary, so Grafloria-specific geometry and styling are not preserved by a later pure-text import. Pass `{ positions: true }` when exact positions should also be written as readable Grafloria directives in the body; the sidecar remains the lossless source.

## Reconcile text with a mounted instance

The [`render`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-element-core#render) function mounts a real canvas and returns a [`DiagramInstance`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-renderer-instance-diagraminstance#diagraminstance). Use the instance's `exportText()` and `loadText()` methods when an editor has both a text area and a canvas. `loadText()` reconciles into the existing diagram, so listeners, plugins, and selection remain attached.

```ts title="mermaid-editor.ts"
import { render } from '@grafloria/element';

const host = document.createElement('div');
host.style.height = '400px';
const editor = document.createElement('textarea');
editor.style.width = '100%';
editor.style.height = '180px';
document.body.append(host, editor);

const instance = render({
  nodes: [
    { id: 'plan', position: { x: 60, y: 90 }, size: { width: 150, height: 66 }, data: { label: 'Plan' } },
    { id: 'build', position: { x: 300, y: 90 }, size: { width: 150, height: 66 }, data: { label: 'Build' } },
  ],
  edges: [{ id: 'plan-to-build', source: 'plan', target: 'build' }],
}, host);

editor.value = instance.exportText();
editor.addEventListener('change', () => {
  const result = instance.loadText(editor.value);
  if (result.unsupported) {
    console.error(`Unsupported Mermaid type: ${result.unsupported}`);
  }
});
```

When the body is unchanged, the sidecar wins. When the body hash differs, `auto` treats the body as a human edit and applies its structure, labels, and shapes. If a sidecar exists, the edit is merged over the sidecar: geometry, styles, ports, groups, and viewport data that the body cannot express stay intact. The result reports `bodyEdited: true`, `source: 'text'`, and `sidecarMerged: true`.

Choose the source explicitly with [`ImportTextOptions`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-engine-serialization#importtextoptions): `prefer: 'sidecar'` ignores body edits, while `prefer: 'text'` ignores the sidecar. The default `prefer: 'auto'` uses the body hash to choose. A malformed sidecar does not discard the body; `sidecarInvalid` reports the problem and the text path remains available.

## Choose the representation

| Need | Use | Result |
| --- | --- | --- |
| Save and restore the full document | `DiagramSerializer` or the lossless text form | Model data survives through the document representation. |
| Give a person or Mermaid renderer readable text | `exportDiagramText(model, { lossless: false })` | Pure Mermaid-compatible body; Grafloria-only data is lossy. |
| Keep a text editor and canvas synchronized | `instance.exportText()` and `instance.loadText(text)` | Text changes reconcile into the mounted instance. |
| Detect an unsupported Mermaid type | `importDiagramText(text).unsupported` | The type name is explicit; no wrong diagram is guessed. |

For a mounted editor, start at the instance. Use the model-level functions when importing before mounting, exporting a model for storage, or processing text without a renderer.

## See it running

[Open the live Mermaid round-trip demo](../../demos/misc/mermaid-text.html). It shows the Mermaid body beside the canvas: leave the sidecar unchanged to restore the exact document, or edit the body to see the text reconciliation path.

Related: [Edit Mermaid diagrams](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/edit-mermaid-diagrams), [Model and documents](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/model-and-documents), and [Export diagrams](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/export-diagrams).
