# Compared with Incremark

Why we redesigned Incremark, and how the new engine handles streaming Markdown.

**Incremark was our first streaming Markdown tool.** QuireKit Markdown continues the same goal:
show formatted content while an AI response is still arriving.

The redesign addresses an easy-to-miss problem: **text received later can change how an earlier
sentence should appear.** The new engine reduces repeated parsing and rendering while allowing
later definitions to update earlier text correctly.

## Watch a tree change

“Middle insert” changes the same source three times: bold text in the second paragraph,
inline code in the first, then a two-item list between paragraphs. Each edit builds on the previous result.

Play to follow each fragment into the source and along the highlighted path to an amber affected
branch, followed by blue updated nodes. Green branches retain their native objects. Pause, step,
go back, or select an insertion and phase. The view has no drag, pan or zoom.

The third edit is at a paragraph boundary. Although the final paragraph's literal text is unchanged,
its native branch is rebuilt: the affected scope depends on syntax and context, not just changed characters.
“Tail append” and “Late reference” remain available; in the latter, **bar** becomes the earlier link.
Trees and output come from the real engine. The motion is teaching choreography, not an internal execution log.

This tree animation also uses our Flowchart library: nodes, edges, moving fragments and tree replacements share one public player.

### algorithms/playback.vue

```vue
<script setup lang="ts">
import { computed, ref } from "vue";
import { QuireSegmented, QuireTypography } from "@quirekit/ui/vue";
import type { ExampleLocale } from "../shared/types";
import type { AlgorithmScenario } from "./playback/types";
import { createAlgorithmPlayback } from "./playback/plan";
import AlgorithmPlayer from "./playback/player.vue";

const props = withDefaults(defineProps<{ /** Tutorial and source language. */ locale?: ExampleLocale }>(), { locale: "zh-CN" });
const scenario = ref<AlgorithmScenario>("insert");
const plan = computed(() => createAlgorithmPlayback(props.locale, scenario.value));
const items = computed(() => [
  { value: "insert", label: props.locale === "en" ? "Middle insert" : "中间插入" },
  { value: "append", label: props.locale === "en" ? "Tail append" : "末尾追加" },
  { value: "reference", label: props.locale === "en" ? "Late reference" : "后置引用" },
]);
/** @param value - Public segmented selection. Starts a fresh recorded run for a documented operation. */
function select(value: string | null | undefined): void { if (value === "insert" || value === "append" || value === "reference") scenario.value = value; }
</script>
<template>
  <div class="algorithm-demo" data-algorithm-demo>
    <div class="algorithm-demo-heading">
      <QuireSegmented :model-value="scenario" :items="items" size="sm" :label="locale === 'en' ? 'Source operation' : '源码操作'" @update:model-value="select" />
      <QuireTypography variant="helper">{{ locale === 'en' ? 'Actual source · actual Lezer trees' : '真实原文 · 真实 Lezer 树' }}</QuireTypography>
    </div>
    <AlgorithmPlayer :key="`${locale}-${scenario}`" :plan="plan" :locale="locale" />
  </div>
</template>
<style scoped>
.algorithm-demo { display: grid; grid-template-columns: minmax(0, 1fr); min-width: 0; gap: 16px; padding: 20px; }
.algorithm-demo-heading { display: flex; align-items: center; flex-wrap: wrap; min-width: 0; gap: 12px; }
.algorithm-demo-heading > :first-child { max-width: 100%; overflow-x: auto; }
.algorithm-demo-heading p { color: var(--qk-color-text-tertiary); }
@media (max-width: 640px) { .algorithm-demo { padding: 12px; } }
</style>

```

### shared/types.ts

```ts
/** Example host language; independent of the documentation router. */
export type ExampleLocale = "zh-CN" | "en";

```

### algorithms/playback/types.ts

```ts
import type { syntaxTree } from "@quirekit/cm-language";
import type { MarkdownRenderWork, MarkdownStream } from "@quirekit/incremark-core";
import type { VNodeChild } from "vue";

/** Native parser identities, independent of a framework's rendered nodes. */
export type NativeTree = ReturnType<typeof syntaxTree>;
/** Actual syntax-node cursor view, including buffered inline nodes. */
export type NativeNode = NativeTree["topNode"];
/** Editable source cases with distinct syntax and dependency effects. */
export type AlgorithmScenario = "insert" | "append" | "reference";

/** Exact source transaction used by this example. */
export interface AlgorithmEdit {
  /** Insertion position in UTF-16 source units. */ readonly from: number;
  /** Unmodified source fragment, including Markdown delimiters. */ readonly insert: string;
}
/** A real syntax node, rather than an inferred semantic text node. */
export interface AlgorithmNode {
  /** Local display key; top-level keys retain native Tree identity across snapshots. */ readonly id: string;
  /** Native Lezer node type. */ readonly name: string;
  /** Inclusive absolute source start. */ readonly from: number;
  /** Exclusive absolute source end. */ readonly to: number;
  /** Exact source slice covered by the node. */ readonly text: string;
  /** Native children in document order. */ readonly children: readonly AlgorithmNode[];
}
/** One top-level syntax branch in its document slot. */
export interface AlgorithmBlock extends AlgorithmNode {
  /** Source-order position used to keep unrelated branches stationary during playback. */ readonly slot: number;
}
/** One actual, accepted parser and Vue projection result. */
export interface AlgorithmSnapshot {
  /** Exact Markdown, without synthetic repair. */ readonly source: string;
  /** Immutable source state with completed native syntax. */ readonly state: MarkdownStream["state"];
  /** Native root's identity key; a new document root can contain retained branches. */ readonly rootId: string;
  /** Top-level nodes extracted from the actual Lezer tree. */ readonly blocks: readonly AlgorithmBlock[];
  /** Projection used only by semantic tests, never remounted as historical VNodes. */ readonly output: VNodeChild;
  /** Detached projection counters, excluding parsing and painting. */ readonly work: Readonly<MarkdownRenderWork>;
}
/** One real source edit in a continuous parser and reader lifetime. */
export interface AlgorithmOperation {
  /** Source-order ordinal within the recorded run. */ readonly index: number;
  /** Short human name of this insertion. */ readonly title: string;
  /** Literal source mutation. */ readonly edit: AlgorithmEdit;
  /** Before and after states, sharing the same parser and projection lifetime. */ readonly snapshots: readonly [AlgorithmSnapshot, AlgorithmSnapshot];
  /** Native old branches actually replaced by this operation. */ readonly affected: readonly AlgorithmBlock[];
  /** New or replaced branches in the accepted result. */ readonly changed: readonly AlgorithmBlock[];
  /** Actual shared top-level Tree objects, including objects whose offsets moved. */ readonly reusedTrees: number;
}
/** A teaching boundary whose data is selected without rerunning the edit. */
export type AlgorithmPhase = "source" | "parse" | "chunk" | "insert" | "locate" | "commit" | "render";
/** One bounded interval in the authored slow-motion sequence. */
export interface AlgorithmStep {
  /** Source operation shown at this boundary. */ readonly operation: number;
  /** Data boundary, rather than a fake graph-processing node. */ readonly phase: AlgorithmPhase;
  /** Authored viewing duration in milliseconds, not parser execution time. */ readonly duration: number;
}
/** Continuous source mutations over one native parser and one reader cache. */
export interface AlgorithmPlaybackPlan {
  /** Selected family of source operations. */ readonly scenario: AlgorithmScenario;
  /** All committed snapshots, including the initial source. */ readonly snapshots: readonly AlgorithmSnapshot[];
  /** Exact sequential edits with their native reuse evidence. */ readonly operations: readonly AlgorithmOperation[];
  /** Authored playback boundaries over those immutable states. */ readonly steps: readonly AlgorithmStep[];
}
/** Recorded source, tree and output at a teaching boundary. */
export interface AlgorithmPlaybackFrame {
  /** Selected actual source operation. */ readonly operation: AlgorithmOperation;
  /** Selected teaching phase. */ readonly phase: AlgorithmPhase;
  /** Source currently available to the demonstration. */ readonly received: string;
  /** Tree visible at this boundary. */ readonly tree: AlgorithmSnapshot;
  /** Display frame accepted at this boundary. */ readonly snapshot: AlgorithmSnapshot;
  /** Whether the initial tree has been revealed. */ readonly treeVisible: boolean;
  /** Whether the source mutation is visible. */ readonly edited: boolean;
  /** Whether the affected range is highlighted. */ readonly located: boolean;
  /** Whether the new syntax tree has been accepted. */ readonly committed: boolean;
  /** Whether the new reading output is visible. */ readonly finished: boolean;
}

```

### algorithms/playback/plan.ts

```ts
import type { ExampleLocale } from "../../shared/types";
import type { AlgorithmOperation, AlgorithmPhase, AlgorithmPlaybackFrame, AlgorithmPlaybackPlan, AlgorithmScenario, AlgorithmStep } from "./types.ts";
import { algorithmSnapshots } from "./recording/snapshots.ts";

/** Shared teaching playback rate for phase timing and node animations. */
export const algorithmPlaybackRate = 1.5;

/** @param locale - Source and interface language. @param scenario - Selected literal mutation. @returns A recorded operation with real native Tree identity evidence. */
export function createAlgorithmPlayback(locale: ExampleLocale, scenario: AlgorithmScenario): AlgorithmPlaybackPlan {
  const { insertions, snapshots } = algorithmSnapshots(locale, scenario);
  const operations: AlgorithmOperation[] = insertions.map(({ edit, title }, index) => {
    const before = snapshots[index]!, after = snapshots[index + 1]!;
    const previous = new Set(before.blocks.map(block => block.id)), current = new Set(after.blocks.map(block => block.id));
    return Object.freeze({ index, title, edit, snapshots: Object.freeze([before, after]) as readonly [typeof before, typeof after],
      affected: Object.freeze(before.blocks.filter(block => !current.has(block.id))), changed: Object.freeze(after.blocks.filter(block => !previous.has(block.id))),
      reusedTrees: after.blocks.filter(block => previous.has(block.id)).length });
  });
  const steps: AlgorithmStep[] = [{ operation: 0, phase: "source", duration: 1200 }, { operation: 0, phase: "parse", duration: 1800 }];
  for (const operation of operations) for (const [phase, duration] of [["chunk", 1800], ["insert", 2600], ["locate", 3000], ["commit", 3200], ["render", 2000]] as const) {
    steps.push(Object.freeze({ operation: operation.index, phase, duration }));
  }
  return Object.freeze({ scenario, snapshots, operations: Object.freeze(operations), steps: Object.freeze(steps) });
}

/** @param plan - Recorded run. @param step - Teaching boundary from source through display. @returns Immutable data selections; seeking never repeats the source mutation. */
export function algorithmFrame(plan: AlgorithmPlaybackPlan, step = 0): AlgorithmPlaybackFrame {
  const boundary = plan.steps[Math.max(0, Math.min(plan.steps.length - 1, Math.trunc(step)))]!;
  const operation = plan.operations[boundary.operation]!, phase = boundary.phase, [before, after] = operation.snapshots;
  const edited = ["insert", "locate", "commit", "render"].includes(phase), committed = phase === "commit" || phase === "render", finished = phase === "render";
  return { operation, phase, received: (edited ? after : before).source, tree: committed ? after : before, snapshot: finished ? after : before,
    treeVisible: phase !== "source", edited, located: ["locate", "commit", "render"].includes(phase), committed, finished };
}

/** @param plan - Recorded run. @param operation - Selected insertion ordinal. @param phase - Selected data boundary. @returns Its stable seek position in the recording. */
export function algorithmStepIndex(plan: AlgorithmPlaybackPlan, operation: number, phase: AlgorithmPhase): number {
  const index = plan.steps.findIndex(step => step.operation === operation && step.phase === phase);
  if (index < 0) throw new Error("Unknown algorithm playback boundary");
  return index;
}

```

### algorithms/playback/recording/snapshots.ts

```ts
import { ensureSyntaxTree, syntaxTree } from "@quirekit/cm-language";
import { MarkdownStream, parseMarkdown, type MarkdownReadingError } from "@quirekit/incremark-core";
import { MarkdownRenderer } from "@quirekit/incremark/vue";
import type { ExampleLocale } from "../../../shared/types";
import type { AlgorithmScenario, AlgorithmSnapshot } from "../types.ts";
import { algorithmFixture, algorithmInsertion } from "./fixture.ts";
import { createTreeRecorder } from "./tree.ts";

/** @param error - Reader failure. Fixtures must not silently accept fallback output. */
function reject(error: MarkdownReadingError): void { throw error; }

/** @param state - Native source transaction result. @returns The accepted completed syntax, without reparsing through a replacement stream. */
function complete(state: AlgorithmSnapshot["state"]): AlgorithmSnapshot["state"] {
  const tree = ensureSyntaxTree(state, state.doc.length, 1000);
  if (!tree) throw new Error("Algorithm example did not finish parsing");
  return tree === syntaxTree(state) ? state : state.update({ filter: false }).state;
}

/** @param locale - Literal source language. @param scenario - Middle edit, append or late definition. @returns Exact source mutation and actual before/after snapshots from one lifetime. */
export function algorithmSnapshots(locale: ExampleLocale, scenario: AlgorithmScenario) {
  const { source } = algorithmFixture(locale, scenario), renderer = new MarkdownRenderer(), record = createTreeRecorder();
  /** @param state - Completed native state. @returns A detached syntax and reading snapshot, with historical VNodes reserved for tests. */
  function capture(state: AlgorithmSnapshot["state"]): AlgorithmSnapshot {
    const output = renderer.render(state, locale, "algorithm", reject);
    return Object.freeze({ source: state.doc.toString(), state, ...record(state), output, work: Object.freeze({ ...renderer.work }) });
  }
  const stream = scenario === "insert" ? undefined : new MarkdownStream();
  if (stream) {
    stream.reset(source);
    if (stream.pending && !stream.advance(1000)) throw new Error("Initial algorithm source did not finish parsing");
  }
  const snapshots = [capture(stream?.state ?? parseMarkdown(source))], insertions = [];
  // All edits share one identity recorder and reader cache. Navigation never reaches this recording phase.
  for (let index = 0; index < (scenario === "insert" ? 3 : 1); index++) {
    const before = snapshots[index]!;
    const insertion = algorithmInsertion(locale, scenario, before.source, index);
    if (stream) {
      stream.append(insertion.edit.insert);
      if (stream.pending && !stream.advance(1000)) throw new Error("Appended algorithm source did not finish parsing");
      snapshots.push(capture(stream.state));
    } else {
      // Non-prefix stream.update would reset; arbitrary insertions use the native source transaction.
      snapshots.push(capture(complete(before.state.update({ changes: insertion.edit, filter: false }).state)));
    }
    insertions.push(insertion);
  }
  return { insertions: Object.freeze(insertions), snapshots: Object.freeze(snapshots) };
}

```

### algorithms/playback/recording/fixture.ts

