# Complete workspace reference

Find complete workspace, model, extension and file-operation implementations by purpose.

Use this page as a reference after following the tutorial. For your first integration, begin with [your first editor](/en/build/project). Find individual capabilities by their file responsibilities below.

## What each file does

| File | Purpose | Tutorial |
| --- | --- | --- |
| `product/standalone.ts` | Mount a workspace in an ordinary Vue application and return cleanup | [Project](/en/build/project) |
| `product/workspace.vue` | Compose toolbar, file controls and editing area | [Interface](/en/build/commands) |
| `product/runtime.ts` | Create Editor and connect View to host dimensions | [View](/en/build/view) |
| `product/assembly/generic.ts` | Configure articles and note groups | [Model](/en/build/model) |
| `product/assembly/word.ts` | Configure the Word preset and matching initial content | [Model](/en/build/model) |
| `product/session.ts` | Update controls and coordinate editing, saving and loading | [Interface](/en/build/commands) |
| `product/files/repository.ts` | Write backups to browser storage | [Files](/en/build/files) |
| `product/files/backup.ts` | Validate backup version, nodes and attributes | [Files](/en/build/files) |
| `product/files/docx.ts` | Load Word conversion on demand and handle actual bytes | [Files](/en/build/files) |
| `product/feature/*` | Note structure, command, layout and selection | [Content extension](/en/build/extension) |
| `product/types.ts` | Workspace shared types | Module inputs and outputs |
| `shared/download.ts` | Hand generated files to the browser for downloading | [Files](/en/build/files) |

## Run it in your project

The complete workspace uses Vue, `@quirekit/core`, `@quirekit/starter-kit`, `@quirekit/view`, `@quirekit/view-engine`, `@quirekit/ui` and `@quirekit/unocss-preset`. The Word variant also uses `@quirekit/classic-docx` and `@quirekit/extension-text-style`. See [availability and licensing](/en/support) for commercial packages and use matching versions you have obtained.

Copy `product/` and `shared/` from the file list into `src/examples/`, preserving relative paths. Enable Vue and UnoCSS in Vite, use `defineQuireKitUnoConfig()` for UnoCSS, and scan `./src/**/*.{vue,ts}`. Import `uno.css` in your entry; the workspace already imports View structure styles.

```ts [src/main.ts]
import 'uno.css'
import { mountDocumentProduct } from './examples/product/standalone'

const release = mountDocumentProduct(document.getElementById('app')!)
if (import.meta.hot) import.meta.hot.dispose(release)
```

`mountDocumentProduct()` receives an existing element and returns cleanup for both Vue and Editor. Call it when the application exits or during hot replacement.

## Complete files

Choose one file and expand its arrow. Copy always returns that file's complete original text. All local dependencies remain available in the list.

### product/standalone.ts

```ts
import { createApp, h } from "vue";
import { defaultTheme, quireIconRegistry } from "@quirekit/ui";
import { QuireThemeProvider, QuireIconProvider, QuireLocaleProvider } from "@quirekit/ui/vue";
import Workspace from "./workspace.vue";
/**
 * Mounts the full reference in an ordinary Vue/Vite host, without Nuxt or Classic's product shell.
 * @param element - Host element whose height and placement belong to the application.
 * @param locale - Initial UI and document-copy language.
 * @returns Release function for Vue, View, Core and all session listeners.
 */
export function mountDocumentProduct(element: HTMLElement, locale: "zh-CN" | "en" = "zh-CN"): () => void {
  const app = createApp({
    render: () => h(QuireIconProvider, { registry: quireIconRegistry }, () =>
      h(QuireThemeProvider, { theme: defaultTheme }, () =>
        h(QuireLocaleProvider, { locale }, () => h(Workspace, { locale, model: "word-v1" })))),
  });
  app.mount(element);
  return () => app.unmount();
}

```

### product/workspace.vue

