Editing heuristics for Koto

Research notes on UX patterns from git-backed CMS, markdown WYSIWYG editors, and writing tools — translated into principles and prioritized recommendations for Koto.

Context

Koto is a markdown CMS with:

Heuristics below are filtered through this context. "Good for Notion" or "good for Obsidian" isn't automatically good for Koto.


Core heuristics

1. Round-trip is sacred

If the editor parses something and re-emits it, the output must equal the input byte-for-byte for unchanged regions. When true lossless isn't possible, preserve the construct as opaque rather than reformatting it.

2. Frontmatter belongs in its own pane

Mixing YAML editing into the visual flow is how you get writers saving invalid front matter. GitLab's Static Site Editor, Front Matter CMS (VSCode), and Decap CMS all separate it.

3. Drafting mode ≠ editing mode ≠ review mode

iA Writer's distinction: Focus mode is for producing words; turn it off when you're shaping what's there. A good CMS acknowledges the phases:

Koto: has focus mode toggle. ✓ Gap: no outline/TOC view; no review/diff mode on editor side (relies on GitHub PR review). Low priority for now, but outline view is cheap to add (scan headings, render nav).

4. Two input modalities: markdown shortcuts and slash commands

Power users type # , - , >; newcomers want a discoverable menu. Supporting both costs little — Tiptap StarterKit already provides the input rules; a slash menu is a separable extension.

5. Paste is the single highest-leverage feature

Contributors paste from Google Docs, Notion, Confluence, Slack, browser address bar. Broken paste makes the whole editor feel broken.

Must-have behaviors:

