Skip to content

Rich text format

Canonical storage for note CRDT bodies and study plan step instructions is a versioned JSON envelope, not markdown. Markdown is used for import, export, and derived search text only.

type StorageEnvelope = {
v: 1;
doc: JSONContent; // TipTap / ProseMirror document
};
  • Notes CRDT (body field): JSON.stringify(envelope); schema version 2 (see Note CRDT sync).
  • Study plan instructions (instructions column): same envelope JSON string (column remains text).
  • markdown_body (notes only): derived plain-text export on every CRDT merge for list cards and search — not the source of truth.

Strings that fail JSON envelope parse are treated as legacy markdown and converted on read via fromMarkdown().

Node / markPurposeLegacy markdown token
scriptureRef (inline atom)Verse chip[[ref:Rom.1.1]]
filterBlock (block)Study-plan conditional content:::filter personality=Task\n...\n:::
Marks: bold, italic, underline, codeFormatting**, *, <u>, `
Nodes: heading (1–3), paragraph, hardBreakStructure#, plain text
AttrTypeDescription
pathstringCanonical ref path, e.g. Rom.1.1
labelstringDisplay label, e.g. Romans 1:1
AttrTypeDescription
personalitystring[]Personality focus values (OR within attribute)
learningstring[]Learning style values (OR within attribute)

All specified attributes must match the user profile (AND). Unconditional blocks omit both arrays or leave them empty.

Shared test vectors live in ui/test/fixtures/richText/ (envelope JSON + expected markdown). PHP and TypeScript test suites import the same files for parity.