```vue
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, shallowRef } from "vue";
import "@quirekit/view/styles.css";
import { defaultTheme } from "@quirekit/ui";
import { QuireButton, useQuireKitTheme } from "@quirekit/ui/vue";
import { downloadJson } from "../shared/download";
import type { ProductModel, ProductRuntime, ProductState } from "./types";
const props = withDefaults(defineProps<{
  /** Interface language; it never translates existing content. */ locale?: "zh-CN" | "en";
  /** Immutable create-time schema family. */ model?: ProductModel;
}>(), { locale: "zh-CN", model: "word-v1" });
const viewport = ref<HTMLElement>(); const surface = ref<HTMLElement>(); const fileInput = ref<HTMLInputElement>();
const wordInput = ref<HTMLInputElement>();
const instance = shallowRef<ProductRuntime>(); const state = shallowRef<ProductState>();
const error = ref<string | null>(null); const busy = ref(false); const pages = ref(0); const mode = ref<"canvas" | "svg">("canvas");
const zoom = ref(70); const width = ref(794); const height = ref(1123); const theme = useQuireKitTheme();
let disposed = false; let release: (() => void) | undefined; let resize: ResizeObserver | undefined;

const english = computed(() => props.locale === "en");
const copy = computed(() => english.value ? {
  title: "My document workspace", bold: "Bold", undo: "Undo", redo: "Redo", add: "Insert paragraph", note: "Insert note",
  save: "Save in this browser", restore: "Restore saved", backup: "Download backup", open: "Open backup", read: "Read only", edit: "Resume editing",
  loading: "Preparing the workspace…", dirty: "Unsaved edits", saved: "No pending edits", saving: "Saving…", reload: "Reload page",
  hint: "Edit, save, switch Canvas/SVG, or restore your saved version after reloading. Opening another file requires saving current edits.",
} : {
  title: "我的文档工作区", bold: "加粗", undo: "撤销", redo: "重做", add: "新增段落", note: "插入提示组",
  save: "保存到此浏览器", restore: "恢复已保存版本", backup: "下载备份", open: "打开备份", read: "只读", edit: "继续编辑",
  loading: "正在准备工作区…", dirty: "有未保存的修改", saved: "无待保存修改", saving: "保存中…", reload: "重新加载页面",
  hint: "编辑、保存、切换 Canvas/SVG，或刷新后恢复已保存版本。打开其他文件前须先保存当前修改。",
});
/** Fits the host's presentation width; page geometry remains owned by the document profile. */
function fit(): void { if (viewport.value) zoom.value = Math.round(Math.min(1, Math.max(.25, (viewport.value.clientWidth - 24) / width.value)) * 100); }
/** @param next - Public registered view mode. */
function changeMode(next: "canvas" | "svg"): void { instance.value?.changeMode(next); mode.value = next; }
/** Downloads an explicit current snapshot, not a mirror updated while typing. */
function backup(): void { if (instance.value) downloadJson(instance.value.session.backup(), "quirekit-product.json"); }
/** @param event - Native host file selection. */
async function open(event: Event): Promise<void> {
  const input = event.target as HTMLInputElement; const file = input.files?.[0]; input.value = "";
  if (file && instance.value) await instance.value.session.open(() => file.text());
}
/** @param event - A complete Word file selected by the host. */
async function openDocx(event: Event): Promise<void> {
  const input = event.target as HTMLInputElement; const file = input.files?.[0]; input.value = "";
  const product = instance.value; if (!file || !product || busy.value) return;
  busy.value = true; error.value = null;
  let messages: string[] = [];
  const accepted = await product.session.open(async () => {
    const { readWord } = await import("./files/docx");
    const result = await readWord(product.editor, new Uint8Array(await file.arrayBuffer()));
    messages = [...result.warnings, ...result.diagnostics].map(item => item.message);
    return JSON.stringify({ version: 1, model: "word-v1", content: result.document });
  });
  if (!disposed) { busy.value = false; if (accepted && messages.length) error.value = messages.join("; "); }
}
/** Downloads a prepared binary artifact through a short-lived browser URL. */
async function exportDocx(): Promise<void> {
  const product = instance.value; if (!product || busy.value) return;
  const captured = product.editor.getJSON();
  busy.value = true; error.value = null;
  try {
    const { exportWord } = await import("./files/docx");
    if (disposed) return;
    const result = await exportWord(product.editor, captured);
    if (disposed) return;
    const url = URL.createObjectURL(new Blob([new Uint8Array(result.data)], { type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" }));
    const anchor = document.createElement("a"); anchor.href = url; anchor.download = "my-product.docx"; anchor.click(); setTimeout(() => URL.revokeObjectURL(url), 1000);
    const messages = result.diagnostics.filter(item => item.severity !== "info").map(item => item.message);
    if (messages.length) error.value = messages.join("; ");
  } catch (failure) { if (!disposed) error.value = String(failure); }
  finally { if (!disposed) busy.value = false; }
}
/** Browser-only resource acquisition; late imports cannot resurrect an unmounted workspace. */
onMounted(async () => {
  try {
    const [{ createProduct }, { browserRepository }, { assembly }] = await Promise.all([
      import("./runtime"), import("./files/repository"), props.model === "word-v1" ? import("./assembly/word") : import("./assembly/generic"),
    ]);
    if (disposed || !surface.value || !viewport.value) return;
    const product = createProduct(surface.value, viewport.value, assembly, theme?.value ?? defaultTheme,
      browserRepository(window.localStorage, assembly.id), props.locale, (count, nextWidth, nextHeight) => {
        pages.value = count; width.value = Math.max(1, nextWidth); height.value = Math.max(1, nextHeight);
      });
    instance.value = product; release = product.session.subscribe(value => { state.value = value; });
    resize = new ResizeObserver(fit); resize.observe(viewport.value); fit();
  } catch (failure) { if (!disposed) error.value = String(failure); }
});
onBeforeUnmount(() => { disposed = true; resize?.disconnect(); release?.(); instance.value?.destroy(); });
</script>
<template>
  <section class="product-example" :data-model="model" :data-ready="Boolean(instance)">
    <header class="product-heading"><strong>{{ copy.title }}</strong><span>{{ model === 'word-v1' ? 'Word · Canvas / SVG' : 'Core · Canvas / SVG' }}</span></header>
    <div class="product-tools" role="toolbar" :aria-label="english ? 'Document commands' : '文档命令'">
      <QuireButton variant="outline" :aria-pressed="state?.bold" :disabled="!state?.canBold" @click="instance?.editor.chain().focus().toggleBold().run()">{{ copy.bold }}</QuireButton>
      <QuireButton variant="outline" :disabled="!state?.canUndo" @click="instance?.editor.chain().focus().undo().run()">{{ copy.undo }}</QuireButton>
      <QuireButton variant="outline" :disabled="!state?.canRedo" @click="instance?.editor.chain().focus().redo().run()">{{ copy.redo }}</QuireButton>
      <QuireButton variant="outline" :disabled="!state?.editable" @click="instance?.editor.chain().focus().insertContent({ type: 'paragraph', content: [{ type: 'text', text: english ? 'A new paragraph.' : '新增的段落。' }] }).run()">{{ copy.add }}</QuireButton>
      <QuireButton v-if="model === 'generic-v1'" variant="outline" :disabled="!state?.editable" @click="instance?.editor.chain().focus().insertDocsNote(english ? 'Note: this grouped content is a custom node.' : '提示：这组内容由自定义节点拥有。').run()">{{ copy.note }}</QuireButton>
      <QuireButton variant="outline" :disabled="!instance" :aria-pressed="state && !state.editable" @click="instance?.session.setEditable(!state?.editable)">{{ state?.editable ? copy.read : copy.edit }}</QuireButton>
    </div>
    <div class="product-tools product-file-tools">
      <QuireButton :disabled="!instance || state?.saving" @click="instance?.session.save()">{{ state?.saving ? copy.saving : copy.save }}</QuireButton>
      <QuireButton variant="outline" :disabled="!instance || state?.dirty || state?.saving || !state?.editable" @click="instance?.session.restore()">{{ copy.restore }}</QuireButton>
      <QuireButton variant="outline" :disabled="!instance" @click="backup">{{ copy.backup }}</QuireButton>
      <QuireButton variant="outline" :disabled="!instance || state?.dirty || state?.saving || !state?.editable" @click="fileInput?.click()">{{ copy.open }}</QuireButton>
      <QuireButton v-if="model === 'word-v1'" variant="outline" :disabled="!instance || busy" @click="exportDocx">{{ busy ? 'DOCX…' : (english ? 'Export DOCX' : '导出 DOCX') }}</QuireButton>
      <QuireButton v-if="model === 'word-v1'" variant="outline" :disabled="!instance || busy || state?.dirty || state?.saving || !state?.editable" @click="wordInput?.click()">{{ english ? 'Open DOCX' : '打开 DOCX' }}</QuireButton>
      <input ref="fileInput" type="file" accept=".json,application/json" hidden :aria-label="copy.open" @change="open">
      <input ref="wordInput" type="file" accept=".docx" hidden :aria-label="english ? 'Open DOCX' : '打开 DOCX'" @change="openDocx">
    </div>
    <p class="product-hint">{{ copy.hint }}</p>
    <p v-if="error || state?.error" class="product-error" role="alert">{{ error || state?.error }}</p>
    <p v-if="!instance" class="product-loading" role="status">{{ error ? (english ? 'Reload this page to retry.' : '请重新加载页面后重试。') : copy.loading }}</p>
    <div ref="viewport" class="product-viewport" :aria-label="english ? 'Document workspace' : '文档工作区'">
      <div class="product-stage" :style="{ width: `${width * zoom / 100}px`, minHeight: `${height * zoom / 100}px` }">
        <div ref="surface" class="product-surface" :style="{ zoom: zoom / 100, width: `${width}px` }" />
      </div>
    </div>
    <footer class="product-status">
      <span role="status">{{ state?.saving ? copy.saving : state?.dirty ? copy.dirty : copy.saved }}</span>
      <div><QuireButton variant="ghost" :aria-pressed="mode === 'canvas'" :disabled="!instance" @click="changeMode('canvas')">Canvas</QuireButton><QuireButton variant="ghost" :aria-pressed="mode === 'svg'" :disabled="!instance" @click="changeMode('svg')">SVG</QuireButton><QuireButton variant="ghost" :disabled="zoom <= 25" @click="zoom -= 10">−</QuireButton><span>{{ zoom }}%</span><QuireButton variant="ghost" :disabled="zoom >= 150" @click="zoom += 10">+</QuireButton><span>{{ pages }} {{ english ? 'pages' : '页' }}</span></div>
    </footer>
  </section>
</template>
<style scoped>
.product-example { min-width: 0; text-align: start; background: var(--qk-color-surface-page); }
.product-heading { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 8px; padding: 16px; border-bottom: 1px solid var(--qk-color-border-subtle); font-size: 14px; }
.product-heading span, .product-hint, .product-status { color: var(--qk-color-text-secondary); font-size: 12px; }
.product-tools { display: flex; flex-wrap: wrap; gap: 8px; padding: 12px 16px 0; }
.product-hint { margin: 0; padding: 12px 16px; line-height: 1.7; }
.product-error { margin: 0; padding: 12px 16px; color: var(--qk-color-status-danger-text, var(--qk-color-text-primary)); overflow-wrap: anywhere; }
.product-loading { padding: 20px; }
.product-viewport { height: 540px; overflow: auto; padding: 12px; background: var(--qk-color-surface-subtle); border-block: 1px solid var(--qk-color-border-default); }
.product-stage { margin-inline: auto; position: relative; }
.product-surface { position: relative; background: var(--qk-color-surface-page); }
.product-status { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px; padding: 8px 16px; }
.product-status > div { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
@media (max-width: 600px) { .product-tools, .product-heading, .product-status { padding-inline: 12px; } .product-viewport { height: 440px; padding: 8px; } }
</style>

```

