# v0.6 — Markdown affordances

> Part of the [Typoena macro plan](macroplan.md). Requirements and targets:
> [qfd.md](qfd.md). Load-bearing decisions: [adr.md](adr.md).

**Status:** render affordances done early. The snippet engine (tab-stop core,
inline Tab-expansion, hint-on-pause, `$` palette) and the `>` command-palette
generalisation are **done in core, host-tested** (187 editor tests); the
remaining work is the **firmware boot-read + `just init` catalog** (slice 5, the
on-device gate). Snippets are net-new scope, added 2026-07-08; reshaped
2026-07-12 from a hard-coded table into a git-synced, Zed-compatible library with
a `$` palette launcher — see [`typoena-snippets.md`](typoena-snippets.md) for the
file format.

- [x] Heading lines bolded in render (faux-bold double-strike)
- [x] List continuation on Enter inside `- ` / `1. ` (with empty-item exit)
- [x] Blockquote continuation on Enter inside `> ` (nested depth preserved,
      empty-quote exit); a nested list inside a quote degrades to `> `
- [x] Soft-wrap at word boundaries

## Snippets

Trigger-driven text expansion for Markdown authoring (Zed-inspired, but **no
completion popup**: e-ink's ~630 ms refresh rules out a live filtering menu, and
it fights the distraction-free premise). The library is a git-synced,
Zed-compatible JSON file — full file-format reference in
[`typoena-snippets.md`](typoena-snippets.md). This section is the _editor_
behaviour.

- [x] **The tab-stop engine** — the shared core both surfaces drive. A body is
      literal text plus numbered stops `$1 … $n` and a final `$0`; on insertion
      the caret lands on `$1` (or the end), in Insert. Tab advances to the next
      stop, **forward only** (no Shift-Tab); pending stops sit after the caret
      and shift with edits there. The session auto-aborts on Esc, a mode change,
      or a motion that leaves the stops. `${n:label}` parses to a bare `$n` (the
      label is stripped — no selection model to fill); no dynamic values (no RTC,
      so no `date`).
- [x] **Inline Tab-expansion (Insert mode).** If the word immediately before the
      caret matches a snippet prefix, Tab expands it and starts the tab-stop
      session; otherwise Tab inserts spaces as today. A check in the Insert
      handler alongside the existing `list_marker` transform
      (`expand_snippet(word) -> Option<Snippet>`). Tab already arrives as
      `Key::Char('\t')`, so no new key event.
- [x] **Hint-on-pause.** On the typing pause (same throttle as the word-count /
      cursor refresh — never a per-keystroke repaint), if the word before the
      caret is a prefix, the right side panel shows a quiet hint (`» name`, on the
      row above the mode line, sharing the slot with the `NO KBD` flag — the two
      never co-occur since the hint means you're typing). The panel is ~15 cols,
      so the hint is the snippet **name**, not the whole body — the full preview is
      the `$` palette's job. Snapshotted in `refresh_stats` so it rides the pause,
      and the firmware's Insert-pause repaint already carries it (no firmware
      change). Latin-9 has no `↹` glyph, hence the `»` marker.
- [x] **`$` palette (browse + insert).** `Cmd-P` then `$` switches the palette to
      the snippet list (the same sigil mechanism as `>`). Fuzzy-matches name /
      prefix / description; Enter inserts the body at the caret and starts the
      tab-stop session. Lists **all** snippets — the fuzzy filter handles clutter,
      so there's no hidden "inline-only vs palette-only" split. Rows read
      `Name [prefix]`, so browsing also teaches the inline trigger.
- [x] **Boot wiring.** The host reads `/sd/repo/.typoena.snippets.json` at boot
      and calls `Editor::set_snippets` (mirroring `set_prefs`); a missing or
      malformed file is non-fatal (no snippets, editor runs). Parse lives in the
      host-testable `editor` crate via `serde_json` — the one new dependency,
      confirmed to build for xtensa (`cargo check`, firmware 0.6.0). On-device
      smoke-test still pending.

## The palette, generalised (`Cmd-P` · `>` · `$`) — done in core

v0.5 shipped `Cmd-P` = files and `>` = a five-entry settings list (`save_on_idle`,
`format_on_save`, `line_numbers` toggles + `theme`/`auto_sync` rotations). v0.6
makes the sigils a clean split by verb, and the empty-palette placeholder legends
them: `Go to file · > settings · $ snippets`.

- **bare `Cmd-P`** → _navigate_: go to file (unchanged).
- **`>`** → _act on the editor_ — the command palette, a real action registry (the
  `PALETTE_CMDS` list, actions first then settings). The pref toggles are just its
  stateful entries, not a special section. Dispatch is by `PaletteCmd::kind`: a
  **toggle** flips and the list **stays open**; a **one-shot** (`format`,
  `publish` — the latter shares `run_publish` with `:sync`) runs and **closes**; a
  **parameterised** command (`new file...`) morphs the palette into a second
  **input step** (the box becomes a filename prompt → Enter creates it, scope read
  from a `repo/`/`local/` prefix as `:enew` does; backspacing past the start steps
  back to the `>` list). This **retired `:e`** — bare `Cmd-P` covers file-opening;
  dotfiles can get a dedicated `> edit ...` command if/when wanted.
- **`$`** → _insert content_ — the snippet launcher above.

Labels avoid `…`/`↹` — the palette/panel fonts are ISO-8859-15, which has neither
glyph — so `new file...` uses ASCII dots and the pause hint leads with `»`.

## First-time setup — snippet catalog (`just init`) — done

[`just init`](../firmware/README.md#provisioning-an-sd-card) seeds the two
git-tracked config files into `repo/` (so they commit + sync on the device's
first `:sync`), **writing only a file that is absent** so a re-`init` never
clobbers a synced library: a starter [`.typoena.toml`](typoena-toml.md) (the five
keys at their defaults) and a [`.typoena.snippets.json`](typoena-snippets.md)
assembled from a **curated catalog** (`firmware/snippets-catalog/`, `jq`-merged).
The catalog is grouped and opt-in — you pick the groups (each `[Y/n]`, all-yes on
a non-interactive run) rather than getting one writer's whole personal set. Not
every Zed snippet is worth proposing: the Slidev/blog-pipeline ones (`@[youtube]`,
`<v-clicks>`, frontmatter, mermaid) and hyper-specific personal ones are left out,
since a distraction-free prose appliance can't render them and you'd never miss
them. Prefixes are **English** (except `edanso`, the mnemonic for `œ`), bodies
translated from the source Zed snippets. **Three groups, 17 snippets:**

- [x] **Symbols** (inline, the keyboard can't type them): `arrow`→`→`,
      `neq`→`≠`, `times`→`×`, `middot`→`·`, `deg`→`°`, `euro`→`€`, `edanso`→`œ`
      (dead keys in v0.2.5 cover accents, _not_ these). **Caveat:** `→` (U+2192)
      and `≠` (U+2260) are outside ISO-8859-15, so they store correctly and sync
      but render as a missing-glyph box on the device panel until the font is
      extended; the other five are Latin-9 and draw fine. Kept deliberately.
- [x] **Structure**: `todo`→`- [ ] `, `link`→`[$1]($2)$0`, `img`→`![$1]($2)$0`,
      `table` (2-col), `code` (fenced block). `img`/`code` are net-new.
- [x] **Prose / PKM templates** (`${n:label}` stripped to `$n`): `booknotes`,
      `reference` (the English reference block — `refangl` folded in), `bias`,
      `capture`, `standard`. (`5w1h` dropped.)