```ts
import type { ExampleLocale } from "../../../shared/types";
import type { AlgorithmEdit, AlgorithmScenario } from "../types.ts";

/** @param locale - Literal source language. @param scenario - Requested mutation. @returns A bounded source and one exact transaction, with unchanged context on both sides. */
export function algorithmFixture(locale: ExampleLocale, scenario: AlgorithmScenario): { source: string; edit: AlgorithmEdit } {
  const zh = locale === "zh-CN";
  const heading = zh ? "## 一份旅行计划" : "## A travel plan";
  const first = zh
    ? "第一段：周六上午，我们从车站出发，沿着河边步道走到老街。路上会经过一座石桥、几家早餐店和一片开阔的草地。大家可以慢慢走，顺便拍照，不必赶时间。到达老街后先找一家小店吃早餐，再去旁边的书店看看。这里记录的是已经写好的行程，接下来的修改发生在第二段，不会改变这一段的文字或格式。"
    : "First paragraph: On Saturday morning we leave the station and follow the riverside path to the old town. We pass a stone bridge, a few breakfast shops and an open meadow. After breakfast we visit the bookshop. This part of the plan is already written; the next edit happens in the second paragraph.";
  const middle = scenario === "reference" ? "[foo][bar][baz]"
    : zh ? "第二段：午后去公园散步，然后回到河边喝杯咖啡。" : "Second paragraph: Visit the park in the afternoon, then have coffee by the river.";
  const last = zh
    ? "第三段：傍晚从河边回到车站，途中可以买一点面包带回家。如果还有时间，就在广场上坐一会儿，看看远处的钟楼和来往的人。晚餐暂时没有安排，我们可以根据当天的天气与心情决定。这一段位于修改位置之后，它的文字没有变化；在前面插入内容时，起止位置会向后移动，但仍然可能使用同一个语法树对象，不必重新解析这段行程。"
    : "Third paragraph: In the evening we return to the station, perhaps buying some bread on the way. If there is time we sit in the square and watch the clock tower. Dinner is still open. This paragraph follows the edit: its text stays the same while its source positions move forward, and the parser may retain the same tree object.";
  const source = `${heading}\n\n${first}\n\n${middle}\n\n${last}\n\n`;
  const edit = scenario === "insert" ? { from: source.indexOf(zh ? "午后" : "Visit"), insert: zh ? "**新增内容**，" : "**new stop**, " }
    : { from: source.length, insert: scenario === "reference" ? `[baz]: ${zh ? "" : "/en"}/guide/getting-started\n`
      : zh ? "新增一段：记得带上相机。\n\n" : "New paragraph: Remember to bring a camera.\n\n" };
  return { source, edit };
}

/** @param locale - Literal source language. @param scenario - Selected operation family. @param source - Accepted source after the preceding edit. @param index - Operation ordinal. @returns One exact insertion and its human name; positions are resolved in the current source. */
export function algorithmInsertion(locale: ExampleLocale, scenario: AlgorithmScenario, source: string, index: number) {
  const zh = locale === "zh-CN";
  if (scenario !== "insert") return { edit: { from: source.length, insert: algorithmFixture(locale, scenario).edit.insert }, title: zh ? scenario === "reference" ? "后置链接定义" : "追加一个段落" : scenario === "reference" ? "Late link definition" : "Append a paragraph" };
  const title = zh ? ["第二段加入加粗", "第一段加入代码", "中间插入列表"][index] : ["Bold in paragraph 2", "Code in paragraph 1", "Insert a list between paragraphs"][index];
  const edit = index === 0 ? { from: source.indexOf(zh ? "午后" : "Visit"), insert: zh ? "**新增内容**，" : "**new stop**, " }
    : index === 1 ? { from: source.indexOf(zh ? "文字或格式" : "next edit"), insert: zh ? "`地图`、" : "`map` and the " }
      : { from: source.indexOf(zh ? "第三段：" : "Third paragraph:"), insert: zh ? "- 带上相机\n- 带上一把雨伞\n\n" : "- Bring a camera\n- Bring an umbrella\n\n" };
  if (!title || edit.from < 0) throw new Error("Invalid algorithm insertion fixture");
  return { edit, title };
}

```

### algorithms/playback/recording/tree.ts

```ts
import { syntaxTree } from "@quirekit/cm-language";
import type { AlgorithmBlock, AlgorithmNode, AlgorithmSnapshot, NativeNode, NativeTree } from "../types.ts";

/** @returns A run-owned identity recorder, comparing native Tree objects rather than their text or ranges. */
export function createTreeRecorder() {
  const identities = new WeakMap<NativeTree, string>();
  let next = 0;
  /** @param tree - Native tree object. @returns The same local key whenever the native parser retains this object. */
  function identity(tree: NativeTree): string {
    let key = identities.get(tree);
    if (!key) { key = `T${++next}`; identities.set(tree, key); }
    return key;
  }
  /** @param node - Real native node. @param source - Matching source. @param key - Buffered nodes get a path key, without claiming native Tree identity. @returns A detached syntax-node description. */
  function read(node: NativeNode, source: string, key: string): AlgorithmNode {
    const children: AlgorithmNode[] = [];
    for (let child = node.firstChild, index = 0; child; child = child.nextSibling, index++) {
      children.push(read(child, source, `${key}.${index}`));
    }
    return Object.freeze({ id: key, name: node.name, from: node.from, to: node.to, text: source.slice(node.from, node.to), children: Object.freeze(children) });
  }
  /** @param state - Completed parser state. @returns The actual root key and all its top-level branches. */
  return function record(state: AlgorithmSnapshot["state"]): Pick<AlgorithmSnapshot, "rootId" | "blocks"> {
    const tree = syntaxTree(state), rootId = identity(tree), source = state.doc.toString(), blocks: AlgorithmBlock[] = [];
    for (let node = tree.topNode.firstChild; node; node = node.nextSibling) {
      const key = node.tree ? identity(node.tree) : `${rootId}.${blocks.length}`;
      blocks.push(Object.freeze({ ...read(node, source, key), slot: blocks.length }));
    }
    return { rootId, blocks: Object.freeze(blocks) };
  };
}

```

### algorithms/playback/player.vue

```vue
<script setup lang="ts">
import { computed, ref } from "vue";
import { QuireButton, QuireIcon, QuireIconButton, QuireTypography } from "@quirekit/ui/vue";
import { QuireMarkdown } from "@quirekit/incremark/vue";
import { syntaxTree } from "@quirekit/cm-language";
import type { ExampleLocale } from "../../shared/types";
import type { AlgorithmPhase, AlgorithmPlaybackPlan } from "./types";
import { algorithmFrame, algorithmStepIndex } from "./plan";
import { algorithmCopy } from "./copy";
import { algorithmGraph } from "./graph/trace";
import { useAlgorithmPlayer } from "./graph/use-player";
import AlgorithmCanvas from "./graph/canvas.vue";

const props = defineProps<{
  /** Recorded continuous source session. A new scenario remounts this owner. */ plan: AlgorithmPlaybackPlan;
  /** Source and interface language. */ locale: ExampleLocale;
}>();
const compact = ref(false);
const model = computed(() => algorithmGraph(props.plan, props.locale, compact.value));
const { step, progress, playing, ready, attach, retire, toggle, seek, replay } = useAlgorithmPlayer(model);
const frame = computed(() => algorithmFrame(props.plan, step.value));
const copy = computed(() => algorithmCopy(props.locale, props.plan, frame.value));
const work = computed(() => frame.value.operation.snapshots[1].work);
const phases: readonly AlgorithmPhase[] = ["parse", "chunk", "insert", "locate", "commit", "render"];
const motion = computed(() => playing.value ? "running" : progress.value < 1 ? "paused" : "settled");
/** @param operation - Actual insertion ordinal. @param phase - Selected data boundary. Navigates only the recording. */
function select(operation: number, phase: AlgorithmPhase): void { seek(algorithmStepIndex(props.plan, operation, phase)); }
</script>
<template>
  <div class="algorithm-run" data-algorithm-playback data-renderer="quirekit-flowchart-x6" :data-step="step" :data-operation="frame.operation.index" :data-phase="frame.phase" :data-progress="progress.toFixed(4)" :data-playing="playing" :data-motion="motion">
    <div class="algorithm-controls">
      <QuireButton size="sm" variant="solid" color="primary" :disabled="!ready" @click="toggle"><template #leading><QuireIcon :name="playing ? 'pause' : 'play'" /></template>{{ playing ? copy.pause : copy.play }}</QuireButton>
      <QuireIconButton size="sm" variant="ghost" :label="copy.back" :disabled="!ready || step === 0" @click="seek(step - 1)"><QuireIcon name="skipBack" /></QuireIconButton>
      <QuireIconButton size="sm" variant="ghost" :label="copy.forward" :disabled="!ready || step === plan.steps.length - 1" @click="seek(step + 1)"><QuireIcon name="skipForward" /></QuireIconButton>
      <QuireIconButton size="sm" variant="ghost" :label="copy.replay" :disabled="!ready" @click="replay"><QuireIcon name="rotateCcw" /></QuireIconButton>
      <QuireTypography as="span" variant="helper" class="algorithm-fixed">{{ copy.fixed }}</QuireTypography>
    </div>
    <nav v-if="plan.operations.length > 1" class="algorithm-operations" :aria-label="copy.operations">
      <QuireButton v-for="operation in plan.operations" :key="operation.index" size="sm" :disabled="!ready" :variant="frame.operation.index === operation.index ? 'solid' : 'outline'" :color="frame.operation.index === operation.index ? 'primary' : 'neutral'" :aria-current="frame.operation.index === operation.index ? 'step' : undefined" :data-current="frame.operation.index === operation.index" @click="select(operation.index, 'chunk')"><template #leading><span>{{ operation.index + 1 }}</span></template>{{ operation.title }}</QuireButton>
    </nav>
    <nav class="algorithm-timeline" :aria-label="copy.timeline">
      <QuireButton v-for="(phase, index) in phases" :key="phase" size="sm" :disabled="!ready" :variant="frame.phase === phase ? 'soft' : 'ghost'" :color="frame.phase === phase ? 'primary' : 'neutral'" :aria-current="frame.phase === phase ? 'step' : undefined" :data-current="frame.phase === phase" :data-phase-button="phase" @click="select(phase === 'parse' ? 0 : frame.operation.index, phase)">{{ copy.phases[index] }}</QuireButton>
    </nav>
    <div class="algorithm-progress" aria-hidden="true"><span :style="{ width: `${(step + progress) / plan.steps.length * 100}%` }"></span></div>
    <AlgorithmCanvas :model="model" :locale="locale" @ready="attach" @retired="retire" @compact="compact = $event" />
    <QuireTypography variant="helper">{{ locale === 'en' ? 'Drawn and animated by QuireKit Flowchart · 1.5× · fixed view' : '由 QuireKit 流程图绘制与播放 · 1.5 倍 · 固定视图' }}</QuireTypography>
    <div class="algorithm-explanation" aria-live="polite" aria-atomic="true">
      <span class="algorithm-step-number">{{ frame.operation.index + 1 }}</span>
      <div><QuireTypography as="h3" variant="control">{{ copy.title }}</QuireTypography><QuireTypography variant="helper">{{ copy.description }}</QuireTypography></div>
    </div>
    <section class="algorithm-reader" :aria-label="copy.output">
      <div class="algorithm-reader-heading"><QuireTypography as="h3" variant="control">{{ copy.output }}</QuireTypography><span>{{ frame.finished ? copy.newTree : copy.oldTree }} · {{ copy.ordinal }}</span></div>
      <QuireMarkdown class="algorithm-output" :state="frame.snapshot.state" :locale="locale" />
    </section>
    <div class="algorithm-measurements" :data-complete="frame.finished">
      <div><strong>{{ frame.committed ? frame.operation.reusedTrees : '—' }}</strong><span>{{ copy.syntaxReuse }}</span></div>
      <div><strong>{{ frame.finished ? work.reusedBlocks : '—' }}</strong><span>{{ copy.displayReuse }}</span></div>
      <QuireTypography variant="helper">{{ locale === 'en' ? 'Work for the current edit; replay and painting are excluded.' : '计数对应当前这次更新，不含回放与绘制。' }}</QuireTypography>
    </div>
    <details class="algorithm-inspect"><summary>{{ copy.full }}</summary><pre class="algorithm-source">{{ frame.received }}</pre><pre>{{ syntaxTree(frame.tree.state).toString() }}</pre></details>
  </div>
</template>
<style scoped src="./player.css"></style>

```

### algorithms/playback/copy.ts

```ts
import type { ExampleLocale } from "../../shared/types";
import type { AlgorithmPlaybackFrame, AlgorithmPlaybackPlan } from "./types.ts";

/** @param locale - Interface language. @param plan - Continuous recorded run. @param frame - Current actual operation and data boundary. @returns Copy reflecting native evidence, including structural-context rebuilds. */
export function algorithmCopy(locale: ExampleLocale, plan: AlgorithmPlaybackPlan, frame: AlgorithmPlaybackFrame) {
  const zh = locale === "zh-CN", { operation, phase } = frame, [before, after] = operation.snapshots;
  const old = operation.affected[0], next = operation.changed[0], delta = operation.edit.insert.length;
  const structural = plan.scenario === "insert" && operation.index === 2;
  const target = old ? `${old.name} ${old.id} · [${old.from}, ${old.to})` : zh ? "新增片段的空位" : "space for new source";
  const changes = operation.changed.map(block => `${block.name} ${block.id}`).join(" · ");
  const retained = after.blocks.filter(block => before.blocks.some(previous => previous.id === block.id)).map(block => block.id).join(" · ");
  return zh ? {
    play: "播放变化", pause: "暂停", back: "上一步", forward: "下一步", replay: "重播", timeline: "变化阶段", operations: "选择连续插入",
    source: "Markdown 原文", tree: "Lezer 语法树", output: "阅读结果", incoming: "待插入的片段", original: "原文", newSource: "已插入", oldTree: "更新前", newTree: "已更新",
    waiting: "原文即将展开为一棵 Lezer 树。", retained: "保留对象", changed: "换入新对象", added: "新增分支", pending: "新分支的空位", range: "这次受影响的范围",
    snippet: "受影响的原文显示插入点附近；其他长段落显示摘要。", full: "查看完整原文与语法树", identity: "绿色保留 · 橙色定位 · 蓝色更新；T 编号来自真实树对象。",
    syntaxReuse: "保留的语法树", displayReuse: "复用的显示块", fixed: "固定视图", phases: ["原始树", "收到片段", "插入原文", "定位范围", "换入新树", "更新显示"],
    ordinal: `第 ${operation.index + 1} / ${plan.operations.length} 次插入`,
    title: phase === "source" ? "从同一份原文开始" : phase === "parse" ? "原文展开为 Lezer 树" : phase === "chunk" ? operation.title
      : phase === "insert" ? `把 ${delta} 个字符放到位置 ${operation.edit.from}` : phase === "locate" ? structural ? "插入位于段落边界，检查相邻分支" : `找到 ${target}`
        : phase === "commit" ? `换入 ${changes}` : "这次变化进入阅读结果",
    description: phase === "source" || phase === "parse" ? "一个标题、三个段落。接下来在同一份原文中连续插入，上一轮的结果就是下一轮的起点。"
      : phase === "chunk" ? "片段先停在上方。跟随它进入原文，再沿着高亮轨迹找到受影响的语法分支。"
        : phase === "insert" ? `新增片段已经进入原文。此时树和阅读结果先保留上一轮，接下来定位 ${target}。`
          : phase === "locate" ? structural ? "这里插入两项列表。原第三段虽没有改字，解析器仍需重新确认它的边界；不能把它直接标为保留。" : plan.scenario === "reference" ? "定义位于末尾，baz 被前面这段使用。沿引用依赖找到旧分支，更新它的链接分组。" : `插入点 ${operation.edit.from} 落入这段原文的范围。橙色框标出旧分支，其他分支保持原对象。`
            : phase === "commit" ? `蓝色分支进入树，${retained} 保留。原文位置后移 +${delta}；树对象的保留与替换分别判断。`
              : `这次保留 ${operation.reusedTrees} 个树对象，复用 ${after.work.reusedBlocks} 个显示块。${operation.index < plan.operations.length - 1 ? "继续播放，下一次插入接着这份结果更新。" : "本次演示已完成。"}`,
  } : {
    play: "Play changes", pause: "Pause", back: "Previous", forward: "Next", replay: "Replay", timeline: "Change phase", operations: "Choose an insertion",
    source: "Markdown source", tree: "Lezer syntax tree", output: "Reader output", incoming: "Incoming fragment", original: "source", newSource: "inserted", oldTree: "before update", newTree: "updated",
    waiting: "The source is about to unfold into a Lezer tree.", retained: "retained object", changed: "new object", added: "new branch", pending: "space for a new branch", range: "Affected range",
    snippet: "Affected source shows the insertion context; other long paragraphs show excerpts.", full: "Inspect complete source and syntax", identity: "Green: retained · amber: located · blue: updated. T labels identify native tree objects.",
    syntaxReuse: "syntax trees retained", displayReuse: "display blocks reused", fixed: "Fixed view", phases: ["Original tree", "Receive text", "Insert source", "Find range", "Accept tree", "Update output"],
    ordinal: `Insertion ${operation.index + 1} / ${plan.operations.length}`,
    title: phase === "source" ? "Start with one source document" : phase === "parse" ? "Source unfolds into a Lezer tree" : phase === "chunk" ? operation.title
      : phase === "insert" ? `Insert ${delta} characters at ${operation.edit.from}` : phase === "locate" ? structural ? "Check the neighboring paragraph boundary" : `Locate ${target}`
        : phase === "commit" ? `Attach ${changes}` : "The change reaches the reader",
    description: phase === "source" || phase === "parse" ? "A heading and three paragraphs. Each insertion builds on the preceding result, using the same source and reader lifetime."
      : phase === "chunk" ? "Follow this fragment into the source and along the highlighted path to the affected syntax branch."
        : phase === "insert" ? `The fragment has entered the source; syntax and output still show the previous result. Next, locate ${target}.`
          : phase === "locate" ? structural ? "A two-item list is inserted at a paragraph boundary. The old final paragraph has unchanged text but its native branch is still rebuilt." : plan.scenario === "reference" ? "The definition arrives at the end; the earlier paragraph uses baz. Follow its reference dependency to update the old link grouping." : `Position ${operation.edit.from} falls inside the highlighted source range. The amber box marks the previous affected branch.`
            : phase === "commit" ? `The blue branches enter the tree; ${retained} remain. Source positions move by +${delta}, separately from native identity reuse.`
              : `This edit retains ${operation.reusedTrees} trees and reuses ${after.work.reusedBlocks} display blocks. ${operation.index < plan.operations.length - 1 ? "The next insertion continues from this result." : "The recorded run is complete."}`,
  };
}

```