### shared/download.ts

```ts
/**
 * Downloads canonical JSON through a short-lived browser URL.
 * @param document - Serializable canonical content.
 * @param name - Download filename chosen by the host.
 */
export function downloadJson(document: unknown, name: string): void {
  const url = URL.createObjectURL(new Blob([JSON.stringify(document, null, 2)], { type: "application/json" }));
  const link = window.document.createElement("a");
  link.href = url;
  link.download = name;
  link.click();
  window.setTimeout(() => URL.revokeObjectURL(url), 1000);
}

```

### product/types.ts

```ts
import type { Editor, QuireAnyExtension, JSONContent } from "@quirekit/core";
import type { QuireKitTheme } from "@quirekit/ui";

/** Canonical model/version namespace; backups from different schemas never mix. */
export type ProductModel = "generic-v1" | "word-v1";
/** Inputs supplied by a product host to its document/view assembly. */
export interface AssemblyContext {
  /** The one element whose scroll offset defines the visible workspace. */ readonly viewport: HTMLElement;
  /** Shared presentation theme, independent of persisted content. */ readonly theme: QuireKitTheme;
  /** Committed layout facts consumed by product chrome. */ readonly onLayout: (pages: number, width: number, height: number) => void;
}
/** Schema, initial content and view adapters selected together. */
export interface ProductAssembly {
  /** Stable schema namespace used by persistence. */ readonly id: ProductModel;
  /** @param locale - Initial copy only. @returns A fresh canonical document. */
  createDocument(locale: "zh-CN" | "en"): JSONContent;
  /** @param editor - Current canonical state. @returns Effective emphasis under this model's formatting rules. */
  isBold(editor: Editor): boolean;
  /** @param context - Host-owned viewport and presentation. @returns Fresh instance-owned extensions. */
  createExtensions(context: AssemblyContext): readonly QuireAnyExtension[];
}
/** Host persistence; production implementations can replace browser Storage with a service. */
export interface ProductRepository {
  /** @returns The last accepted backup, or null. */ read(): Promise<string | null>;
  /** @param source - Captured, versioned backup. @returns Completion for that exact capture. */ write(source: string): Promise<void>;
}
/** Versioned portable backup owned by the product, not the renderer. */
export interface ProductBackup {
  /** Backup envelope version. */ readonly version: 1;
  /** Canonical schema family and version. */ readonly model: ProductModel;
  /** Canonical content; no geometry or mounted DOM. */ readonly content: JSONContent;
}
/** Small state projection used by product controls. */
export interface ProductState {
  /** Whether content differs from the last acknowledged immutable snapshot. */ readonly dirty: boolean;
  /** One explicit save is in flight. */ readonly saving: boolean;
  /** Current mark state at the selection. */ readonly bold: boolean;
  /** Installed command can run against the current state. */ readonly canBold: boolean;
  /** History availability. */ readonly canUndo: boolean;
  /** Forward history availability. */ readonly canRedo: boolean;
  /** Canonical editability, not a UI-only checkbox. */ readonly editable: boolean;
  /** Recoverable operation error, or null. */ readonly error: string | null;
}
/** Host workflow and subscription lifetime around a borrowed Editor. */
export interface ProductSession {
  /** Latest bounded projection. */ readonly state: ProductState;
  /** @param listener - Visible UI consumer. @returns Its release function. */ subscribe(listener: (state: ProductState) => void): () => void;
  /** @returns Whether the captured snapshot was persisted. */ save(): Promise<boolean>;
  /** @returns Whether the saved version was loaded without overwriting edits. */ restore(): Promise<boolean>;
  /** @param read - Deferred file read. @returns Whether it was still safe to apply the result. */ open(read: () => Promise<string>): Promise<boolean>;
  /** @returns Complete versioned backup of the state at invocation time. */ backup(): ProductBackup;
  /** @param editable - Product edit policy. */ setEditable(editable: boolean): void;
  /** Releases listeners and invalidates asynchronous completions; the creator destroys Editor. */ dispose(): void;
}
/** Runtime resources owned by one mounted product. */
export interface ProductRuntime {
  /** Single canonical editor, also used by every toolbar command. */ readonly editor: Editor;
  /** Host persistence and state lifecycle. */ readonly session: ProductSession;
  /** @param mode - Registered view backend. */ changeMode(mode: "canvas" | "svg"): void;
  /** Destroys borrowed services, mounted views and the canonical editor once. */ destroy(): void;
}

```