Koto gap: using Tiptap defaults, which cover HTML→markdown imperfectly. URL- on-selection works via the link mark. Image paste is the biggest gap (see #6).

6. Images need a declared policy, then automate it

Three honest options:

Policy Where images live Tradeoff
Same-repo docs/foo/image.png Fully versioned; bloats git; best for small volumes
External bucket R2 / S3 / Cloudflare Images Keeps git lean; adds moving part; URL refs decouple
Base64 inline inside the markdown Only ok for tiny icons; don't use for photos

For a VitePress-targeted site with contributor volume ≤ low hundreds of images, same-repo is usually right. That means:

Koto gap: no image ingest at all. This is the single biggest "feels incomplete" gap for non-technical editors.

7. Save boundaries should map to user mental models

Users think in two grains:

  1. "I'm actively working on this piece" — needs not-losing-work, not every keystroke in git
  2. "I'm done for now" or "this is ready to look at" — needs an explicit checkpoint

Good mapping:

Koto: only has explicit save (commit). No local draft persistence — so if the browser crashes mid-edit, work is lost until the last commit.

Recommendation: add local draft persistence keyed by repo + branch + path. On re-open, if the on-disk file matches what we committed but the local draft is newer, show a "restore draft?" banner. ~½ day.

8. Make the git model invisible when possible

Every "branch", "PR", "ready for review" button forces the editor to learn a model they don't have.

Tier 1 — editors should see:

Tier 2 — power affordances, hidden behind a menu:

Koto: currently exposes branches/PRs at Tier 1. Mostly OK because the current audience is still "editors who understand git". Rename the labels to task language as a cheap win:

9. Detect conflicts, don't resolve them in-browser

Three-way merge in a browser WYSIWYG is a bad experience. Better:

  1. Every GET returns a SHA
  2. Every PUT sends the SHA
  3. On 409 conflict: stop, show the user "this file was changed by someone else; you can overwrite, or discard your edits and reload"

Koto: already sends SHA on save. ✓ Gap: the 409 path in the frontend isn't surfaced with clear recovery actions; today it just bubbles as an error toast.

10. Cross-document links need autocomplete

VitePress uses [text](./path.md). Obsidian uses [[Note Name]] with auto-complete from the vault and auto-backlinks.

For Koto's VitePress target, the right primitive is the markdown link, but with an autocomplete that scans the tree (we already have /api/tree) and suggests paths when the user types ]( or [[.

Koto gap: no suggestion. Medium value for docs-heavy repos, low for small sites.

11. Keep a "view source" escape hatch

When the WYSIWYG hides a bug or fails a round-trip, writers need to see the markdown. The escape can be simple:

Koto gap: no raw view. ~2h to add, huge debuggability payoff.

12. Keyboard contracts are invariants

Writers across tools expect:

Koto: Cmd+S wired ✓, StarterKit gives the formatting shortcuts ✓, no find/replace. Find is a common ask once docs get long; defer until writers complain.

13. Don't surprise the user with auto-transforms

Tiptap input rules can silently rewrite text (smart quotes, em-dashes, ellipsis). Useful for some audiences, alarming for others (e.g., technical writers copy-pasting code examples).

Koto: StarterKit defaults are mild. Decision: no additional auto-transforms by default. If writers want smart punctuation, make it an opt-in.


Tool comparison (at a glance)

Aspect Koto (today) iA Writer Decap Tina Obsidian
Primary surface WYSIWYG (Tiptap) plain markdown WYSIWYG inline live-preview dual (source + preview)
Frontmatter separate pane inline YAML form fields form fields inline
Slash menu ✓ (palette, not inline)
Image upload drag-drop (local) Git commit Git commit paste to vault
Autosave (local) continuous continuous continuous continuous
Git commit grain per save n/a per save per save per-interval batch
Conflict detect SHA check n/a SHA check SHA check filesystem mtime
Cross-link autocomplete partial ✓ wikilinks
Raw source view n/a (is the view) toggle toggle
Focus mode

Gap analysis for Koto

Ranked by (user-pain × frequency) / (cost to fix):

# Gap Pain Freq Cost Score
1 No image upload high high medium 🔴
2 No local draft persistence high (when it strikes) rare but catastrophic low 🔴
3 Raw YAML frontmatter, no typed fields medium high medium 🟠
4 No "view source" toggle medium medium low 🟠
5 No slash menu medium (non-tech writers) medium medium 🟠
6 409 conflict UX unclear medium rare low 🟡
7 Git labels surface too much model low ambient very low 🟡
8 No cross-document link autocomplete low–medium depends on site size medium 🟡
9 No outline/TOC nav low medium low 🟢
10 No paste-from-Google-Docs normalizer medium varies high 🟢

Recommended next moves, ordered

  1. Local draft persistence (½ day) — single file useDraftPersistence.svelte.ts, keyed by repo+branch+path, uses localStorage, restore-or-discard banner on file open.
  2. "View source" toggle (2h) — textarea alternative for the editor body, toggle button, same draftContent state.
  3. Image upload policy + endpoint (1–2d) — POST /api/asset in DO that base64-commits to the working branch, frontend paste/drop handler, asset path inferred from current file's directory.
  4. Typed frontmatter fields (1d for basic schema support) — read a frontmatter section from .koto.json declaring fields; fall back to raw YAML if not declared.
  5. Slash menu (1d) — Tiptap suggestion extension, 10–15 items including Markdoc callouts.
  6. Rename git-surface labels (30min) — cosmetic but meaningful for non-dev editors.
  7. Better 409 UX (½d) — dedicated modal with reload/overwrite/discard choices.

Items 8–10 are nice-to-haves and can wait until a content-heavy pilot surfaces their need.


Progress & deferrals — 2026-04-24

Everything 🔴 🟠 🟡 from the gap table above has been built. Both 🟢 items (Outline/TOC and Google Docs paste normalizer) are deliberately deferred — not because they'd be bad, but because the right trigger for building them is a real user with a concrete example, not speculation:

Stance: don't decide the next editor feature in a vacuum. Ship what's built, watch what real users hit first, let their pain rank the backlog.

What to explicitly not do (yet)


Sources