### algorithms/playback/graph/trace.ts

```ts
import type { FlowchartMotionEffect, FlowchartPlaybackStep } from "@quirekit/flowchart";
import type { ExampleLocale } from "../../../shared/types";
import type { AlgorithmPlaybackPlan } from "../types.ts";
import { algorithmFrame } from "../plan.ts";
import { algorithmCopy } from "../copy.ts";
import { algorithmScene, algorithmSlots } from "./scene.ts";
import type { AlgorithmGraphPlan } from "./types.ts";

/** @param plan - Already-recorded parser transactions. @param locale - Source/caption language. @param compact - Vertical presentation. @returns Portable native-flowchart scenes and public playback cues; no animation implementation lives here. */
export function algorithmGraph(plan: AlgorithmPlaybackPlan, locale: ExampleLocale, compact: boolean): AlgorithmGraphPlan {
  const slots = algorithmSlots(plan);
  const steps: FlowchartPlaybackStep[] = plan.steps.map((step, index) => {
    const frame = algorithmFrame(plan, index), copy = algorithmCopy(locale, plan, frame), scene = algorithmScene(frame, locale, slots, compact);
    const effects: FlowchartMotionEffect[] = [];
    if (["chunk", "insert", "locate", "commit"].includes(frame.phase)) {
      const id = frame.phase === "chunk" || frame.phase === "insert" ? "transfer-source" : frame.phase === "locate" ? "transfer-tree" : "transfer-new";
      effects.push({ id, kind: "travel", payload: "token", label: frame.operation.edit.insert.trim().replace(/\n/gu, " ↵ ").slice(0, 80), duration: Math.min(1800, step.duration), easing: "ease-in-out", tone: frame.phase === "locate" ? "running" : "completed" });
      if (frame.phase === "commit" && frame.operation.changed.length > 1) effects.push({ id: "transfer-other", kind: "travel", payload: "token", label: locale === "en" ? "Recheck boundary" : "重新确认边界", duration: Math.min(1800, step.duration), easing: "ease-in-out", tone: "completed" });
      const targets = frame.committed ? frame.operation.changed : frame.operation.affected;
      if (frame.phase === "locate" || frame.phase === "commit") for (const target of targets) effects.push({ id: `tree-${target.id}`, kind: "pulse", duration: Math.min(1800, step.duration), tone: frame.phase === "locate" ? "running" : "completed" });
    }
    return { id: `step-${index}`, title: copy.title, description: copy.description, duration: step.duration,
      scene: scene.document, sceneDuration: Math.min(step.duration, frame.phase === "commit" ? 1400 : 800), changes: scene.changes, effects };
  });
  const documents = steps.map(step => step.scene!);
  const width = Math.max(...documents.flatMap(document => document.nodes.map(node => node.x + node.width))) + 24;
  const height = Math.max(...documents.flatMap(document => document.nodes.map(node => node.y + node.height))) + 24;
  return { document: steps[0]!.scene!, trace: { title: locale === "en" ? "Markdown tree evolution" : "Markdown 树的变化", presentation: "status", hidden: ["transfer-source", "transfer-tree", "transfer-new", "transfer-other"], steps }, width, height };
}

```

### algorithms/playback/graph/scene.ts

```ts
import { createFlowchart, createFlowchartNode, readFlowchart } from "@quirekit/flowchart";
import type { FlowchartDocument, FlowchartEdge, FlowchartNode, FlowchartRunChange, FlowchartRunState } from "@quirekit/flowchart";
import type { ExampleLocale } from "../../../shared/types";
import type { AlgorithmNode, AlgorithmPlaybackFrame, AlgorithmPlaybackPlan } from "../types.ts";
import { algorithmPairs, sourceExcerpt, sourceTarget } from "./pairs.ts";

/** @param plan - Continuous real parse snapshots. @returns Stable positions including space reserved for genuinely new branches. */
export function algorithmSlots(plan: AlgorithmPlaybackPlan): ReadonlyMap<string, number> {
  const slots = new Map(plan.snapshots.at(-1)!.blocks.map((block, index) => [block.id, index]));
  for (const operation of [...plan.operations].reverse()) for (const pair of algorithmPairs(operation)) if (pair.before) slots.set(pair.before.id, slots.get(pair.after.id)!);
  return slots;
}

/** @param frame - Source/tree boundary. @param locale - Caption language. @param slots - Stable branch positions. @param compact - Vertical readable layout. @returns One portable actual-data scene and its target states. */
export function algorithmScene(frame: AlgorithmPlaybackFrame, locale: ExampleLocale, slots: ReadonlyMap<string, number>, compact: boolean): { document: FlowchartDocument; changes: readonly FlowchartRunChange[] } {
  const nodes: FlowchartNode[] = [], edges: FlowchartEdge[] = [], changes: FlowchartRunChange[] = [];
  const zh = locale === "zh-CN", columns = Math.max(...slots.values()) + 1, width = compact ? 240 : columns * 242 + 24;
  const source = frame.edited ? frame.operation.snapshots[1] : frame.operation.snapshots[0];
  const sourceFocus = sourceTarget(frame.operation), targetSlot = slots.get(sourceFocus.id) ?? columns - 1;
  /** @param id - Graph identity. @param text - Literal caption. @param x - World x. @param y - World y. @param w - Width. @param h - Height. @param heading - Larger label. @returns Canonical node with readable text. */
  function box(id: string, text: string, x: number, y: number, w: number, h: number, heading = false): FlowchartNode {
    const node = { ...createFlowchartNode(id, "process", { x, y }, text), width: w, height: h, style: { fontSize: compact ? heading ? 17 : 15 : heading && h > 64 ? 26 : 22, padding: heading && h <= 64 ? 4 : 10, textAlign: "left" as const, ...(heading ? { bold: true } : {}) } };
    nodes.push(node); return node;
  }
  /** @param parent - Real syntax parent. @param child - Real syntax child. Adds a normal native diagram connector. */
  function connect(parent: FlowchartNode, child: FlowchartNode): void {
    edges.push({ id: `link-${child.id}`, source: { node: parent.id, anchor: { x: .5, y: 1 } }, target: { node: child.id, anchor: { x: .5, y: 0 } },
      vertices: [], routing: "orthogonal", connector: "rounded", label: "", labelPosition: .5, style: { targetArrow: "none" } });
  }
  /** @param syntax - Real inline syntax. @param parent - Diagram parent. @param x - Branch x. @param y - Next y. @param level - Native nesting depth. @param state - Parent branch status. @returns Next available y in the branch. */
  function children(syntax: readonly AlgorithmNode[], parent: FlowchartNode, x: number, y: number, level: number, state?: FlowchartRunState): number {
    for (const child of syntax) {
      const node = box(`tree-${child.id}`, `${child.name}\n[${child.from}, ${child.to})\n${child.text.slice(0, 24).replace(/\n/gu, " ↵ ")}`, x + level * 12, y, (compact ? 216 : 218) - level * 12, compact ? 84 : 128);
      connect(parent, node); if (state) changes.push({ id: node.id, state });
      y = children(child.children, node, x, y + (compact ? 104 : 148), level + 1, state);
    }
    return y;
  }
  box("incoming", `${zh ? "插入" : "Insert"} ${frame.operation.index + 1} · ${frame.operation.title}\n${frame.operation.edit.insert.trim().replace(/\n/gu, " ↵ ")}\n+${frame.operation.edit.insert.length} UTF-16 @ ${frame.operation.edit.from}`, compact ? 12 : width / 2 - 280, 12, compact ? 216 : 560, compact || !zh ? 200 : 134, true);
  const sourceY = compact || !zh ? 240 : 156;
  box("source-title", zh ? "Markdown 原文" : "Markdown source", 12, sourceY, compact ? 216 : width - 24, 44, true);
  for (const block of source.blocks) {
    const slot = slots.get(block.id) ?? block.slot, focus = slot === targetSlot;
    box(`source-${slot}`, `${String(block.slot + 1).padStart(2, "0")}  [${block.from}, ${block.to})\n${sourceExcerpt(block, frame.operation, focus)}`, compact ? 12 : 12 + slot * 242, sourceY + 64 + (compact ? block.slot * 158 : 0), compact ? 216 : 218, compact ? 140 : 216);
    changes.push({ id: `source-${slot}`, state: focus && frame.edited ? "running" : "reused" });
  }
  const treeY = sourceY + (compact ? source.blocks.length * 158 + 84 : 320);
  box("tree-title", zh ? "Lezer 语法树" : "Lezer syntax tree", 12, treeY, compact ? 216 : width - 24, 44, true);
  const root = frame.treeVisible ? box(`root-${frame.tree.rootId}`, `Document  ${frame.tree.rootId}\n[0, ${frame.tree.source.length})`, compact ? 12 : width / 2 - 135, treeY + 68, compact ? 216 : 270, compact ? 80 : 100, true) : undefined;
  let bottom = treeY + 196;
  if (root) for (const [index, block] of frame.tree.blocks.entries()) {
    const slot = slots.get(block.id) ?? block.slot, previous = frame.operation.snapshots[0].blocks.find(old => old.id === block.id);
    const pair = algorithmPairs(frame.operation).find(item => (frame.committed ? item.after : item.before)?.id === block.id);
    const replaced = frame.committed && pair?.before && pair.before.id !== block.id ? `${pair.before.id} → ` : "";
    const delta = frame.committed && previous ? block.from - previous.from : 0;
    const state: FlowchartRunState | undefined = !frame.located ? undefined : frame.committed ? frame.operation.changed.some(item => item.id === block.id) ? "completed" : "reused" : frame.operation.affected.some(item => item.id === block.id) ? "running" : "reused";
    const status = state === "reused" ? zh ? "保留对象" : "Retained object" : state === "running" ? zh ? "定位受影响分支" : "Affected branch" : state === "completed" ? zh ? "换入新对象" : "New object" : "";
    const x = compact ? 12 : 12 + slot * 242, y = compact ? bottom : treeY + 196;
    const node = box(`tree-${block.id}`, `${block.name}  ${replaced}${block.id}\n[${block.from}, ${block.to})${delta ? `  +${delta}` : ""}\n${status ? `${status}\n` : ""}${block.text.length > 24 ? `${block.text.slice(0, 24)}…` : block.text}`, x, y, compact ? 216 : 218, compact ? 174 : 220);
    connect(root, node);
    if (state) changes.push({ id: node.id, state });
    const focus = frame.operation.affected.some(item => item.id === block.id) || frame.operation.changed.some(item => item.id === block.id);
    const next = !compact || focus ? children(block.children, node, x, y + (compact ? 196 : 244), 0, state) : y + 194;
    bottom = Math.max(bottom, next + (compact ? 20 : 0));
  }
  // Travel paths belong to the graph, using real diagram coordinates. Keeping them
  // hidden preserves the tree's visual structure while the library moves payloads.
  const sourceNode = nodes.find(node => node.id === `source-${targetSlot}`);
  const affected = frame.committed ? frame.operation.changed[0] : frame.operation.affected[0];
  const treeNode = nodes.find(node => node.id === `tree-${affected?.id}`);
  const incoming = nodes[0]!;
  const from = { x: incoming.x + incoming.width / 2, y: incoming.y + incoming.height };
  const drop = sourceNode ? { x: sourceNode.x + sourceNode.width / 2, y: sourceNode.y + sourceNode.height / 2 } : { x: compact ? 120 : 12 + targetSlot * 242 + 109, y: sourceY + 140 };
  const tree = treeNode ? { x: treeNode.x + treeNode.width / 2, y: treeNode.y + treeNode.height / 2 } : { x: drop.x, y: treeY + 240 };
  const neighbor = nodes.find(node => node.id === `tree-${frame.operation.changed[1]?.id}`);
  const other = neighbor ? { x: neighbor.x + neighbor.width / 2, y: neighbor.y + neighbor.height / 2 } : tree;
  for (const [id, a, b] of [["transfer-source", from, drop], ["transfer-tree", drop, tree], ["transfer-new", { x: tree.x, y: tree.y - 48 }, tree], ["transfer-other", { x: tree.x, y: tree.y - 48 }, other]] as const) {
    const lane = Math.max(a.x, b.x, id === "transfer-tree" && root ? root.x + root.width : 0) + 32;
    edges.push({ id, source: a, target: b, vertices: [{ x: compact ? a.x : lane, y: a.y + 24 }, { x: compact ? b.x : lane, y: b.y - 24 }], routing: "straight", connector: "smooth", label: "", labelPosition: .5, style: { targetArrow: "none" } });
  }
  return { document: readFlowchart({ ...createFlowchart(), nodes, edges }), changes };
}

```