### product/files/docx.ts

```ts
import type { Editor, JSONContent } from "@quirekit/core";
import { createWorkspaceLitDocxCodec, exportDocxDocument, importDocxDocument,
  normalizeQuireJsonMarkOrder } from "@quirekit/classic-docx/docx";
/**
 * Exports a captured Word-model document. The caller displays returned compatibility messages.
 * @param editor - Editor assembled with Word codecs, not a generic document schema.
 * @param content - Complete content captured when the host's export action began.
 * @returns Binary DOCX and deterministic compatibility diagnostics.
 */
export async function exportWord(editor: Editor, content: JSONContent) {
  return exportDocxDocument(createWorkspaceLitDocxCodec(), content, {
    manager: editor.docx, schema: editor.schema, lossPolicy: "best-effort",
  });
}
/**
 * Prepares a Word import without writing to the live editor.
 * @param editor - Target Word schema and codec registry.
 * @param bytes - Complete file bytes.
 * @returns A validated candidate and compatibility diagnostics; the host owns freshness and commit.
 */
export async function readWord(editor: Editor, bytes: Uint8Array) {
  const result = await importDocxDocument(createWorkspaceLitDocxCodec(), bytes, { manager: editor.docx });
  return { ...result, document: normalizeQuireJsonMarkOrder(result.document, editor.schema.marks) };
}

```

