| 1 | # Composer coordinates and serialization |
| 2 | |
| 3 | The draft store owns Markdown, while Tiptap owns the editing document. The rich-text |
| 4 | setting changes the installed extensions, so toggling it remounts the editor from the |
| 5 | stored draft. Plain mode must leave formatting markers literal. Rich mode preserves |
| 6 | whitespace and chip sources, but canonicalizes supported delimiters (`__` to `**`, |
| 7 | `_` to `*`) and checkbox case (`[X]` to `[x]`). |
| 8 | |
| 9 | Store cursors are not ProseMirror positions: collapsed coordinates count a chip as one |
| 10 | character, expanded coordinates count its source text, and ProseMirror also counts |
| 11 | block boundaries. Keep conversion in [the document model](../../apps/web/src/composer-rich-text-doc.ts). |
| 12 | Empty paragraphs need real caret positions even though they contain no text. Markers |
| 13 | shown beside styled text are decorations, so offsets inside them clamp to the text edge. |
| 14 | |
| 15 | Only replace editor content when the controlled text changes. Replacing it for a cursor |
| 16 | move creates undo entries and regenerates citation identities. Pending citation popovers |
| 17 | must wait until the requested draft has reached the editor before locating their chip. |
| 18 | |
| 19 | Rich tasks split through native editor commands so marks and chips survive. Literal |
| 20 | lists use [store edits](../../apps/web/src/composer-list-continuation.ts). Newlines become |
| 21 | paragraph splits: trailing hard breaks otherwise appear to require two presses. |
| 22 | Programmatic moves must explicitly scroll the caret into view. |
| 23 | |
| 24 | Clipboard text must come from the Markdown serializer, not DOM text: chip labels omit |
| 25 | the source and marker decorations are not content. Structured context records accompany |
| 26 | that text when available. Paste completes trailing chip delimiters and adds a leading |
| 27 | boundary when inserting a chip directly after text. |
| 28 | |
| 29 | Block nodes (fences, lists, quotes, rules, headings) keep their exact Markdown |
| 30 | delimiters as node attributes, so the round trip is byte-identical and offsets |
| 31 | inside a delimiter clamp to the block's text like task and style markers. The |
| 32 | serializers write only what the grammar has: a quote holds paragraphs, a list |
| 33 | holds paragraphs and lists. So blocks may only form at a top-level paragraph. |
| 34 | Input rules check this, and Tiptap's own block chords (Mod-Shift-8, Mod-Alt-c, |
| 35 | and so on) are removed, because nesting a block the serializer cannot write |
| 36 | drops its text from the stored draft. The list grammar is the plain-mode |
| 37 | continuation's in [composer-list-continuation](../../apps/web/src/composer-list-continuation.ts); |
| 38 | keep the two in step. |
| 39 | |
| 40 | Enter sends, so the fence handling in `handleKeyDown` must run before the send |
| 41 | handler: checked afterwards, a fence never opens and a newline inside one sends |
| 42 | the draft. |