### algorithms/playback/graph/pairs.ts

```ts
import type { AlgorithmBlock, AlgorithmOperation, AlgorithmPlaybackFrame } from "../types.ts";
import type { AlgorithmPair } from "./types.ts";

/** @param operation - Actual sequential edit. @returns Visual alignment, without equating text similarity with native reuse. */
export function algorithmPairs(operation: AlgorithmOperation): readonly AlgorithmPair[] {
  const [before, after] = operation.snapshots, { from, insert } = operation.edit;
  return after.blocks.map(block => {
    const previous = before.blocks.find(old => old.id === block.id) ?? before.blocks.find(old => old.name === block.name && old.from + (old.from >= from ? insert.length : 0) === block.from);
    return { key: block.id, before: previous, after: block };
  });
}
/** @param operation - Accepted edit. @returns Real accepted syntax containing the insertion. */
export function sourceTarget(operation: AlgorithmOperation): AlgorithmBlock {
  const block = operation.snapshots[1].blocks.find(item => item.from <= operation.edit.from && item.to > operation.edit.from) ?? operation.changed[0];
  if (!block) throw new Error("No source target for recorded edit");
  return block;
}
/** @param pair - Presentation alignment. @param frame - Current accepted boundary. @returns Status based only on recorded native identities. */
export function branchStatus(pair: AlgorithmPair, frame: AlgorithmPlaybackFrame): string {
  if (!frame.located) return "original";
  if (!frame.committed) return pair.before ? frame.operation.affected.some(block => block.id === pair.before!.id) ? "affected" : "retained" : "pending";
  return pair.before?.id === pair.after.id ? "retained" : pair.before ? "changed" : "added";
}
/** @param block - Actual source slice. @param operation - Current edit. @param focus - Reveal its insertion context. @returns Exact source with explicit omissions. */
export function sourceExcerpt(block: AlgorithmBlock, operation: AlgorithmOperation, focus = false): string {
  const local = operation.edit.from - block.from, start = focus && local >= 0 && local < block.text.length ? Math.max(0, local - 24) : 0;
  const end = Math.min(block.text.length, start + (focus ? 68 : 36));
  return `${start ? "…" : ""}${block.text.slice(start, end)}${end < block.text.length ? "…" : ""}`;
}

```

### algorithms/playback/graph/types.ts

```ts
import type { FlowchartDocument, FlowchartPlaybackTrace } from "@quirekit/flowchart";
import type { AlgorithmBlock } from "../types";

/** Presentation alignment; matching ranges never claims native identity reuse. */
export interface AlgorithmPair {
  /** Previous native branch when this teaching slot existed. */ readonly before?: AlgorithmBlock;
  /** Accepted native branch at this slot. */ readonly after: AlgorithmBlock;
  /** Accepted identity used for presentation alignment only. */ readonly key: string;
}

/** Portable graph and teaching trace over the recorded parser snapshots. */
export interface AlgorithmGraphPlan {
  /** Static source graph preserved by the flowchart editor. */ readonly document: FlowchartDocument;
  /** Actual snapshot-derived scenes and public motion effects. */ readonly trace: FlowchartPlaybackTrace;
  /** Stable world width for responsive presentation. */ readonly width: number;
  /** Stable world height across all steps. */ readonly height: number;
}

```

### algorithms/playback/graph/use-player.ts

```ts
import { computed, onBeforeUnmount, ref, shallowRef, watch, type Ref } from "vue";
import type { FlowchartEditor } from "@quirekit/flowchart-x6";
import type { FlowchartPlaybackState } from "@quirekit/flowchart";
import type { AlgorithmGraphPlan } from "./types.ts";
import { algorithmPlaybackRate } from "../plan.ts";

/** @param model - Immutable diagram and trace. @returns Public flowchart controls and a read-only progress sampler; the library owns all time and motion. */
export function useAlgorithmPlayer(model: Ref<AlgorithmGraphPlan>) {
  const editor = shallowRef<FlowchartEditor>(), state = shallowRef<FlowchartPlaybackState>(), progress = ref(1);
  let unsubscribe: (() => void) | undefined, sample = 0, started = false;
  const step = computed(() => Math.max(0, (state.value?.index ?? 2) - 1)), playing = computed(() => state.value?.status === "playing");
  /** Samples only the public constant-time progress getter, never advancing time or reading source. */
  function paint(): void { progress.value = editor.value?.playback.getProgress() ?? 1; if (playing.value) sample = requestAnimationFrame(paint); }
  /** @param value - Actual flowchart semantic boundary. */
  function changed(value: FlowchartPlaybackState): void {
    state.value = value; cancelAnimationFrame(sample); progress.value = editor.value?.playback.getProgress() ?? 1;
    if (value.status === "playing") sample = requestAnimationFrame(paint);
  }
  /** @param value - Accepted native editor. Loads the portable trace exactly once at its mount boundary. */
  function attach(value: FlowchartEditor): void {
    editor.value = value; value.playback.load(model.value.trace); value.playback.setRate(algorithmPlaybackRate);
    unsubscribe = value.playback.subscribe(changed); value.playback.seek(2);
  }
  /** @param value - Retired native editor. Releases only its own subscription and progress sampling. */
  function retire(value: FlowchartEditor): void { if (editor.value !== value) return; unsubscribe?.(); unsubscribe = undefined; cancelAnimationFrame(sample); editor.value = undefined; state.value = undefined; }
  /** Toggles the library's logical player; the first run begins with source materialization. */
  function toggle(): void {
    const player = editor.value?.playback; if (!player) return;
    if (playing.value) player.pause(); else if (!started || state.value?.status === "completed") { started = true; player.replay(); } else player.play();
  }
  /** @param boundary - Recorded teaching step. Public prefix seeking never repeats the actual parser transaction. */
  function seek(boundary: number): void { started = true; editor.value?.playback.seek(Math.max(0, Math.min(model.value.trace.steps.length, boundary + 1))); }
  /** Replays the library's accepted trace without rerunning source edits. */
  function replay(): void { started = true; editor.value?.playback.replay(); }
  watch(model, value => {
    const player = editor.value?.playback; if (!player) return;
    const before = player.getState(), position = player.getProgress();
    player.load(value.trace); player.setRate(before.rate); player.seek(Math.min(before.index, value.trace.steps.length));
    if (position < 1) player.seekProgress(position);
    if (before.status === "playing") player.play();
  });
  onBeforeUnmount(() => { unsubscribe?.(); cancelAnimationFrame(sample); });
  return { step, progress, playing, ready: computed(() => !!editor.value), attach, retire, toggle, seek, replay };
}

```

### algorithms/playback/graph/canvas.vue

```vue
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
import type { FlowchartEditor } from "@quirekit/flowchart-x6";
import type { AlgorithmGraphPlan } from "./types";
import type { ExampleLocale } from "../../../shared/types";
const props = defineProps<{
  /** Actual-data scenes; playback owns every visual transition. */ model: AlgorithmGraphPlan;
  /** Native control and accessible caption language. */ locale: ExampleLocale;
}>();
const emit = defineEmits<{
  /** Accepted native renderer lifetime. */ ready: [editor: FlowchartEditor];
  /** Retired native renderer lifetime. */ retired: [editor: FlowchartEditor];
  /** Readable vertical layout chosen from this actual host width. */ compact: [value: boolean];
}>();
const parent = ref<HTMLDivElement>(), width = ref(0), failure = ref(false), ready = ref(false);
const height = computed(() => Math.ceil((props.model.height + 40) * Math.min(1, Math.max(220, width.value - 40) / props.model.width)) + 40);
const lifetime = new AbortController();
let instance: FlowchartEditor | undefined, observer: ResizeObserver | undefined;
/** @returns Completion of the current, abortable native renderer initialization. */
async function initialize(): Promise<void> {
  try {
    const { createX6FlowchartEditor } = await import("@quirekit/flowchart-x6");
    if (lifetime.signal.aborted || !parent.value) return;
    const fontFamily = getComputedStyle(parent.value).fontFamily;
    const editor = await createX6FlowchartEditor({ container: parent.value, document: props.model.document, locale: props.locale, readOnly: true, signal: lifetime.signal,
      label: props.locale === "en" ? "Markdown source and syntax tree" : "Markdown 原文与语法树",
      appearance: { surface: "var(--qk-color-surface-page)", text: "var(--qk-color-text-primary)", line: "var(--qk-color-border-default)", primary: "var(--qk-color-border-default)", fontFamily,
        runColors: { pending: "var(--qk-color-text-tertiary)", running: "var(--qk-color-warning-solid)", completed: "var(--qk-color-action-primary)", reused: "var(--qk-color-success-solid)", invalid: "var(--qk-color-text-tertiary)" } },
    });
    if (lifetime.signal.aborted) { await editor.destroy(); return; }
    instance = editor; emit("ready", editor); ready.value = true;
  } catch (error) { if (!lifetime.signal.aborted) { failure.value = true; console.error(error); } }
}
onMounted(() => {
  if (!parent.value) return;
  observer = new ResizeObserver(() => { if (parent.value) { width.value = parent.value.clientWidth; emit("compact", width.value < 720); } });
  observer.observe(parent.value); width.value = parent.value.clientWidth; emit("compact", width.value < 720); void initialize();
});
onBeforeUnmount(() => { lifetime.abort(); observer?.disconnect(); if (instance) { emit("retired", instance); void instance.destroy(); } });
</script>
<template>
  <div class="algorithm-flow-host" data-algorithm-flow-canvas :data-flow-ready="ready" :style="{ height: `${height}px` }">
    <div ref="parent" class="algorithm-flow-mount" />
    <p v-if="!ready" class="algorithm-flow-placeholder" role="status">{{ failure ? locale === 'en' ? 'The diagram could not load.' : '流程图未能载入。' : locale === 'en' ? 'Loading the flowchart…' : '正在载入流程图…' }}</p>
  </div>
</template>
<style scoped>
.algorithm-flow-host { position: relative; width: 100%; min-width: 0; border: 1px solid var(--qk-color-border-default); border-radius: 6px; background: var(--qk-color-surface-page); overflow: hidden; }
.algorithm-flow-mount { width: 100%; height: 100%; pointer-events: none; }
.algorithm-flow-placeholder { position: absolute; inset: 32px 0 auto; text-align: center; color: var(--qk-color-text-secondary); }
</style>

```

### Compare the earlier flow

Switch between “Earlier Incremark” and “Current algorithm” to follow a new chunk through the system.
Our existing Flow Editor renderer provides zoom and a larger view.

### algorithms/flow.vue

```vue
<script setup lang="ts">
import { computed, ref } from "vue";
import { QuireButton, QuireSpace, QuireTypography } from "@quirekit/ui/vue";
import type { ExampleLocale } from "../shared/types";
import DiagramCode from "../diagrams/code.vue";
import { algorithmFlow } from "./flow";
const props = withDefaults(defineProps<{ /** Diagram content and interface language. */ locale?: ExampleLocale }>(), { locale: "zh-CN" });
const version = ref<"old" | "current">("current");
const code = computed(() => algorithmFlow(props.locale, version.value));
</script>
<template>
  <div class="p-qk-surface" data-algorithm-flow>
    <QuireSpace gap="inline" wrap>
      <QuireButton size="sm" :variant="version === 'old' ? 'solid' : 'outline'" :aria-pressed="version === 'old'" @click="version = 'old'">{{ locale === 'en' ? 'Earlier Incremark' : '旧 Incremark' }}</QuireButton>
      <QuireButton size="sm" :variant="version === 'current' ? 'solid' : 'outline'" :aria-pressed="version === 'current'" @click="version = 'current'">{{ locale === 'en' ? 'Current algorithm' : '当前算法' }}</QuireButton>
    </QuireSpace>
    <DiagramCode :code="code" language="quirekit-flowchart" :locale="locale" />
    <QuireTypography variant="helper" class="text-qk-text-secondary">{{ locale === 'en' ? 'The two earlier parsers share the same streaming strategy. The current engine checks syntax reuse and rendering reuse separately.' : '旧版两种解析引擎共用会话策略。当前算法分别判断语法能否复用、显示内容能否复用。' }}</QuireTypography>
  </div>
</template>

```

### shared/types.ts

```ts
/** Example host language; independent of the documentation router. */
export type ExampleLocale = "zh-CN" | "en";

```

### diagrams/code.vue

```vue
<script setup lang="ts">
import { computed, ref } from "vue";
import { QuireDialogRoot, QuireDialogTrigger, QuireDialogContent, QuireProseSource, QuireTypography } from "@quirekit/ui/vue";
import type { DocsCodeProps } from "@quirekit/docs-kit/runtime";
import { readDiagram } from "./document";
import DiagramView from "./view.vue";
const props = defineProps<DocsCodeProps & {
  /** Interface language, provided by examples or the documentation route. */ locale?: string;
}>();
const locale = computed(() => props.locale ?? "zh-CN");
const open = ref(false);
const diagram = computed(() => {
  try { return readDiagram(props.language?.trim() ?? "", props.code ?? ""); }
  catch { return null; }
});
</script>
<template>
  <figure class="docs-native-diagram" data-qk-not-prose>
    <template v-if="diagram">
      <figcaption class="docs-diagram-caption">
        <QuireTypography as="span" variant="control">{{ diagram.value.title }}</QuireTypography>
        <QuireDialogRoot v-model:open="open" lazy-mount unmount-on-exit>
          <QuireDialogTrigger class="qk-button qk-button--neutral-text qk-button--sm">{{ locale === 'en' ? 'Full view' : '查看大图' }}</QuireDialogTrigger>
          <QuireDialogContent :title="diagram.value.title" size="large">
            <DiagramView v-if="open" :diagram="diagram" :locale="locale" expanded />
          </QuireDialogContent>
        </QuireDialogRoot>
      </figcaption>
      <DiagramView :diagram="diagram" :locale="locale" />
      <details class="docs-diagram-source"><summary>{{ locale === 'en' ? 'Diagram source' : '查看图稿' }}</summary>
        <QuireProseSource :code="code ?? ''" language="json" :filename="filename" :collapsible="true" :max-height="320" />
      </details>
    </template>
    <template v-else>
      <QuireTypography variant="helper" role="status">{{ locale === 'en' ? 'Invalid or oversized diagram; original source is preserved.' : '图稿格式不完整或超出预览范围，已保留原始代码。' }}</QuireTypography>
      <QuireProseSource :code="code ?? ''" :language="language" :filename="filename" />
    </template>
  </figure>
</template>
<style>
.docs-native-diagram { margin: 16px 0; border: 1px solid var(--qk-color-border-subtle); border-radius: var(--qk-radius-group); overflow: hidden; min-width: 0; }
.docs-diagram-caption { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px; padding: 12px 16px; }
.docs-diagram-source { border-top: 1px solid var(--qk-color-border-subtle); padding: 10px 16px; }
.docs-diagram-source summary { cursor: pointer; font-size: 13px; color: var(--qk-color-text-secondary); }
</style>

```

### diagrams/document.ts