### product/runtime.ts

```ts
import { Editor } from "@quirekit/core";
import type { QuireKitTheme } from "@quirekit/ui";
import { createProductSession } from "./session";
import type { ProductAssembly, ProductRepository, ProductRuntime } from "./types";

/**
 * Creates a product out of public Core, assembly and host ports. It never creates Classic's shell.
 * @param element - Mount receiving View-owned children.
 * @param viewport - One product-owned scroll container.
 * @param assembly - Schema, initial content, and adapters selected together.
 * @param theme - Presentation theme.
 * @param repository - Explicit persistence port.
 * @param locale - Initial document copy only.
 * @param onLayout - Committed page/size facts; the host does not calculate pagination.
 * @returns One lifetime owning Editor, mounted View and session subscriptions.
 */
export function createProduct(element: HTMLElement, viewport: HTMLElement, assembly: ProductAssembly, theme: QuireKitTheme,
  repository: ProductRepository, locale: "zh-CN" | "en", onLayout: (pages: number, width: number, height: number) => void): ProductRuntime {
  const editor = new Editor({ element, mode: "canvas", extensions: [...assembly.createExtensions({ viewport, theme, onLayout })],
    content: assembly.createDocument(locale) });
  const session = createProductSession(editor, assembly.id, repository, assembly.isBold);
  let disposed = false;
  return {
    editor, session,
    changeMode(mode) { editor.changeMode(mode); },
    destroy() { if (disposed) return; disposed = true; session.dispose(); editor.destroy(); },
  };
}

```

### product/session.ts

