"The characters on disk are the characters in the editor. Everything else is a layer painted on top."
One Text, Not Two Documents
There are two ways an editor can hold a document. A WYSIWYG editor holds a rich-text tree: a heading is a node, bold is a formatting mark attached to the text inside a node, and the ** or # characters don't exist as data — they're inferred, and re-emitted as Markdown only when you export. Rekindle does the opposite. Its model is the Markdown source string itself. The # in your heading is a real character in the document, at a real offset. There is one text, and it's the source.
Rendered Look Is a Layer, Not a Model Swap
So how does bold look bold? A decoration layer paints appearance over the source without changing it. The word **fire** stays literally itself in the model, asterisks and all. A decoration renders it bold and dims those asterisks on inactive lines. Nothing is converted; nothing is lost; the source is never abandoned for a second representation.
Model (what's on disk and in the editor): The **fire** catches.
Decoration layer (painted over, not stored): The 𝗳𝗶𝗿𝗲 catches.
# The model never changes. The look is a layer you can toggle off
# and still have byte-identical Markdown underneath.
Why This Matters for Everything Downstream
Choosing source-as-model isn't cosmetic — it decides the architecture. Because the model is plain Markdown text, the file on disk is the truth (Track 7), a diff is a real text diff, a grep finds real characters, and every soul feature (voice underline, CMD+K) operates on offsets into source rather than nodes in a proprietary tree. The whole editor has exactly one representation to reason about. WYSIWYG's rich-text tree would force a translation layer between what you see, what you store, and what Pippa reads — three representations to keep in sync instead of one.
Code
One model, a look painted on top·text
Model (on disk AND in the editor): The **fire** catches.
Decoration layer (painted over, not stored): The 𝗳𝗶𝗿𝗲 catches.
# Toggle every decoration off and you still have byte-identical Markdown.
# There is ONE representation. WYSIWYG would give you three:
# what you see != what you store != what Pippa reads.
Soul features operate on source offsets, not tree nodes·typescript
// Because the model IS the source string, a voice-rule match is just
// a range of character offsets — no proprietary node walk required.
type VoiceIssue = { from: number; to: number; note: string };
function detectVoiceIssues(doc: string): VoiceIssue[] {
// scan the literal source text; return character ranges to underline
// (Track 4 turns these ranges into CM6 decorations)
return scan(doc);
}
// A WYSIWYG tree would force every feature to translate between the tree,
// the rendered view, and the Markdown export. Source-as-model deletes that tax.
Open a note in an editor you use and ask: if I saved this and opened the raw file in a plain text viewer, would its characters be the same characters the editor lets me edit — the same ones I see when I put my cursor on a line or switch to its source view — or a translation of them? If the same, you're in a source model (a rendered look painted on top, like Live Preview, doesn't change that). If translated (or binary), you're in a rich-text/WYSIWYG model. Notice which one makes diff, grep, and version control feel natural.
Hint
The tell: can you meaningfully git diff two versions and read the change as text? Source models diff cleanly; rich-text/binary models produce diffs you can't read, because the stored form isn't the form you edited.
Progress
Progress is local-only — sign in to sync across devices.