```ts
import { parseFlowchartEmbed } from "@quirekit/flowchart";
import { parseMindMapEmbed } from "@quirekit/mind-map";
import { parseWhiteboard } from "@quirekit/whiteboard";
import type { DiagramDocument } from "./types";

/** @param language - Explicit native fenced format. @param source - Exact JSON body. @returns Validated source; malformed or over-budget input throws. */
export function readDiagram(language: string, source: string): DiagramDocument {
  if (source.length > 4_194_304) throw new RangeError("Diagram preview source exceeds 4,194,304 UTF-16 code units");
  if (language === "quirekit-flowchart") {
    const value = parseFlowchartEmbed(source);
    if (value.document.nodes.length > 200 || value.document.edges.length > 400) throw new RangeError("Diagram preview exceeds 200 shapes or 400 edges");
    return { kind: "flowchart", value };
  }
  if (language === "quirekit-mind-map") {
    const value = parseMindMapEmbed(source);
    let count = 0;
    const pending = [value.document.root, ...(value.document.floatingTopics?.map(topic => topic.root) ?? [])];
    while (pending.length) {
      if (++count > 500) throw new RangeError("Diagram preview exceeds 500 topics");
      pending.push(...pending.pop()!.children);
    }
    return { kind: "mind-map", value };
  }
  if (language === "quirekit-whiteboard") {
    const value = parseWhiteboard(source);
    if (value.scene.elements.length > 500) throw new RangeError("Diagram preview exceeds 500 elements");
    return { kind: "whiteboard", value };
  }
  throw new TypeError("Unsupported diagram language");
}

```

### diagrams/types.ts

```ts
import type { FlowchartEmbed } from "@quirekit/flowchart";
import type { MindMapEmbed } from "@quirekit/mind-map";
import type { WhiteboardDocument } from "@quirekit/whiteboard";

/** Canonical source admitted by its own domain validator. */
export type DiagramDocument = { kind: "flowchart"; value: FlowchartEmbed }
  | { kind: "mind-map"; value: MindMapEmbed } | { kind: "whiteboard"; value: WhiteboardDocument };

/** Viewport-only public adapter; source operations are deliberately absent. */
export interface DiagramView {
  /** @returns Completion of fitting the immutable source to the host. */ fit(): void | Promise<void>;
  /** @param factor - Relative zoom multiplier; source is never read. */ zoom(factor: number): void;
  /** @returns Completion of adapter resource release. */ destroy(): void | Promise<void>;
}

```

### diagrams/view.vue

```vue
<script setup lang="ts">
import { onMounted, onBeforeUnmount, ref, shallowRef, watchEffect } from "vue";
import { QuireButton, QuireSpace, QuireTypography } from "@quirekit/ui/vue";
import type { DiagramDocument, DiagramView } from "./types";
import { mountDiagram } from "./mount";
const props = defineProps<{
  /** Validated immutable source. */ diagram: DiagramDocument;
  /** Interface language. */ locale?: string;
  /** Larger viewport inside the full-view dialog. */ expanded?: boolean;
}>();
const parent = shallowRef<HTMLElement>();
const view = shallowRef<DiagramView>();
const active = ref(false), ready = ref(false), failed = ref(false);
let visibility: IntersectionObserver | undefined;
onMounted(() => {
  if (!parent.value || typeof IntersectionObserver === "undefined") { active.value = true; return; }
  visibility = new IntersectionObserver(entries => {
    if (entries.some(entry => entry.isIntersecting)) { active.value = true; visibility?.disconnect(); }
  }, { rootMargin: "160px" });
  visibility.observe(parent.value);
});
onBeforeUnmount(() => visibility?.disconnect());
watchEffect(onCleanup => {
  if (!active.value || !parent.value) return;
  const source = props.diagram, locale = props.locale ?? "zh-CN", host = parent.value;
  const lifetime = new AbortController();
  // Every source revision owns its mount, including any asynchronous native layout.
  const mount = host.ownerDocument.createElement("div");
  mount.className = "docs-diagram-mount";
  host.append(mount);
  ready.value = false;
  failed.value = false;
  let instance: DiagramView | undefined;

  /** Shows failure only while this source revision still owns the view. */
  function showFailure(): void {
    if (lifetime.signal.aborted) return;
    ready.value = false;
    failed.value = true;
  }

  const resize = new ResizeObserver(() => {
    if (ready.value && instance) void Promise.resolve().then(() => instance?.fit()).catch(showFailure);
  });
  resize.observe(host);
  onCleanup(() => {
    lifetime.abort();
    resize.disconnect();
    view.value = undefined;
    ready.value = false;
    void instance?.destroy();
    mount.remove();
  });

  void mountDiagram(source, mount, locale, lifetime.signal).then(async result => {
    // A superseded loader can only own its detached mount, never the next graph's host.
    if (lifetime.signal.aborted) { await result.destroy(); return; }
    instance = result;
    view.value = result;
    await result.fit();
    if (!lifetime.signal.aborted) ready.value = true;
  }).catch(showFailure);
});
/** @param factor - Relative zoom or zero to fit the current viewport. @returns Completion without publishing a failure from an old view. */
async function navigate(factor: number): Promise<void> {
  const current = view.value;
  if (!current) return;
  try { if (factor) current.zoom(factor); else await current.fit(); }
  catch { if (view.value === current) { ready.value = false; failed.value = true; } }
}
</script>
<template>
  <div class="docs-diagram-view" :data-diagram-kind="diagram.kind" :data-diagram-ready="ready || undefined" data-qk-not-prose>
    <QuireSpace justify="end" gap="inline" class="docs-diagram-navigation">
      <QuireButton size="sm" variant="text" :disabled="!ready" :aria-label="locale === 'en' ? 'Zoom out' : '缩小'" @click="navigate(0.8)">−</QuireButton>
      <QuireButton size="sm" variant="text" :disabled="!ready" :aria-label="locale === 'en' ? 'Zoom in' : '放大'" @click="navigate(1.25)">+</QuireButton>
      <QuireButton size="sm" variant="text" :disabled="!ready" @click="navigate(0)">{{ locale === 'en' ? 'Fit' : '适应画布' }}</QuireButton>
    </QuireSpace>
    <div ref="parent" class="docs-diagram-canvas" :class="{ 'docs-diagram-canvas--expanded': expanded }" :aria-label="diagram.value.title" />
    <QuireTypography v-if="!ready" variant="helper" role="status" class="docs-diagram-status">
      {{ failed ? (locale === 'en' ? 'The diagram could not load. Its source is available below.' : '图表加载失败，可以查看原始图稿。') : (locale === 'en' ? 'Preparing diagram…' : '正在准备图表…') }}
    </QuireTypography>
  </div>
</template>
<style>
.docs-diagram-view { min-width: 0; position: relative; }
.docs-diagram-navigation { padding: 4px 8px; background: var(--qk-color-surface-subtle); }
.docs-diagram-canvas { height: 400px; position: relative; background: #ffffff; color: #243247; overflow: hidden; }
.docs-diagram-canvas--expanded { height: min(68vh, 740px); }
.docs-diagram-mount { position: absolute; inset: 0; }
.docs-diagram-status { position: absolute; inset: 46% 16px auto; text-align: center; color: #475569; pointer-events: none; }
.docs-diagram-canvas .excalidraw { --color-primary: #2563eb; }
@media (max-width: 600px) { .docs-diagram-canvas { height: 340px; } .docs-diagram-canvas--expanded { height: 62vh; } }
</style>

```

### diagrams/mount.ts

```ts
import type { DiagramDocument, DiagramView } from "./types";
import { whiteboardView } from "./whiteboard";

/** @param source - Validated immutable diagram. @param container - Instance-owned, connected mount. @param locale - Interface language. @param signal - Initialization and mount lifetime. @returns A viewport-only adapter over the existing editor. */
export async function mountDiagram(source: DiagramDocument, container: HTMLElement, locale: string, signal: AbortSignal): Promise<DiagramView> {
  // Source semantics remain in their domain; only client resources and viewport controls live here.
  signal.throwIfAborted();
  if (source.kind === "flowchart") {
    const { createX6FlowchartEditor } = await import("@quirekit/flowchart-x6");
    signal.throwIfAborted();
    const editor = await createX6FlowchartEditor({ container, document: source.value.document, locale, signal, readOnly: true, label: source.value.title });
    return { fit: () => editor.fit(), zoom: factor => editor.setZoom(editor.getState().scale * factor), destroy: () => editor.destroy() };
  }
  if (source.kind === "mind-map") {
    const { createSimpleMindMapEditor } = await import("@quirekit/mind-map-simple");
    signal.throwIfAborted();
    const editor = await createSimpleMindMapEditor({ container, document: source.value.document, locale, signal, readOnly: true, label: source.value.title });
    return { fit: () => editor.fit(), zoom: factor => editor.setZoom(editor.getState().scale * factor), destroy: () => editor.destroy() };
  }
  const { createExcalidrawWhiteboard } = await import("@quirekit/whiteboard-excalidraw");
  signal.throwIfAborted();
  const editor = await createExcalidrawWhiteboard({ container, document: source.value, locale, signal, readOnly: true, theme: "light" });
  return whiteboardView(editor, container.ownerDocument.defaultView!, signal);
}

```

### diagrams/whiteboard.ts

```ts
import type { WhiteboardEditor } from "@quirekit/whiteboard-excalidraw";
import type { DiagramView } from "./types";

/**
 * Waits for a native viewport commit, with cancellation on detached mounts.
 * @param window - Owning browsing context.
 * @param signal - Diagram lifetime.
 * @returns Completion of the next animation frame.
 */
function viewportFrame(window: Window, signal: AbortSignal): Promise<void> {
  signal.throwIfAborted();
  return new Promise((resolve, reject) => {
    /** Cancels the pending frame when the owning view closes. */
    function abort(): void {
      window.cancelAnimationFrame(frame);
      reject(signal.reason);
    }
    const frame = window.requestAnimationFrame(() => {
      signal.removeEventListener("abort", abort);
      resolve();
    });
    signal.addEventListener("abort", abort, { once: true });
  });
}

/**
 * Reserves space for native read-only chrome without serializing the scene.
 * @param editor - Existing public Excalidraw adapter.
 * @param window - Owning browsing context.
 * @param signal - Diagram lifetime.
 * @returns Coalesced viewport controls and the original disposal boundary.
 */
export function whiteboardView(editor: WhiteboardEditor, window: Window, signal: AbortSignal): DiagramView {
  let fitting: Promise<void> | undefined;

  /** @returns Completion after initial scene restore and the fitted viewport commit. */
  async function fitViewport(): Promise<void> {
    await viewportFrame(window, signal);
    editor.fit();
    await viewportFrame(window, signal);
    // Native fitting uses the full host; reserve the footer's space in this reader.
    editor.zoom(0.8);
  }

  /** @returns One shared fit operation while the native viewport is settling. */
  function fit(): Promise<void> {
    return fitting ??= fitViewport().finally(() => { fitting = undefined; });
  }

  return { fit, zoom: factor => editor.zoom(factor), destroy: () => editor.destroy() };
}

```

### algorithms/flow.ts

```ts
import { createFlowchart, createFlowchartEmbed, createFlowchartNode, readFlowchart, serializeFlowchartEmbed } from "@quirekit/flowchart";
import type { FlowchartEdge, FlowchartNode, FlowchartPort, FlowchartShape } from "@quirekit/flowchart";
import type { ExampleLocale } from "../shared/types";

/** @param id - Stable graph identity. @param x - World left. @param y - World top. @param text - Literal caption. @param shape - Native semantic shape. @returns A readable, validated graph node. */
function node(id: string, x: number, y: number, text: string, shape: FlowchartShape = "process"): FlowchartNode {
  return { ...createFlowchartNode(id, shape, { x, y }, text), width: 220, height: 110,
    style: { fontSize: 17, padding: 8, fill: "#f8fafc", stroke: "#94a3b8", color: "#1e293b" } };
}
/** @param from - Source identity. @param to - Target identity. @param label - Literal relation label. @param sourcePort - Source side. @param targetPort - Target side. @param vertices - Explicit world bends avoiding intervening nodes. @returns A canonical connector, independent of the renderer. */
function edge(from: string, to: string, label = "", sourcePort: FlowchartPort = "right", targetPort: FlowchartPort = "left", vertices: readonly { x: number; y: number }[] = []): FlowchartEdge {
  return { id: `${from}-${to}`, source: { node: from, port: sourcePort }, target: { node: to, port: targetPort },
    routing: "straight", connector: "rounded", vertices, label, labelPosition: 0.5, style: { stroke: "#64748b", width: 2 } };
}

/** @param locale - Literal diagram language. @param version - Earlier session or current implementation. @returns Portable graph source for the existing Flow Editor renderer. */
export function algorithmFlow(locale: ExampleLocale, version: "old" | "current"): string {
  const zh = locale === "zh-CN";
  const title = version === "old" ? (zh ? "旧 Incremark：稳定块 + 尾部解析" : "Earlier Incremark: blocks and tail")
    : (zh ? "当前算法：语法复用 + 依赖校验" : "Current algorithm: reuse and validation");
  const nodes = version === "old" ? [
    node("receive", 0, 0, zh ? "收到新片段" : "Receive a chunk", "terminator"),
    node("split", 260, 0, zh ? "划分稳定块\n与未完成尾部" : "Find stable blocks\nand unfinished tail"),
    node("keep", 520, 0, zh ? "保留已有块\n及其解析节点" : "Retain earlier blocks\nand parsed nodes"),
    node("tail", 260, 160, zh ? "Marked / Micromark\n解析尾部" : "Marked / Micromark\nparse the tail"),
    node("append", 520, 160, zh ? "合并新节点\n和引用定义" : "Merge new nodes\nand definitions"),
    node("patch", 520, 320, zh ? "定义到达后\n补已有节点的地址" : "Fill destinations\non existing nodes"),
    { ...node("risk", 0, 320, zh ? "风险：补地址无法\n修正旧的链接分组" : "Risk: filling a URL\ncannot fix grouping", "note"), style: { fill: "#fff1f2", stroke: "#fb7185", color: "#9f1239", fontSize: 17 } },
  ] : [
    node("receive", 0, 0, zh ? "追加真实源码\n建立文本事务" : "Append actual source\nmap text changes", "terminator"),
    node("structure", 260, 0, zh ? "Lezer 结构解析\n复用有效片段" : "Lezer structural pass\nreuse valid fragments"),
    node("definitions", 520, 0, zh ? "收集当前定义\n同名取首次定义" : "Collect definitions\nfirst definition wins"),
    node("syntax-check", 520, 160, zh ? "引用分组\n受到影响？" : "Reference grouping\naffected?", "decision"),
    node("refine", 260, 160, zh ? "重判受影响段落\n保留无关语法片段" : "Refine affected blocks\nretain other fragments"),
    node("accept", 0, 160, zh ? "接受语义一致\n的原生语法树" : "Accept the native tree\nwith correct semantics"),
    node("render-check", 0, 350, zh ? "缓存条件\n都满足？" : "Cache inputs\nunchanged?", "decision"),
    { ...node("reuse", 260, 350, zh ? "复用显示内容\n保留组件状态" : "Reuse display output\nretain component state"), style: { fill: "#eff6ff", stroke: "#60a5fa", color: "#1e40af", fontSize: 17 } },
    { ...node("rebuild", 520, 350, zh ? "重新生成受影响块\n更新链接、脚注等" : "Project affected blocks\nupdate links and notes"), style: { fill: "#fffbeb", stroke: "#fbbf24", color: "#92400e", fontSize: 17 } },
  ];
  const edges = version === "old" ? [edge("receive", "split"), edge("split", "keep"), edge("split", "tail", "", "bottom", "top"),
    edge("tail", "append"), edge("keep", "append", "", "bottom", "top"), edge("append", "patch", "", "bottom", "top"), edge("patch", "risk", "", "left", "right")]
    : [edge("receive", "structure"), edge("structure", "definitions"), edge("definitions", "syntax-check", "", "bottom", "top"),
      edge("syntax-check", "refine", zh ? "是" : "Yes", "left", "right"), edge("refine", "accept", "", "left", "right"),
      edge("syntax-check", "accept", zh ? "否" : "No", "bottom", "bottom", [{ x: 630, y: 305 }, { x: 110, y: 305 }]),
      edge("accept", "render-check", "", "bottom", "top"), edge("render-check", "reuse", zh ? "是" : "Yes"),
      edge("render-check", "rebuild", zh ? "否 / 无法证明" : "No / unproven", "bottom", "bottom", [{ x: 110, y: 510 }, { x: 630, y: 510 }])];
  // The domain validator owns IDs, geometry and source encoding; the example only describes the algorithm.
  const document = readFlowchart({ ...createFlowchart(), nodes, edges });
  return serializeFlowchartEmbed({ ...createFlowchartEmbed(`algorithm-${version}`, title), document });
}

```