```ts
import type { Editor } from "@quirekit/core";
import { createBackup, readBackup } from "./files/backup";
import type { ProductModel, ProductRepository, ProductSession, ProductState } from "./types";

/**
 * Owns host workflows while borrowing a single Editor. No JSON is computed during typing.
 * @param editor - Canonical state and command authority.
 * @param model - Installed schema version.
 * @param repository - Host-owned persistence.
 * @param isBold - Model-owned effective formatting query; generic schemas use a semantic mark.
 * @returns A disposable, bounded UI projection and explicit file workflows.
 */
export function createProductSession(editor: Editor, model: ProductModel, repository: ProductRepository,
  isBold: (instance: Editor) => boolean = instance => instance.isActive("bold")): ProductSession {
  let acceptedDocument = editor.state.doc;
  let projectedState = editor.state.pm;
  let disposed = false;
  let generation = 0;
  let saving = false;
  let error: string | null = null;
  const listeners = new Set<(state: ProductState) => void>();
  /** @returns Fixed-size control state; dry runs never focus or write content. */
  function project(): ProductState {
    return { dirty: editor.state.doc !== acceptedDocument, saving, error,
      bold: isBold(editor), editable: editor.isEditable,
      canBold: editor.isEditable && editor.can().toggleBold(),
      canUndo: editor.isEditable && editor.can().undo(), canRedo: editor.isEditable && editor.can().redo() };
  }
  let state = project();
  /** Publishes a host-state change or one newly accepted PM state. */
  function publish(): void {
    if (disposed) return;
    projectedState = editor.state.pm;
    state = project();
    listeners.forEach(listener => listener(state));
  }
  /** Same-state duplicate event entries do not project controls again. */
  function transaction(): void { if (editor.state.pm !== projectedState) publish(); }
  editor.on("transaction", transaction);
  /** @param failure - Failed host operation. */
  function reject(failure: unknown): void { error = failure instanceof Error ? failure.message : String(failure); publish(); }
  /**
   * Commits a whole-document replacement only while the original operation is current.
   * @param read - Deferred file or repository read.
   * @param saved - Whether this source is an acknowledged repository version.
   * @returns Whether a safe replacement completed.
   */
  async function load(read: () => Promise<string | null>, saved: boolean): Promise<boolean> {
    if (disposed) return false;
    const operation = ++generation;
    const original = editor.state.doc;
    try {
      if (!editor.isEditable || state.dirty || saving) throw new Error("Save current edits before opening another document");
      const source = await read();
      if (disposed || operation !== generation) return false;
      if (editor.state.doc !== original) throw new Error("Document changed while loading; keep the current draft and try again");
      if (!editor.isEditable) throw new Error("Document became read only while loading");
      if (source === null) throw new Error("No saved document in this browser");
      const content = readBackup(source, model, editor.schema);
      if (!editor.commands.replaceDocument(content, { errorOnInvalidContent: true })) throw new Error("Document replacement was rejected");
      if (saved) acceptedDocument = editor.state.doc;
      error = null; publish(); return true;
    } catch (failure) { if (!disposed && operation === generation) reject(failure); return false; }
  }
  return {
    get state() { return state; },
    subscribe(listener) { listeners.add(listener); listener(state); return () => { listeners.delete(listener); }; },
    async save() {
      if (disposed || saving) return false;
      const captured = editor.state.doc;
      const source = JSON.stringify(createBackup(editor, model));
      saving = true; error = null; publish();
      try {
        await repository.write(source);
        if (disposed) return false;
        // A successful old save never acknowledges characters typed while it was running.
        acceptedDocument = captured;
        return true;
      } catch (failure) { if (!disposed) reject(failure); return false; }
      finally { saving = false; publish(); }
    },
    restore() { return load(() => repository.read(), true); },
    open(read) { return load(read, false); },
    backup() { return createBackup(editor, model); },
    setEditable(editable) { if (!disposed) { editor.setEditable(editable, false); publish(); } },
    dispose() { if (disposed) return; disposed = true; generation += 1; editor.off("transaction", transaction); listeners.clear(); },
  };
}

```

### product/files/backup.ts

```ts
import type { Editor, JSONContent } from "@quirekit/core";
import type { ProductBackup, ProductModel } from "../types";

/** @param value - Parsed JSON value. @returns Whether it is a non-array record. */
function record(value: unknown): value is Record<string, unknown> {
  return value !== null && typeof value === "object" && !Array.isArray(value);
}
/**
 * Rejects unknown node, mark and attribute names before PM can discard them.
 * @param value - Candidate node JSON.
 * @param schema - Schema selected by this product's assembly.
 */
function checkNames(value: unknown, schema: Editor["schema"]): void {
  if (!record(value) || typeof value.type !== "string" || !schema.nodes[value.type]) throw new Error("Unknown document node");
  const type = schema.nodes[value.type]!;
  if (value.attrs !== undefined) {
    if (!record(value.attrs) || Object.keys(value.attrs).some(name => !(name in (type.spec.attrs ?? {})))) throw new Error("Unknown node attribute; migrate this document first");
  }
  if (value.content !== undefined) {
    if (!Array.isArray(value.content)) throw new Error("Invalid node content");
    value.content.forEach(child => checkNames(child, schema));
  }
  if (value.marks !== undefined) {
    if (!Array.isArray(value.marks)) throw new Error("Invalid marks");
    for (const mark of value.marks) {
      if (!record(mark) || typeof mark.type !== "string" || !schema.marks[mark.type]) throw new Error("Unknown document mark");
      const markType = schema.marks[mark.type]!;
      if (mark.attrs !== undefined && (!record(mark.attrs) || Object.keys(mark.attrs).some(name => !(name in (markType.spec.attrs ?? {}))))) throw new Error("Unknown mark attribute; migrate this document first");
    }
  }
}
/**
 * Validates envelope, schema names and the full content expression without changing Editor.
 * @param source - Original backup, retained by the caller on error.
 * @param model - Installed schema family/version.
 * @param schema - Installed, complete schema.
 * @returns Validated canonical content ready for setContent.
 */
export function readBackup(source: string, model: ProductModel, schema: Editor["schema"]): JSONContent {
  const value: unknown = JSON.parse(source);
  if (!record(value) || value.version !== 1 || value.model !== model) throw new Error("Unsupported backup model or version");
  checkNames(value.content, schema);
  const node = schema.nodeFromJSON(value.content);
  if (node.type !== schema.topNodeType) throw new Error("Invalid document root");
  node.check();
  return node.toJSON() as JSONContent;
}
/** @param editor - Current canonical authority. @param model - Installed schema. @returns Explicit serialization boundary. */
export function createBackup(editor: Editor, model: ProductModel): ProductBackup {
  return { version: 1, model, content: editor.getJSON() };
}

```

### product/files/repository.ts

```ts
import type { ProductModel, ProductRepository } from "../types";
/**
 * Creates an explicit browser persistence port; access errors remain visible to the caller.
 * @param storage - Host-provided Storage, acquired only in the browser.
 * @param model - Separate key for each canonical schema.
 * @returns Replaceable persistence adapter.
 */
export function browserRepository(storage: Pick<Storage, "getItem" | "setItem">, model: ProductModel): ProductRepository {
  const key = `quirekit-product-example:${model}`;
  return {
    async read() { return storage.getItem(key); },
    async write(source) { storage.setItem(key, source); },
  };
}

```

### product/assembly/word.ts

```ts
import { createClassicWordDefaultDocument, createClassicWordPreset } from "@quirekit/classic-docx/vue";
import { readEffectiveDirectEmphasisState } from "@quirekit/extension-text-style";
import type { ProductAssembly } from "../types";
/** Word schema, document-profile facets and views resolved without creating a Classic shell. */
export const assembly: ProductAssembly = {
  id: "word-v1",
  isBold: editor => readEffectiveDirectEmphasisState(editor.state, "bold") === true,
  createDocument: createClassicWordDefaultDocument,
  createExtensions({ viewport, theme, onLayout }) {
    const preset = createClassicWordPreset({ proofing: false });
    const runtime = { width: 794, height: 1123, scrollSource: "viewport" as const, scrollViewport: viewport,
      onRender: ({ renderResult }: import("@quirekit/view").QuireMountedViewRenderEvent) => {
        const snapshot = renderResult.layoutSnapshot;
        onLayout(snapshot.pages?.length ?? 0, snapshot.documentRect.width, snapshot.documentRect.height);
      } };
    return preset.resolveExtensions({ features: Object.fromEntries(Object.entries(preset.features).map(([key, value]) => [key, value === true])), theme, workspaceViewport: viewport, backend: "canvas", canvasRuntime: runtime, svgRuntime: runtime });
  },
};

```

### product/assembly/generic.ts

```ts
import { StarterKit } from "@quirekit/starter-kit";
import { canvasViewExtension, svgViewExtension } from "@quirekit/view";
import { DocsNoteFeature } from "../feature/extension";
import type { ProductAssembly } from "../types";
/** Continuous-flow assembly using the neutral profile and a feature-owned note container. */
export const assembly: ProductAssembly = {
  id: "generic-v1",
  isBold: editor => editor.isActive("bold"),
  createDocument(locale) {
    return { type: "doc", content: [
      { type: "heading", attrs: { level: 1 }, content: [{ type: "text", text: locale === "en" ? "My document product" : "我的文档产品" }] },
      { type: "paragraph", content: [{ type: "text", text: locale === "en" ? "This workspace owns its interface. Core owns content; View owns input and geometry." : "工作区拥有自己的界面，Core 拥有正文，View 拥有输入和几何。" }] },
    ] };
  },
  createExtensions({ viewport, onLayout, theme }) {
    const runtime = { width: 720, height: 900, pageStack: false, scrollSource: "viewport" as const, scrollViewport: viewport,
      surfaceConfig: { selection: { fillStyle: theme.color.selection.document } },
      onRender: ({ renderResult }: import("@quirekit/view").QuireMountedViewRenderEvent) => {
        const snapshot = renderResult.layoutSnapshot;
        onLayout(snapshot.pages?.length ?? 0, snapshot.documentRect.width, snapshot.documentRect.height);
      } };
    return [StarterKit, DocsNoteFeature, canvasViewExtension({ runtime }), svgViewExtension({ runtime: { ...runtime, textMeasurementBackend: "browser" } })];
  },
};

```

### product/feature/extension.ts

```ts
import { Extension } from "@quirekit/core";
import { DocsNote } from "./node";
import { noteLayout } from "./layout";
import { noteSelection } from "./selection";

declare module "@quirekit/core" {
  /** The same typed command registry used by direct, chain and can modes. */
  interface Commands<ReturnType> {
    /** Feature-owned commands. */
    docsNote: {
      /** @param text - Paragraph text inside a new note. @returns Whether insertion can run or was handled. */
      insertDocsNote: (text: string) => ReturnType;
    };
  }
}
/** One installable feature owns schema, command, retained layout and structured selection. */
export const DocsNoteFeature = Extension.create({
  name: "docsNoteFeature",
  /** @returns Schema owner; no second document is maintained. */
  addExtensions() { return [DocsNote]; },
  /** @returns Typed raw command factories, also safe in can() dry runs. */
  addCommands() {
    return {
      insertDocsNote: (text: string) => ({ commands }) => commands.insertContent({ type: "docsNote",
        content: [{ type: "paragraph", content: [{ type: "text", text }] }] }),
    };
  },
  /** @returns Renderer-neutral geometry owner. */
  addLayoutProviders() { return [noteLayout]; },
  /** @returns Structured crossing semantics, independently of hit testing. */
  addSelectionAdapters() { return [noteSelection]; },
});

```