## Why redesign it

Earlier Incremark divided a growing response into finished blocks and the part still being written.
When more text arrived, it tried to preserve earlier parse results and reparse only the tail.
That reduced repeated work.

But finishing a paragraph does not always settle its meaning. An AI can write the text first and
provide a link destination at the end:

```md
更多内容见[文档][api]。

[api]: https://example.com/docs
```

Until the last line arrives, `[文档][api]` has no destination. Once it arrives, “文档” should become
a link. Footnotes work similarly: a reference can appear long before its explanation.

The earlier version handled these cases by recording references and filling in their destinations later.
The difficulty is that a later definition can also change **which brackets form a link**. Adding a
destination to an existing node is then insufficient. Earlier syntax needs another look, and the page
needs the corresponding update.

## Two concrete problems in the earlier implementation

### Defining the same link twice

Suppose the paragraph and each destination arrive in order:

```md
[文档][api]

[api]: /first

[api]: /second
```

Markdown uses the first definition of a label, so “文档” should link to `/first`.
The earlier implementation overwrote the first value when merging definitions, changing the link
to `/second`. The current engine preserves the first definition.

### Linking the wrong word

Consider another short example. The first line arrives, followed by the definition for `baz`:

```md
[foo][bar][baz]

[baz]: /target
```

The intended reading is literal `[foo]`, followed by the link `[bar][baz]`. The clickable word is **bar**.

The earlier version had already recognized `[foo][bar]` as one reference and left `[baz]` as another.
When the destination arrived, **baz** became clickable. Filling in the address could not correct the
earlier grouping. The current engine checks grouping against the definitions that actually arrived,
so **bar** becomes the link.

We ran both earlier engines on these examples again:

| Case | Earlier Marked | Earlier Micromark | Current QuireKit Markdown |
| --- | --- | --- | --- |
| Define `api` twice | Links to `/second` | Links to `/second` | Links to `/first` |
| Three adjacent bracket groups | baz is clickable | baz is clickable | bar is clickable |

## How the new algorithm handles a stream

### 1. Keep the source that arrived

Every append updates the same Markdown source. Stopping preserves the received text without adding
fences, brackets or HTML closing tags for display. Switching messages starts a new parsing session.

### 2. Let Lezer identify reusable parse results

Lezer records headings, paragraphs, lists and links in a syntax tree. When text arrives, it uses
syntax context to decide which earlier results remain usable and which need parsing again.
We use that mechanism for streamed appends.

For example, starting a paragraph at the end can leave unaffected earlier paragraphs reusable.
Changes to lists or fences are handled according to their syntax, rather than treating a blank line
alone as proof that earlier content is finished.

### 3. Let later definitions correct earlier references

The new engine collects the current link definitions, then checks which paragraphs' reference
decisions were affected. `[foo][bar][baz]` needs a new grouping decision; repeated definitions
keep the first value.

It also records labels a paragraph queried, including those not yet defined. When those records
provide enough evidence that a new definition did not affect the paragraph, its result remains reusable.

This whiteboard shows the dependencies: when `api` arrives, paragraph B needs another check.
An introduction and a paragraph that only queried `faq` can remain reusable when their other inputs match.

### algorithms/references.vue

```vue
<script setup lang="ts">
import { computed } from "vue";
import type { ExampleLocale } from "../shared/types";
import DiagramCode from "../diagrams/code.vue";
import { algorithmReferences } from "./references";
const props = withDefaults(defineProps<{ /** Diagram language. */ locale?: ExampleLocale }>(), { locale: "zh-CN" });
const code = computed(() => algorithmReferences(props.locale));
</script>
<template><DiagramCode :code="code" language="quirekit-whiteboard" :locale="locale" /></template>

```

### shared/types.ts

```ts
/** Example host language; independent of the documentation router. */
export type ExampleLocale = "zh-CN" | "en";

```

### diagrams/code.vue

```vue
<script setup lang="ts">
import { computed, ref } from "vue";
import { QuireDialogRoot, QuireDialogTrigger, QuireDialogContent, QuireProseSource, QuireTypography } from "@quirekit/ui/vue";
import type { DocsCodeProps } from "@quirekit/docs-kit/runtime";
import { readDiagram } from "./document";
import DiagramView from "./view.vue";
const props = defineProps<DocsCodeProps & {
  /** Interface language, provided by examples or the documentation route. */ locale?: string;
}>();
const locale = computed(() => props.locale ?? "zh-CN");
const open = ref(false);
const diagram = computed(() => {
  try { return readDiagram(props.language?.trim() ?? "", props.code ?? ""); }
  catch { return null; }
});
</script>
<template>
  <figure class="docs-native-diagram" data-qk-not-prose>
    <template v-if="diagram">
      <figcaption class="docs-diagram-caption">
        <QuireTypography as="span" variant="control">{{ diagram.value.title }}</QuireTypography>
        <QuireDialogRoot v-model:open="open" lazy-mount unmount-on-exit>
          <QuireDialogTrigger class="qk-button qk-button--neutral-text qk-button--sm">{{ locale === 'en' ? 'Full view' : '查看大图' }}</QuireDialogTrigger>
          <QuireDialogContent :title="diagram.value.title" size="large">
            <DiagramView v-if="open" :diagram="diagram" :locale="locale" expanded />
          </QuireDialogContent>
        </QuireDialogRoot>
      </figcaption>
      <DiagramView :diagram="diagram" :locale="locale" />
      <details class="docs-diagram-source"><summary>{{ locale === 'en' ? 'Diagram source' : '查看图稿' }}</summary>
        <QuireProseSource :code="code ?? ''" language="json" :filename="filename" :collapsible="true" :max-height="320" />
      </details>
    </template>
    <template v-else>
      <QuireTypography variant="helper" role="status">{{ locale === 'en' ? 'Invalid or oversized diagram; original source is preserved.' : '图稿格式不完整或超出预览范围，已保留原始代码。' }}</QuireTypography>
      <QuireProseSource :code="code ?? ''" :language="language" :filename="filename" />
    </template>
  </figure>
</template>
<style>
.docs-native-diagram { margin: 16px 0; border: 1px solid var(--qk-color-border-subtle); border-radius: var(--qk-radius-group); overflow: hidden; min-width: 0; }
.docs-diagram-caption { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px; padding: 12px 16px; }
.docs-diagram-source { border-top: 1px solid var(--qk-color-border-subtle); padding: 10px 16px; }
.docs-diagram-source summary { cursor: pointer; font-size: 13px; color: var(--qk-color-text-secondary); }
</style>

```

### diagrams/document.ts

```ts
import { parseFlowchartEmbed } from "@quirekit/flowchart";
import { parseMindMapEmbed } from "@quirekit/mind-map";
import { parseWhiteboard } from "@quirekit/whiteboard";
import type { DiagramDocument } from "./types";

/** @param language - Explicit native fenced format. @param source - Exact JSON body. @returns Validated source; malformed or over-budget input throws. */
export function readDiagram(language: string, source: string): DiagramDocument {
  if (source.length > 4_194_304) throw new RangeError("Diagram preview source exceeds 4,194,304 UTF-16 code units");
  if (language === "quirekit-flowchart") {
    const value = parseFlowchartEmbed(source);
    if (value.document.nodes.length > 200 || value.document.edges.length > 400) throw new RangeError("Diagram preview exceeds 200 shapes or 400 edges");
    return { kind: "flowchart", value };
  }
  if (language === "quirekit-mind-map") {
    const value = parseMindMapEmbed(source);
    let count = 0;
    const pending = [value.document.root, ...(value.document.floatingTopics?.map(topic => topic.root) ?? [])];
    while (pending.length) {
      if (++count > 500) throw new RangeError("Diagram preview exceeds 500 topics");
      pending.push(...pending.pop()!.children);
    }
    return { kind: "mind-map", value };
  }
  if (language === "quirekit-whiteboard") {
    const value = parseWhiteboard(source);
    if (value.scene.elements.length > 500) throw new RangeError("Diagram preview exceeds 500 elements");
    return { kind: "whiteboard", value };
  }
  throw new TypeError("Unsupported diagram language");
}

```

### diagrams/types.ts

```ts
import type { FlowchartEmbed } from "@quirekit/flowchart";
import type { MindMapEmbed } from "@quirekit/mind-map";
import type { WhiteboardDocument } from "@quirekit/whiteboard";

/** Canonical source admitted by its own domain validator. */
export type DiagramDocument = { kind: "flowchart"; value: FlowchartEmbed }
  | { kind: "mind-map"; value: MindMapEmbed } | { kind: "whiteboard"; value: WhiteboardDocument };

/** Viewport-only public adapter; source operations are deliberately absent. */
export interface DiagramView {
  /** @returns Completion of fitting the immutable source to the host. */ fit(): void | Promise<void>;
  /** @param factor - Relative zoom multiplier; source is never read. */ zoom(factor: number): void;
  /** @returns Completion of adapter resource release. */ destroy(): void | Promise<void>;
}

```

### diagrams/view.vue

```vue
<script setup lang="ts">
import { onMounted, onBeforeUnmount, ref, shallowRef, watchEffect } from "vue";
import { QuireButton, QuireSpace, QuireTypography } from "@quirekit/ui/vue";
import type { DiagramDocument, DiagramView } from "./types";
import { mountDiagram } from "./mount";
const props = defineProps<{
  /** Validated immutable source. */ diagram: DiagramDocument;
  /** Interface language. */ locale?: string;
  /** Larger viewport inside the full-view dialog. */ expanded?: boolean;
}>();
const parent = shallowRef<HTMLElement>();
const view = shallowRef<DiagramView>();
const active = ref(false), ready = ref(false), failed = ref(false);
let visibility: IntersectionObserver | undefined;
onMounted(() => {
  if (!parent.value || typeof IntersectionObserver === "undefined") { active.value = true; return; }
  visibility = new IntersectionObserver(entries => {
    if (entries.some(entry => entry.isIntersecting)) { active.value = true; visibility?.disconnect(); }
  }, { rootMargin: "160px" });
  visibility.observe(parent.value);
});
onBeforeUnmount(() => visibility?.disconnect());
watchEffect(onCleanup => {
  if (!active.value || !parent.value) return;
  const source = props.diagram, locale = props.locale ?? "zh-CN", host = parent.value;
  const lifetime = new AbortController();
  // Every source revision owns its mount, including any asynchronous native layout.
  const mount = host.ownerDocument.createElement("div");
  mount.className = "docs-diagram-mount";
  host.append(mount);
  ready.value = false;
  failed.value = false;
  let instance: DiagramView | undefined;

  /** Shows failure only while this source revision still owns the view. */
  function showFailure(): void {
    if (lifetime.signal.aborted) return;
    ready.value = false;
    failed.value = true;
  }

  const resize = new ResizeObserver(() => {
    if (ready.value && instance) void Promise.resolve().then(() => instance?.fit()).catch(showFailure);
  });
  resize.observe(host);
  onCleanup(() => {
    lifetime.abort();
    resize.disconnect();
    view.value = undefined;
    ready.value = false;
    void instance?.destroy();
    mount.remove();
  });

  void mountDiagram(source, mount, locale, lifetime.signal).then(async result => {
    // A superseded loader can only own its detached mount, never the next graph's host.
    if (lifetime.signal.aborted) { await result.destroy(); return; }
    instance = result;
    view.value = result;
    await result.fit();
    if (!lifetime.signal.aborted) ready.value = true;
  }).catch(showFailure);
});
/** @param factor - Relative zoom or zero to fit the current viewport. @returns Completion without publishing a failure from an old view. */
async function navigate(factor: number): Promise<void> {
  const current = view.value;
  if (!current) return;
  try { if (factor) current.zoom(factor); else await current.fit(); }
  catch { if (view.value === current) { ready.value = false; failed.value = true; } }
}
</script>
<template>
  <div class="docs-diagram-view" :data-diagram-kind="diagram.kind" :data-diagram-ready="ready || undefined" data-qk-not-prose>
    <QuireSpace justify="end" gap="inline" class="docs-diagram-navigation">
      <QuireButton size="sm" variant="text" :disabled="!ready" :aria-label="locale === 'en' ? 'Zoom out' : '缩小'" @click="navigate(0.8)">−</QuireButton>
      <QuireButton size="sm" variant="text" :disabled="!ready" :aria-label="locale === 'en' ? 'Zoom in' : '放大'" @click="navigate(1.25)">+</QuireButton>
      <QuireButton size="sm" variant="text" :disabled="!ready" @click="navigate(0)">{{ locale === 'en' ? 'Fit' : '适应画布' }}</QuireButton>
    </QuireSpace>
    <div ref="parent" class="docs-diagram-canvas" :class="{ 'docs-diagram-canvas--expanded': expanded }" :aria-label="diagram.value.title" />
    <QuireTypography v-if="!ready" variant="helper" role="status" class="docs-diagram-status">
      {{ failed ? (locale === 'en' ? 'The diagram could not load. Its source is available below.' : '图表加载失败，可以查看原始图稿。') : (locale === 'en' ? 'Preparing diagram…' : '正在准备图表…') }}
    </QuireTypography>
  </div>
</template>
<style>
.docs-diagram-view { min-width: 0; position: relative; }
.docs-diagram-navigation { padding: 4px 8px; background: var(--qk-color-surface-subtle); }
.docs-diagram-canvas { height: 400px; position: relative; background: #ffffff; color: #243247; overflow: hidden; }
.docs-diagram-canvas--expanded { height: min(68vh, 740px); }
.docs-diagram-mount { position: absolute; inset: 0; }
.docs-diagram-status { position: absolute; inset: 46% 16px auto; text-align: center; color: #475569; pointer-events: none; }
.docs-diagram-canvas .excalidraw { --color-primary: #2563eb; }
@media (max-width: 600px) { .docs-diagram-canvas { height: 340px; } .docs-diagram-canvas--expanded { height: 62vh; } }
</style>

```

### diagrams/mount.ts