### product/feature/node.ts

```ts
import { Node, mergeAttributes } from "@quirekit/core";
/** Source structure shared by JSON, HTML and registered retained-view layout. */
export const DocsNote = Node.create({
  name: "docsNote", group: "block", content: "block+", isolating: true,
  /** @returns HTML parser ownership for this exact custom node. */
  parseHTML() { return [{ tag: "aside[data-docs-note]" }]; },
  /** @param input - Current serialized attributes. @returns HTML plus one child-content hole. */
  renderHTML({ HTMLAttributes }) { return ["aside", mergeAttributes(HTMLAttributes, { "data-docs-note": "" }), 0]; },
});

```

### product/feature/layout.ts

```ts
import { createQuireLayoutProviderRegistration, type QuireLayoutProvider } from "@quirekit/view-engine/layout-provider";
/** Editable grouped content; a provider consumes source and constraints, never mounted DOM. */
const provider: QuireLayoutProvider = {
  id: "docs-note-layout", nodeTypes: ["docsNote"],
  capabilities: ["child-flow", "fragmentation", "selection-domain", "semantic-export"],
  regionContract: { rootKind: "block-container", regionKind: "block-container",
    capabilities: { editable: true, selectable: true, focusable: true, copyable: true, commandable: true, accessible: true },
    selectionPolicy: "delegate-to-text" },
  /** @param context - Source identity, document coordinates and engine services. @returns Retained layout products for both Canvas and SVG. */
  layout(context) {
    if (!context.source.range) throw new Error("Note layout requires a source range");
    // Measure source children at the accepted width before placing any retained geometry.
    const children = context.services.collectBlockInputs({ node: context.sourceNode, contentStart: context.sourceContentStart,
      path: context.sourcePath, inlineSize: context.constraints.inlineSize });
    const source = context.services.layoutBlockContainer({ id: `${context.source.sourceId}:source`, range: context.source.range,
      rect: { x: 0, y: 0, width: context.constraints.inlineSize, height: context.constraints.blockHeight },
      blockHeight: context.constraints.blockHeight, blockGap: context.constraints.blockGap, children });
    const rect = { ...context.constraints.origin, width: context.constraints.inlineSize, height: Math.max(context.constraints.blockHeight, source.rect.height) };
    // Place the measured flow through one owner-relative track; source order and coordinate space stay authoritative.
    const owner = { root: context.root, region: context.region };
    const flow = context.services.flowBlockChildren({ id: `${context.source.sourceId}:flow`, owner, source,
      tracks: [{ id: `${context.source.sourceId}:track`, rect, coordinateSpace: context.constraints.coordinateSpace,
        region: context.region, source: context.source, capabilities: context.root.capabilities }],
      dependencySignature: `${context.source.sourceId}:${rect.width}:${rect.height}` });
    if (!flow.complete) throw new Error("Note layout must cover all measured children");
    const exportFallbacks = [{ kind: "native" as const, targetFormat: "html" as const, reason: "Children retain their source order." }];
    return { providerId: context.providerId, snapshotVersion: context.snapshotVersion, source: context.source,
      layout: context.services.createContribution({ id: context.source.sourceId, range: context.source.range, rect, owner, fragments: flow.fragments, exportFallbacks }),
      declarations: [context.services.declareRegionProducts({ declaration: context.services.declarePrimaryRegion(rect), owner,
        semantics: { role: "group", label: "Note", search: { kind: "source-text" } }, exportFallbacks })],
      fragments: flow.fragments, continuation: null };
  },
};
/** Exact concrete provider depends only on immutable source plus accepted constraints. */
export const noteLayout = { ...createQuireLayoutProviderRegistration(provider), layoutDependency: { kind: "document" as const } };

```

### product/feature/selection.ts

```ts
import { createQuireSelectionAdapterRegistration, type QuireSelectionAdapter } from "@quirekit/view-engine/selection-adapter";
/** Crossing the group owns complete children; editing inside continues to delegate to text. */
const adapter: QuireSelectionAdapter = {
  version: 1, id: "docs-note-selection", nodeTypes: ["docsNote"],
  modes: { internal: "delegate", entry: "semantic-units", exit: "semantic-units", fullyCrossed: "owner" },
  commandDomain: "docs-note", clipboardDomain: "docs-note", mappingFallback: "text", replacementFallback: "text",
  /** @param context - Canonical owner node. @returns Child ranges relative to its opening token. */
  compileUnits(context) {
    let offset = 1;
    return Array.from({ length: context.node.childCount }, (_, index) => {
      const child = context.node.child(index);
      const unit = { id: `child-${index}`, kind: "note-child", range: { from: offset, to: offset + child.nodeSize } };
      offset += child.nodeSize;
      return unit;
    });
  },
};
/** Registration whose mutable dependencies are absent by construction. */
export const noteSelection = { ...createQuireSelectionAdapterRegistration(adapter), layoutDependency: { kind: "document" as const } };

```