```ts
import type { DiagramDocument, DiagramView } from "./types";
import { whiteboardView } from "./whiteboard";

/** @param source - Validated immutable diagram. @param container - Instance-owned, connected mount. @param locale - Interface language. @param signal - Initialization and mount lifetime. @returns A viewport-only adapter over the existing editor. */
export async function mountDiagram(source: DiagramDocument, container: HTMLElement, locale: string, signal: AbortSignal): Promise<DiagramView> {
  // Source semantics remain in their domain; only client resources and viewport controls live here.
  signal.throwIfAborted();
  if (source.kind === "flowchart") {
    const { createX6FlowchartEditor } = await import("@quirekit/flowchart-x6");
    signal.throwIfAborted();
    const editor = await createX6FlowchartEditor({ container, document: source.value.document, locale, signal, readOnly: true, label: source.value.title });
    return { fit: () => editor.fit(), zoom: factor => editor.setZoom(editor.getState().scale * factor), destroy: () => editor.destroy() };
  }
  if (source.kind === "mind-map") {
    const { createSimpleMindMapEditor } = await import("@quirekit/mind-map-simple");
    signal.throwIfAborted();
    const editor = await createSimpleMindMapEditor({ container, document: source.value.document, locale, signal, readOnly: true, label: source.value.title });
    return { fit: () => editor.fit(), zoom: factor => editor.setZoom(editor.getState().scale * factor), destroy: () => editor.destroy() };
  }
  const { createExcalidrawWhiteboard } = await import("@quirekit/whiteboard-excalidraw");
  signal.throwIfAborted();
  const editor = await createExcalidrawWhiteboard({ container, document: source.value, locale, signal, readOnly: true, theme: "light" });
  return whiteboardView(editor, container.ownerDocument.defaultView!, signal);
}

```

### diagrams/whiteboard.ts

```ts
import type { WhiteboardEditor } from "@quirekit/whiteboard-excalidraw";
import type { DiagramView } from "./types";

/**
 * Waits for a native viewport commit, with cancellation on detached mounts.
 * @param window - Owning browsing context.
 * @param signal - Diagram lifetime.
 * @returns Completion of the next animation frame.
 */
function viewportFrame(window: Window, signal: AbortSignal): Promise<void> {
  signal.throwIfAborted();
  return new Promise((resolve, reject) => {
    /** Cancels the pending frame when the owning view closes. */
    function abort(): void {
      window.cancelAnimationFrame(frame);
      reject(signal.reason);
    }
    const frame = window.requestAnimationFrame(() => {
      signal.removeEventListener("abort", abort);
      resolve();
    });
    signal.addEventListener("abort", abort, { once: true });
  });
}

/**
 * Reserves space for native read-only chrome without serializing the scene.
 * @param editor - Existing public Excalidraw adapter.
 * @param window - Owning browsing context.
 * @param signal - Diagram lifetime.
 * @returns Coalesced viewport controls and the original disposal boundary.
 */
export function whiteboardView(editor: WhiteboardEditor, window: Window, signal: AbortSignal): DiagramView {
  let fitting: Promise<void> | undefined;

  /** @returns Completion after initial scene restore and the fitted viewport commit. */
  async function fitViewport(): Promise<void> {
    await viewportFrame(window, signal);
    editor.fit();
    await viewportFrame(window, signal);
    // Native fitting uses the full host; reserve the footer's space in this reader.
    editor.zoom(0.8);
  }

  /** @returns One shared fit operation while the native viewport is settling. */
  function fit(): Promise<void> {
    return fitting ??= fitViewport().finally(() => { fitting = undefined; });
  }

  return { fit, zoom: factor => editor.zoom(factor), destroy: () => editor.destroy() };
}

```

### algorithms/references.ts

```ts
import { createWhiteboardDocument, editWhiteboardScene, serializeWhiteboard } from "@quirekit/whiteboard";
import type { ExampleLocale } from "../shared/types";

/** @param locale - Literal diagram language. @returns A native whiteboard describing the dependency from a late definition to earlier text. */
export function algorithmReferences(locale: ExampleLocale): string {
  const zh = locale === "zh-CN", board = createWhiteboardDocument("algorithm-references", zh ? "后置定义：谁需要更新？" : "Late definitions: which blocks change?");
  // Semantic shapes and bindings are authored through the shared API; Excalidraw owns rendering.
  const scene = editWhiteboardScene(board.scene, { nodes: [
    { id: "plain", x: 0, y: 0, width: 300, height: 84, label: zh ? "段落 A：普通介绍\n无引用 → 继续复用" : "A: ordinary introduction\nNo references → reuse", fill: "#eff6ff", color: "#1e40af", fontSize: 19 },
    { id: "api-user", x: 0, y: 160, width: 300, height: 96, label: zh ? "段落 B：[文档][api]\n查询过 api → 重新检查" : "B: [Documentation][api]\nQueried api → recheck", fill: "#fffbeb", color: "#92400e", fontSize: 19 },
    { id: "faq-user", x: 0, y: 330, width: 300, height: 96, label: zh ? "段落 C：[帮助][faq]\n只查询 faq → 继续复用" : "C: [Help][faq]\nOnly queried faq → reuse", fill: "#eff6ff", color: "#1e40af", fontSize: 19 },
    { id: "late", x: 450, y: 160, width: 280, height: 96, label: zh ? "最后收到的定义\n[api]: /docs" : "Definition arrives last\n[api]: /docs", fill: "#f0fdf4", color: "#166534", fontSize: 19 },
    { id: "update", x: 450, y: 0, width: 280, height: 84, label: zh ? "api 从不存在变为存在\n重判 B，再更新链接" : "api now exists\nRefine B, then update its link", fill: "#fffbeb", color: "#92400e", fontSize: 19 },
    { id: "limit", shape: "text", x: 450, y: 345, width: 310, height: 70, label: zh ? "无法证明不受影响时，重新处理。\n依赖检查本身仍有开销。" : "Unproven reuse → process again.\nDependency checks still cost work.", color: "#475569", fontSize: 17 },
  ], edges: [
    { id: "late-to-user", from: "late", to: "api-user", label: zh ? "api 依赖" : "api dependency", color: "#a16207" },
    { id: "late-to-update", from: "late", to: "update", color: "#a16207" },
  ] });
  return serializeWhiteboard({ ...board, scene });
}

```

### 4. Check displayed content before reusing it

Unchanged syntax can still have a different link destination or footnote number. The rendering cache
checks those inputs too, along with components and HTML configuration. Matching entries retain their
output; affected blocks, or blocks without enough evidence, are generated again.

As the AI continues writing, reusable earlier content stays in place. When a later definition changes
its meaning, earlier links and footnotes can still update.

### Who owns these steps

The core owns parsing, reference decisions and cache validation. Vue and React own their components
and commit lifecycle; the application owns network delivery and optional diagram renderers.

### algorithms/layers.vue

```vue
<script setup lang="ts">
import { computed } from "vue";
import type { ExampleLocale } from "../shared/types";
import DiagramCode from "../diagrams/code.vue";
import { algorithmLayers } from "./layers";
const props = withDefaults(defineProps<{ /** Diagram language. */ locale?: ExampleLocale }>(), { locale: "zh-CN" });
const code = computed(() => algorithmLayers(props.locale));
</script>
<template><DiagramCode :code="code" language="quirekit-mind-map" :locale="locale" /></template>

```

### shared/types.ts

```ts
/** Example host language; independent of the documentation router. */
export type ExampleLocale = "zh-CN" | "en";

```

### diagrams/code.vue

```vue
<script setup lang="ts">
import { computed, ref } from "vue";
import { QuireDialogRoot, QuireDialogTrigger, QuireDialogContent, QuireProseSource, QuireTypography } from "@quirekit/ui/vue";
import type { DocsCodeProps } from "@quirekit/docs-kit/runtime";
import { readDiagram } from "./document";
import DiagramView from "./view.vue";
const props = defineProps<DocsCodeProps & {
  /** Interface language, provided by examples or the documentation route. */ locale?: string;
}>();
const locale = computed(() => props.locale ?? "zh-CN");
const open = ref(false);
const diagram = computed(() => {
  try { return readDiagram(props.language?.trim() ?? "", props.code ?? ""); }
  catch { return null; }
});
</script>
<template>
  <figure class="docs-native-diagram" data-qk-not-prose>
    <template v-if="diagram">
      <figcaption class="docs-diagram-caption">
        <QuireTypography as="span" variant="control">{{ diagram.value.title }}</QuireTypography>
        <QuireDialogRoot v-model:open="open" lazy-mount unmount-on-exit>
          <QuireDialogTrigger class="qk-button qk-button--neutral-text qk-button--sm">{{ locale === 'en' ? 'Full view' : '查看大图' }}</QuireDialogTrigger>
          <QuireDialogContent :title="diagram.value.title" size="large">
            <DiagramView v-if="open" :diagram="diagram" :locale="locale" expanded />
          </QuireDialogContent>
        </QuireDialogRoot>
      </figcaption>
      <DiagramView :diagram="diagram" :locale="locale" />
      <details class="docs-diagram-source"><summary>{{ locale === 'en' ? 'Diagram source' : '查看图稿' }}</summary>
        <QuireProseSource :code="code ?? ''" language="json" :filename="filename" :collapsible="true" :max-height="320" />
      </details>
    </template>
    <template v-else>
      <QuireTypography variant="helper" role="status">{{ locale === 'en' ? 'Invalid or oversized diagram; original source is preserved.' : '图稿格式不完整或超出预览范围，已保留原始代码。' }}</QuireTypography>
      <QuireProseSource :code="code ?? ''" :language="language" :filename="filename" />
    </template>
  </figure>
</template>
<style>
.docs-native-diagram { margin: 16px 0; border: 1px solid var(--qk-color-border-subtle); border-radius: var(--qk-radius-group); overflow: hidden; min-width: 0; }
.docs-diagram-caption { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px; padding: 12px 16px; }
.docs-diagram-source { border-top: 1px solid var(--qk-color-border-subtle); padding: 10px 16px; }
.docs-diagram-source summary { cursor: pointer; font-size: 13px; color: var(--qk-color-text-secondary); }
</style>

```

### diagrams/document.ts

```ts
import { parseFlowchartEmbed } from "@quirekit/flowchart";
import { parseMindMapEmbed } from "@quirekit/mind-map";
import { parseWhiteboard } from "@quirekit/whiteboard";
import type { DiagramDocument } from "./types";

/** @param language - Explicit native fenced format. @param source - Exact JSON body. @returns Validated source; malformed or over-budget input throws. */
export function readDiagram(language: string, source: string): DiagramDocument {
  if (source.length > 4_194_304) throw new RangeError("Diagram preview source exceeds 4,194,304 UTF-16 code units");
  if (language === "quirekit-flowchart") {
    const value = parseFlowchartEmbed(source);
    if (value.document.nodes.length > 200 || value.document.edges.length > 400) throw new RangeError("Diagram preview exceeds 200 shapes or 400 edges");
    return { kind: "flowchart", value };
  }
  if (language === "quirekit-mind-map") {
    const value = parseMindMapEmbed(source);
    let count = 0;
    const pending = [value.document.root, ...(value.document.floatingTopics?.map(topic => topic.root) ?? [])];
    while (pending.length) {
      if (++count > 500) throw new RangeError("Diagram preview exceeds 500 topics");
      pending.push(...pending.pop()!.children);
    }
    return { kind: "mind-map", value };
  }
  if (language === "quirekit-whiteboard") {
    const value = parseWhiteboard(source);
    if (value.scene.elements.length > 500) throw new RangeError("Diagram preview exceeds 500 elements");
    return { kind: "whiteboard", value };
  }
  throw new TypeError("Unsupported diagram language");
}

```

### diagrams/types.ts

```ts
import type { FlowchartEmbed } from "@quirekit/flowchart";
import type { MindMapEmbed } from "@quirekit/mind-map";
import type { WhiteboardDocument } from "@quirekit/whiteboard";

/** Canonical source admitted by its own domain validator. */
export type DiagramDocument = { kind: "flowchart"; value: FlowchartEmbed }
  | { kind: "mind-map"; value: MindMapEmbed } | { kind: "whiteboard"; value: WhiteboardDocument };

/** Viewport-only public adapter; source operations are deliberately absent. */
export interface DiagramView {
  /** @returns Completion of fitting the immutable source to the host. */ fit(): void | Promise<void>;
  /** @param factor - Relative zoom multiplier; source is never read. */ zoom(factor: number): void;
  /** @returns Completion of adapter resource release. */ destroy(): void | Promise<void>;
}

```

### diagrams/view.vue

```vue
<script setup lang="ts">
import { onMounted, onBeforeUnmount, ref, shallowRef, watchEffect } from "vue";
import { QuireButton, QuireSpace, QuireTypography } from "@quirekit/ui/vue";
import type { DiagramDocument, DiagramView } from "./types";
import { mountDiagram } from "./mount";
const props = defineProps<{
  /** Validated immutable source. */ diagram: DiagramDocument;
  /** Interface language. */ locale?: string;
  /** Larger viewport inside the full-view dialog. */ expanded?: boolean;
}>();
const parent = shallowRef<HTMLElement>();
const view = shallowRef<DiagramView>();
const active = ref(false), ready = ref(false), failed = ref(false);
let visibility: IntersectionObserver | undefined;
onMounted(() => {
  if (!parent.value || typeof IntersectionObserver === "undefined") { active.value = true; return; }
  visibility = new IntersectionObserver(entries => {
    if (entries.some(entry => entry.isIntersecting)) { active.value = true; visibility?.disconnect(); }
  }, { rootMargin: "160px" });
  visibility.observe(parent.value);
});
onBeforeUnmount(() => visibility?.disconnect());
watchEffect(onCleanup => {
  if (!active.value || !parent.value) return;
  const source = props.diagram, locale = props.locale ?? "zh-CN", host = parent.value;
  const lifetime = new AbortController();
  // Every source revision owns its mount, including any asynchronous native layout.
  const mount = host.ownerDocument.createElement("div");
  mount.className = "docs-diagram-mount";
  host.append(mount);
  ready.value = false;
  failed.value = false;
  let instance: DiagramView | undefined;

  /** Shows failure only while this source revision still owns the view. */
  function showFailure(): void {
    if (lifetime.signal.aborted) return;
    ready.value = false;
    failed.value = true;
  }

  const resize = new ResizeObserver(() => {
    if (ready.value && instance) void Promise.resolve().then(() => instance?.fit()).catch(showFailure);
  });
  resize.observe(host);
  onCleanup(() => {
    lifetime.abort();
    resize.disconnect();
    view.value = undefined;
    ready.value = false;
    void instance?.destroy();
    mount.remove();
  });

  void mountDiagram(source, mount, locale, lifetime.signal).then(async result => {
    // A superseded loader can only own its detached mount, never the next graph's host.
    if (lifetime.signal.aborted) { await result.destroy(); return; }
    instance = result;
    view.value = result;
    await result.fit();
    if (!lifetime.signal.aborted) ready.value = true;
  }).catch(showFailure);
});
/** @param factor - Relative zoom or zero to fit the current viewport. @returns Completion without publishing a failure from an old view. */
async function navigate(factor: number): Promise<void> {
  const current = view.value;
  if (!current) return;
  try { if (factor) current.zoom(factor); else await current.fit(); }
  catch { if (view.value === current) { ready.value = false; failed.value = true; } }
}
</script>
<template>
  <div class="docs-diagram-view" :data-diagram-kind="diagram.kind" :data-diagram-ready="ready || undefined" data-qk-not-prose>
    <QuireSpace justify="end" gap="inline" class="docs-diagram-navigation">
      <QuireButton size="sm" variant="text" :disabled="!ready" :aria-label="locale === 'en' ? 'Zoom out' : '缩小'" @click="navigate(0.8)">−</QuireButton>
      <QuireButton size="sm" variant="text" :disabled="!ready" :aria-label="locale === 'en' ? 'Zoom in' : '放大'" @click="navigate(1.25)">+</QuireButton>
      <QuireButton size="sm" variant="text" :disabled="!ready" @click="navigate(0)">{{ locale === 'en' ? 'Fit' : '适应画布' }}</QuireButton>
    </QuireSpace>
    <div ref="parent" class="docs-diagram-canvas" :class="{ 'docs-diagram-canvas--expanded': expanded }" :aria-label="diagram.value.title" />
    <QuireTypography v-if="!ready" variant="helper" role="status" class="docs-diagram-status">
      {{ failed ? (locale === 'en' ? 'The diagram could not load. Its source is available below.' : '图表加载失败，可以查看原始图稿。') : (locale === 'en' ? 'Preparing diagram…' : '正在准备图表…') }}
    </QuireTypography>
  </div>
</template>
<style>
.docs-diagram-view { min-width: 0; position: relative; }
.docs-diagram-navigation { padding: 4px 8px; background: var(--qk-color-surface-subtle); }
.docs-diagram-canvas { height: 400px; position: relative; background: #ffffff; color: #243247; overflow: hidden; }
.docs-diagram-canvas--expanded { height: min(68vh, 740px); }
.docs-diagram-mount { position: absolute; inset: 0; }
.docs-diagram-status { position: absolute; inset: 46% 16px auto; text-align: center; color: #475569; pointer-events: none; }
.docs-diagram-canvas .excalidraw { --color-primary: #2563eb; }
@media (max-width: 600px) { .docs-diagram-canvas { height: 340px; } .docs-diagram-canvas--expanded { height: 62vh; } }
</style>

```

### diagrams/mount.ts

```ts
import type { DiagramDocument, DiagramView } from "./types";
import { whiteboardView } from "./whiteboard";

/** @param source - Validated immutable diagram. @param container - Instance-owned, connected mount. @param locale - Interface language. @param signal - Initialization and mount lifetime. @returns A viewport-only adapter over the existing editor. */
export async function mountDiagram(source: DiagramDocument, container: HTMLElement, locale: string, signal: AbortSignal): Promise<DiagramView> {
  // Source semantics remain in their domain; only client resources and viewport controls live here.
  signal.throwIfAborted();
  if (source.kind === "flowchart") {
    const { createX6FlowchartEditor } = await import("@quirekit/flowchart-x6");
    signal.throwIfAborted();
    const editor = await createX6FlowchartEditor({ container, document: source.value.document, locale, signal, readOnly: true, label: source.value.title });
    return { fit: () => editor.fit(), zoom: factor => editor.setZoom(editor.getState().scale * factor), destroy: () => editor.destroy() };
  }
  if (source.kind === "mind-map") {
    const { createSimpleMindMapEditor } = await import("@quirekit/mind-map-simple");
    signal.throwIfAborted();
    const editor = await createSimpleMindMapEditor({ container, document: source.value.document, locale, signal, readOnly: true, label: source.value.title });
    return { fit: () => editor.fit(), zoom: factor => editor.setZoom(editor.getState().scale * factor), destroy: () => editor.destroy() };
  }
  const { createExcalidrawWhiteboard } = await import("@quirekit/whiteboard-excalidraw");
  signal.throwIfAborted();
  const editor = await createExcalidrawWhiteboard({ container, document: source.value, locale, signal, readOnly: true, theme: "light" });
  return whiteboardView(editor, container.ownerDocument.defaultView!, signal);
}

```

### diagrams/whiteboard.ts

```ts
import type { WhiteboardEditor } from "@quirekit/whiteboard-excalidraw";
import type { DiagramView } from "./types";

/**
 * Waits for a native viewport commit, with cancellation on detached mounts.
 * @param window - Owning browsing context.
 * @param signal - Diagram lifetime.
 * @returns Completion of the next animation frame.
 */
function viewportFrame(window: Window, signal: AbortSignal): Promise<void> {
  signal.throwIfAborted();
  return new Promise((resolve, reject) => {
    /** Cancels the pending frame when the owning view closes. */
    function abort(): void {
      window.cancelAnimationFrame(frame);
      reject(signal.reason);
    }
    const frame = window.requestAnimationFrame(() => {
      signal.removeEventListener("abort", abort);
      resolve();
    });
    signal.addEventListener("abort", abort, { once: true });
  });
}

/**
 * Reserves space for native read-only chrome without serializing the scene.
 * @param editor - Existing public Excalidraw adapter.
 * @param window - Owning browsing context.
 * @param signal - Diagram lifetime.
 * @returns Coalesced viewport controls and the original disposal boundary.
 */
export function whiteboardView(editor: WhiteboardEditor, window: Window, signal: AbortSignal): DiagramView {
  let fitting: Promise<void> | undefined;

  /** @returns Completion after initial scene restore and the fitted viewport commit. */
  async function fitViewport(): Promise<void> {
    await viewportFrame(window, signal);
    editor.fit();
    await viewportFrame(window, signal);
    // Native fitting uses the full host; reserve the footer's space in this reader.
    editor.zoom(0.8);
  }

  /** @returns One shared fit operation while the native viewport is settling. */
  function fit(): Promise<void> {
    return fitting ??= fitViewport().finally(() => { fitting = undefined; });
  }

  return { fit, zoom: factor => editor.zoom(factor), destroy: () => editor.destroy() };
}

```

### algorithms/layers.ts

```ts
import { createMindMapDocument, createMindMapEmbed, serializeMindMapEmbed } from "@quirekit/mind-map";
import type { MindMapOutline } from "@quirekit/mind-map";
import type { ExampleLocale } from "../shared/types";

/** @param text - Literal topic title. @param children - Child responsibility topics. @returns A plain outline consumed by the public domain factory. */
function topic(text: string, children: readonly MindMapOutline[] = []): MindMapOutline { return { text, children }; }

/** @param locale - Literal content language. @returns Canonical mind-map source showing public ownership boundaries. */
export function algorithmLayers(locale: ExampleLocale): string {
  const zh = locale === "zh-CN", title = zh ? "解析、渲染和宿主各自负责什么" : "Parsing, rendering and host responsibilities";
  const outline = topic("QuireKit Markdown", [
    topic("incremark-core", [
      topic(zh ? "源码与原生语法树" : "Source and native syntax tree", [topic(zh ? "追加、替换、解析续作" : "Append, replace, resume parsing"), topic(zh ? "引用分组与首次定义" : "Reference grouping; first definition wins")]),
      topic(zh ? "阅读投影与缓存证明" : "Reading projection and cache validation", [topic(zh ? "链接、脚注、HTML 策略" : "Links, notes, HTML policy"), topic(zh ? "块身份与外部依赖" : "Block identity and external inputs")]),
    ]),
    topic(zh ? "框架适配" : "Framework adapters", [topic(zh ? "Vue：VNode 与组件生命周期" : "Vue: VNodes and component lifetime"), topic(zh ? "React：提交后接受缓存" : "React: accept cache after commit")]),
    topic(zh ? "应用宿主" : "Application host", [topic(zh ? "网络流、停止与消息 identity" : "Network stream, stop, message identity"), topic(zh ? "组件替换与可选图表渲染器" : "Components and optional diagrams"), topic(zh ? "资源取消、释放与错误处理" : "Resource cleanup and error handling")]),
  ]);
  return serializeMindMapEmbed({ ...createMindMapEmbed("algorithm-layers", title), document: createMindMapDocument(outline, { layout: "logical-right" }) });
}

```

All three diagrams retain their native source. You can also integrate these
[flowchart, mind-map and whiteboard renderers](/en/guide/diagrams) in your own documentation.

## Watch a stream

Start the example to watch text arrive before its link and footnote definitions. The same footnote
is referenced twice, and its body also contains a link. Stop to retain the text received so far.

### reading/stream.vue

```vue
<script setup lang="ts">
import { computed, onBeforeUnmount, ref } from "vue";
import { QuireMarkdown, type QuireMarkdownProps } from "@quirekit/incremark/vue";
import { QuireButton, QuireTypography } from "@quirekit/ui/vue";
import type { ExampleLocale } from "../shared/types";
import { readingSample } from "./sample";

const props = withDefaults(defineProps<{ /** Tutorial language. */ locale?: ExampleLocale }>(), { locale: "zh-CN" });
const content = ref(readingSample(props.locale)), identity = ref(0);
const status = ref<NonNullable<QuireMarkdownProps["status"]>>("complete");
const labels = computed(() => props.locale === "zh-CN"
  ? { start: "开始流式演示", stop: "停止", load: "一次性加载", reading: "流式阅读预览", receiving: "正在接收", stopped: "已停止", complete: "已完成" }
  : { start: "Start streaming", stop: "Stop", load: "Load all", reading: "Streaming reading preview", receiving: "Receiving", stopped: "Stopped", complete: "Complete" });
let timer: ReturnType<typeof setTimeout> | undefined;

/** Cancels the producer independently of QuireMarkdown's parser lifecycle. */
function cancel(): void { if (timer !== undefined) clearTimeout(timer); timer = undefined; }
/** Supplies the authoritative full text after each simulated network chunk; no parser APIs are needed. */
function start(): void {
  cancel(); identity.value++; content.value = ""; status.value = "receiving";
  const source = readingSample(props.locale);
  /** Delivers the next 20 UTF-16 code units, including arbitrary syntax boundaries. */
  function append(): void {
    content.value = source.slice(0, content.value.length + 20);
    if (content.value.length < source.length) timer = setTimeout(append, 30);
    else { timer = undefined; status.value = "complete"; }
  }
  append();
}
/** Retains the exact received prefix when the user stops delivery. */
function stop(): void { cancel(); status.value = "stopped"; }
/** Replaces the message with its complete source and a new resource identity. */
function load(): void { cancel(); identity.value++; content.value = readingSample(props.locale); status.value = "complete"; }
onBeforeUnmount(cancel);
</script>
<template>
  <div class="grid gap-4 p-qk-surface">
    <div class="flex flex-wrap items-center gap-2">
      <QuireButton size="sm" variant="outline" @click="start">{{ labels.start }}</QuireButton>
      <QuireButton size="sm" variant="ghost" :disabled="status !== 'receiving'" @click="stop">{{ labels.stop }}</QuireButton>
      <QuireButton size="sm" variant="ghost" @click="load">{{ labels.load }}</QuireButton>
      <QuireTypography as="span" variant="helper">{{ status === 'receiving' ? labels.receiving : status === 'stopped' ? labels.stopped : labels.complete }}</QuireTypography>
    </div>
    <section :aria-label="labels.reading"><QuireMarkdown :content="content" :identity="String(identity)" :status="status" :locale="locale" /></section>
  </div>
</template>

```

### shared/types.ts

```ts
/** Example host language; independent of the documentation router. */
export type ExampleLocale = "zh-CN" | "en";

```

### reading/sample.ts

````ts
import type { ExampleLocale } from "../shared/types";

/** @param locale - Tutorial language. @returns One source with late definitions, repeated footnotes and an incrementally displayed code block. */
export function readingSample(locale: ExampleLocale): string {
  const chinese = locale === "zh-CN";
  return [chinese ? "## 流式 Markdown" : "## Streaming Markdown",
    chinese ? "先收到正文和[文档][api]引用，定义会在末尾到达。这里重复引用同一脚注[^note]、[^note]。"
      : "The [documentation][api] reference arrives before its definition. This footnote is used twice[^note], [^note].",
    ...Array.from({ length: 6 }, (_, index) => chinese
      ? `第 ${index + 1} 段保留 **强调**、中文与 emoji 🙂。已收到的正文继续可读，后置定义到达时前面的链接会更新。`
      : `Paragraph ${index + 1} keeps **emphasis** and emoji 🙂. Existing text stays readable while late definitions update earlier links.`),
    "```ts\nconst message = 'hello'\nconsole.log(message)\n```",
    chinese ? "[^note]: 脚注正文也使用[文档][api]链接。" : "[^note]: The footnote also links to the [documentation][api].",
    "[api]: https://example.com/docs", "[api]: https://example.com/ignored",
  ].join("\n\n");
}

````

You can also paste either short example into the [Playground](/en/playground) to inspect the new engine's output.

## What about speed

Earlier Marked is fast. Switching to Lezer does not make every Markdown input faster to parse.
The current optimizations reduce two kinds of repeated work: parsing unaffected text again and
rebuilding reusable display output.

An earlier original-demo parse measurement put Lezer/MSFE ahead of the old Micromark and behind the
old Marked. Those measurements predate the current rendering cache and cannot rank the current version.

A large list, table or long paragraph can still be processed as one block. Streaming performance
depends on parsing, rendering and browser updates together. See [performance](/en/guide/performance)
for the measurement method.

## Using it and migrating

QuireKit Markdown supports Vue and React with shared parsing rules. Basic usage still takes Markdown
text. You can replace links, code blocks and other elements with your components, add custom syntax,
and explicitly enable HTML.

The new package does not retain earlier Incremark APIs, so existing plugins need adaptation.
Start with [Vue](/en/guide/getting-started) or [React](/en/guide/react), then follow
[custom syntax and nodes](/en/guide/extensions) for extensions. This remains an unreleased preview.
Earlier Incremark and the current core, Vue and React packages all use MIT.

## Further implementation reading

These details are useful when extending the parser or studying the cache:

- **Syntax reuse:** CodeMirror maps text transactions and `TreeFragment`. Our Lezer layer exposes
  top-level block reuse while preserving native grammar and reuse decisions. Reference handling
  belongs to the MSFE parser wrapper.
- **Reference decisions:** Complete paragraphs record labels actually queried by grammar and whether
  they exist. Those decisions are reused only when query results match; ranges without complete
  records are checked conservatively. Rendering separately checks destination and title changes.
- **Rendering cache:** Entries check native Tree identity, source range, reference targets, footnote
  navigation and rendering configuration. Custom nodes can declare additional dependencies with
  `cacheKey(document)`; without it, output is generated again. See [performance](/en/guide/performance)
  for conditions and cache limits.
- **Frameworks and resources:** Vue and React have their own components. React accepts candidate
  caches after commit and starts resources after client commit. Cancellation and release handle
  switching, unmount and late results; see [React integration](/en/guide/react).

The two examples were checked on 2026-10-11 against `@incremark/core` 1.0.2, commit `98f6b95`.
Earlier results come from each engine's AST and definitions; current results come from React server
rendering, checked against an independent full GFM parse and
[CommonMark reference rules](https://spec.commonmark.org/0.31.2/#links).
These examples identify specific problems in that version, without extending the findings to later Incremark versions.

Source: [earlier parser session](https://github.com/kingshuaishuai/incremark/blob/98f6b95bba58d0c9e4ba1af37a4d52c2f2fae1df/packages/core/src/parser/IncremarkParser.ts),
[reference recognition](https://github.com/kingshuaishuai/incremark/blob/98f6b95bba58d0c9e4ba1af37a4d52c2f2fae1df/packages/core/src/extensions/marked-extensions/optimisticReferenceExtension.ts),
[definition merging](https://github.com/kingshuaishuai/incremark/blob/98f6b95bba58d0c9e4ba1af37a4d52c2f2fae1df/packages/core/src/parser/manager/DefinitionManager.ts).
