feat(editor): add .typoena.toml prefs and palette settings (v0.5 slice 4)
Git-tracked editor preferences read at boot and toggled live on-device: - Prefs type (line-based TOML parse/serialize, no crate on xtensa) held on Editor; firmware reads /sd/repo/.typoena.toml before the first render and falls back to per-key defaults. Keys: save_on_idle, format_on_save, line_numbers (bool) + auto_sync (string, schema/default only until v0.7). - line_numbers applied live (gutter_cols -> 0 when off). - Palette > command mode toggles the three bools; the list stays open so several flip in one visit, and :settings opens it directly. Each toggle applies live and queues Effect::SavePrefs (host atomic-writes the file, which rides the next :sync). - save_on_idle honoured host-side as a silent, unformatted idle auto-save. - to_toml is newline-free; save_path now appends exactly one terminator unconditionally so buffers round-trip byte-for-byte (trailing blanks kept). - Firmware 0.4.0 -> 0.5.0; new docs/typoena-toml.md reference. Also refreshes the slice-3 macroplan status (delete fix was confirmed on device).
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
| [`v0.1-mvp-product.md`](v0.1-mvp-product.md) | v0.1 product design — boot, type one file, `Ctrl-S` to save, `Ctrl-G` to publish. |
|
||||
| [`v0.1-mvp-technical.md`](v0.1-mvp-technical.md) | v0.1 technical design — single Rust binary on `esp-idf-rs`, modules, threads, bring-up order. |
|
||||
| [`macroplan.md`](macroplan.md) | Version-by-version plan; each release is a usable artifact, not a checkpoint. |
|
||||
| [`typoena-toml.md`](typoena-toml.md) | `.typoena.toml` reference — the git-tracked editor preferences (auto-save, format-on-save, line numbers, auto-sync). |
|
||||
| [`hardware.md`](hardware.md) | Part choices for the bench build and the rationale behind them. |
|
||||
|
||||
## Quality method
|
||||
|
||||
@@ -356,8 +356,8 @@ no ghosting** (user flashed it and eyeballed the full-area partial the palette
|
||||
forces); Cmd-P opens it on-device too. Remaining v0.5 slice: 4 prefs +
|
||||
palette command mode.
|
||||
|
||||
**Slice 3 (`:enew` + delete) COMPLETE in core 2026-07-12, HOST-TESTED not yet
|
||||
on-device.** `:enew <name>` creates a new file: empty, active, marked **dirty**
|
||||
**Slice 3 (`:enew` + delete) COMPLETE + CONFIRMED ON DEVICE 2026-07-12**
|
||||
(committed `c9c0716`). `:enew <name>` creates a new file: empty, active, marked **dirty**
|
||||
so eviction/`:w` persists it, and added to the in-core file list so the palette
|
||||
finds it without a disk re-enumeration — no card IO until it is saved. `:delete`
|
||||
unlinks the **current** file (a new `Effect::Delete` the host services), then
|
||||
@@ -377,9 +377,48 @@ which removes index entries whose working-tree file is gone — together they ar
|
||||
the scoped file and, for a Tracked file, that it is local until `:sync`
|
||||
(`deleted repo/notes.md - :sync to publish`). Deferred to later: greying the
|
||||
Publish affordance for a Local buffer, and the multi-file publish count. 123
|
||||
editor tests + 28 keymap tests pass; the no-git firmware binary builds clean (the
|
||||
`update_all` fix is behind `--features git`, unbuildable locally — on-device
|
||||
re-test pending).
|
||||
editor tests + 28 keymap tests pass; the no-git firmware binary builds clean. The
|
||||
`update_all` fix (behind `--features git`, unbuildable locally) was **verified on
|
||||
device 2026-07-12** — `:enew test.txt` → `:sync` → `:delete` → `:sync` removed
|
||||
test.txt from origin.
|
||||
|
||||
**Slice 4 (`.typoena.toml` prefs + palette `>` command mode) COMPLETE in core
|
||||
2026-07-12, HOST-TESTED not yet on-device.** A `Prefs` type (host-testable
|
||||
line-based TOML parse/serialize — flat `key = value` bools + one string with `#`
|
||||
comments, no crate pulled onto xtensa) lives on `Editor`; the host reads
|
||||
`/sd/repo/.typoena.toml` at boot and applies it before the first render, and a
|
||||
missing/partial file falls back to per-key defaults. Keys: `save_on_idle`,
|
||||
`format_on_save`, `line_numbers` (all bool, default on) and `auto_sync`
|
||||
(string, default `"10m"`, **schema + default only** — nothing reads it yet).
|
||||
`line_numbers` is live: `gutter_cols()` returns 0 when off, so the text reclaims
|
||||
the gutter's columns (the `gutter - 1` field width made saturating to avoid the
|
||||
underflow). The palette `>` command mode (VS Code semantics — a leading `>` in
|
||||
the query switches file search to the command list) exposes the three booleans
|
||||
as live toggles; Enter flips the pref, applies it at once, queues a new
|
||||
`Effect::SavePrefs` (the editor serializes; the host does the atomic write to
|
||||
`.typoena.toml`, which rides the next `:sync` to other devices), and confirms
|
||||
the new state on the snackbar. **The list stays open after a toggle** so several
|
||||
prefs flip in one visit (Esc/`Cmd-P` closes); **`:settings` opens the palette
|
||||
straight into `>` mode** as a one-command shortcut (both requested by the user
|
||||
2026-07-12, chosen over a separate settings modal — same surface, no duplicate
|
||||
machinery). **Three "decide before build" calls:** (1) the
|
||||
idle auto-save is **unformatted** — `:fmt` runs only on explicit `:w`/`:sync`, so
|
||||
tables/blank-lines are never reflowed mid-session; (2) the per-device `auto_sync`
|
||||
override (card-local `typoena.conf`) is **deferred** — auto_sync is inert in
|
||||
v0.5, so there is nothing yet to override; (3) `> auto sync: <dur>` as a palette
|
||||
command is **deferred to v0.7** — a control that changes a value nothing reads
|
||||
would be a dead switch. `save_on_idle` is honoured host-side: a silent idle
|
||||
auto-save (no snackbar, no forced e-ink flash — a safety net, not an action)
|
||||
fires once per typing burst after a 1.5 s pause. 141 editor tests + 28 keymap
|
||||
tests pass; the no-git firmware binary builds clean. Firmware bumped **0.4.0 →
|
||||
0.5.0** (the v0.5 feature set is met). **Boot-read of the prefs file CONFIRMED ON
|
||||
DEVICE 2026-07-12** — a `.typoena.toml` in `typoena-test` with non-default values
|
||||
(`save_on_idle=false`, `line_numbers=false`, `auto_sync="5m"`) logged back
|
||||
`prefs: Prefs { save_on_idle: false, format_on_save: true, line_numbers: false,
|
||||
auto_sync: "5m" }` at boot, a byte-exact parse (comments skipped, bools + quoted
|
||||
string read). Remaining gate (still pending): the palette `>` live toggle +
|
||||
`SavePrefs` write-back, and the `save_on_idle` autosave (this test ran with it
|
||||
off).
|
||||
|
||||
- [x] `Cmd-P` opens fuzzy file palette over **both** `/sd/repo/` and
|
||||
`/sd/local/` — **landed and CONFIRMED ON DEVICE 2026-07-12** (Spike 11: no
|
||||
@@ -403,7 +442,8 @@ re-test pending).
|
||||
`stage_and_commit` now also runs `update_all(["*"])` (`git add -u`) — the
|
||||
two together are `git add -A`. A Local file is just unlinked. The snackbar
|
||||
now confirms the delete and flags that a Tracked file needs `:sync`.
|
||||
**On-device re-test of the fix pending** (build is `--features git`).
|
||||
**Verified on device 2026-07-12** — the `:enew`→`:sync`→`:delete`→`:sync`
|
||||
cycle removed test.txt from origin.
|
||||
- [~] `Ctrl-G` is disabled / hidden when the current buffer is local-scope —
|
||||
**`:sync` / Publish is blocked in-core for a Local buffer** (posts "Publish
|
||||
unavailable (Local)"); the side-panel affordance that hides/greys the
|
||||
@@ -411,25 +451,31 @@ re-test pending).
|
||||
- [ ] The side panel briefly shows file count on `Ctrl-G` when the publish bundles
|
||||
more than one dirty Tracked file (e.g. `"publishing 3 files: abc1234"`),
|
||||
so workspace-scoped behaviour stays visible to the user
|
||||
- [ ] **Preferences file** `/sd/repo/.typoena.toml` — a git-tracked,
|
||||
- [x] **Preferences file** `/sd/repo/.typoena.toml` — a git-tracked,
|
||||
hand-editable TOML file for editor behaviour, deliberately **distinct from
|
||||
the `/sd/typoena.conf` card secrets** (Wi-Fi / PAT / remote / author,
|
||||
gitignored, never committed — see v0.1). Read at boot; a missing file or
|
||||
key falls back to the defaults below. Keys:
|
||||
- [ ] `save_on_idle` (bool, default `true`) — auto-save the current buffer on
|
||||
the existing idle pause (the ≥ 1 s typing-pause the panel already uses
|
||||
for its refresh), so `:w` becomes optional rather than required.
|
||||
- [ ] `format_on_save` (bool, default `true`) — run `:fmt` (table alignment,
|
||||
key falls back to the defaults below. **Core done 2026-07-12** (a `Prefs`
|
||||
type on `Editor`, host-testable parse/serialize, applied via
|
||||
`Editor::set_prefs` before the first render); full reference:
|
||||
[`typoena-toml.md`](typoena-toml.md). Keys:
|
||||
- [x] `save_on_idle` (bool, default `true`) — auto-save the current buffer on
|
||||
the idle typing-pause, so `:w` becomes optional rather than required.
|
||||
**Honoured host-side** as a *silent* save (no snackbar, no forced e-ink
|
||||
flash — a safety net, not an action), unformatted, once per typing burst
|
||||
after a 1.5 s pause.
|
||||
- [x] `format_on_save` (bool, default `true`) — run `:fmt` (table alignment,
|
||||
blank-line collapse, trailing-whitespace strip) on the buffer before it
|
||||
is persisted, so `:sync` is **fmt → save → commit → push** and `:w`
|
||||
saves formatted. Implemented in-core 2026-07-11 (`Editor::format_on_save`,
|
||||
default on); this key will drive it. **Open question:** with
|
||||
`save_on_idle` also on, this reformats on every idle pause — reflowing
|
||||
tables / collapsing blanks mid-session. Consider limiting fmt to
|
||||
explicit `:w`/`:sync` and leaving the idle auto-save unformatted.
|
||||
- [ ] `line_numbers` (bool, default `true`) — show the absolute line-number
|
||||
saves formatted. Implemented in-core 2026-07-11 (`Editor`), now **driven
|
||||
by this key**. **Open question RESOLVED (2026-07-12):** fmt runs only on
|
||||
an explicit `:w`/`:sync`; the `save_on_idle` auto-save is deliberately
|
||||
**unformatted**, so tables/blank lines are never reflowed mid-session
|
||||
(the caret would jump under you on every thinking pause).
|
||||
- [x] `line_numbers` (bool, default `true`) — show the absolute line-number
|
||||
gutter (built always-on in v0.2). Off reclaims the gutter's columns for
|
||||
text; the palette `> line numbers: on/off` command toggles it live.
|
||||
text (`gutter_cols()` → 0); the palette `> line numbers: on/off` command
|
||||
toggles it live. **Done 2026-07-12.**
|
||||
- [ ] `auto_sync` (duration string, default `"10m"`; `"0"` / omitted
|
||||
disables; **min clamp ~`"2m"`** so a palette typo can't drain the
|
||||
battery) — a *max-staleness cap*, not a wall-clock timer:
|
||||
@@ -440,18 +486,26 @@ re-test pending).
|
||||
`save_on_idle` already owns local data safety — so 10 min halves the
|
||||
sync energy of a 5-min default for no real risk. Full derivation:
|
||||
[`tradeoff-curves/wifi-auto-sync.md`](tradeoff-curves/wifi-auto-sync.md).
|
||||
The **schema + defaults live here in v0.5**; the periodic side rides the
|
||||
better-git work (v0.7) and must interact with light / deep sleep (v0.8).
|
||||
- [ ] Open question: because the file is committed, these prefs **sync to
|
||||
every device** that clones the repo — a per-device sync cadence may
|
||||
instead want a card-local override (in `typoena.conf`). Decide before
|
||||
build.
|
||||
- [ ] **Palette command mode** — typing `>` at the `Ctrl-P` palette switches it
|
||||
from file search to a command list (VS Code-style). The v0.5 commands edit
|
||||
the `.typoena.toml` prefs above — e.g. `> save on idle: on/off` and
|
||||
`> auto sync: 10m` — writing the value back to the file and applying it
|
||||
live. This command list is the discoverable surface that later actions
|
||||
(`:fmt`, theme, font) also register into.
|
||||
The **schema + default (`"10m"`) live here in v0.5** and round-trip
|
||||
through `Prefs`; **nothing reads the value yet** — the periodic side
|
||||
rides the better-git work (v0.7) and must interact with light / deep
|
||||
sleep (v0.8). Marked `[~]`: parsed and preserved, no behaviour.
|
||||
- [x] Open question RESOLVED (2026-07-12): the per-device sync cadence override
|
||||
(a card-local `typoena.conf` layer over the committed prefs) is
|
||||
**deferred** — `auto_sync` is inert in v0.5, so there is nothing yet to
|
||||
override; revisit when v0.7 makes the periodic push real.
|
||||
- [x] **Palette command mode** — typing `>` at the `Cmd-P` palette switches it
|
||||
from file search to a command list (VS Code-style). **Done in core
|
||||
2026-07-12.** The v0.5 commands toggle the three boolean `.typoena.toml`
|
||||
prefs — `> save on idle`, `> format on save`, `> line numbers` — each label
|
||||
carrying its live state; Enter flips the pref, applies it at once, queues
|
||||
`Effect::SavePrefs` (persist to the file), and confirms on the snackbar.
|
||||
**The list stays open after a toggle** (flip several, Esc/`Cmd-P` closes),
|
||||
and **`:settings` opens it directly** — both added 2026-07-12 as the "change
|
||||
config from the device" surface (chosen over a separate settings modal).
|
||||
This command list is the discoverable surface later actions (`:fmt`, theme,
|
||||
font) also register into. **`> auto sync: <dur>` deferred to v0.7** — a
|
||||
value control that changes nothing readable would be a dead switch.
|
||||
|
||||
## v0.6 — Markdown affordances — [~]
|
||||
|
||||
|
||||
149
docs/typoena-toml.md
Normal file
149
docs/typoena-toml.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# `.typoena.toml` — editor preferences
|
||||
|
||||
> The git-tracked file that controls how the editor behaves — auto-save,
|
||||
> format-on-save, and the line-number gutter. Hand-editable, or toggled live
|
||||
> from the `Cmd-P` palette. Landed in **v0.5** (see
|
||||
> [`macroplan.md`](macroplan.md)).
|
||||
>
|
||||
> **Not to be confused with `/sd/typoena.conf`** — that holds the device
|
||||
> *secrets* (Wi-Fi, PAT, remote URL, commit author), is gitignored, and is never
|
||||
> committed. `.typoena.toml` is *behaviour*, shared across devices; `typoena.conf`
|
||||
> is *secrets*, per-device. See [v0.1 product](v0.1-mvp-product.md).
|
||||
|
||||
## Location
|
||||
|
||||
```
|
||||
/sd/repo/.typoena.toml
|
||||
```
|
||||
|
||||
It lives inside the Tracked repo (`/sd/repo`), so it is **committed and pushed**
|
||||
like any note — which means the preferences **sync to every device** that clones
|
||||
the repo. That is deliberate: your editor behaviour follows you. (A per-device
|
||||
override for the one genuinely device-specific key, `auto_sync`, may layer on top
|
||||
later via `typoena.conf` — deferred until `auto_sync` actually does something in
|
||||
v0.7. See the [auto_sync](#auto_sync) note.)
|
||||
|
||||
The file is read **once at boot**, before the first screen is drawn (so
|
||||
`line_numbers` shapes the opening frame). A **missing, empty, or partial file is
|
||||
fine** — every absent key falls back to its default below, so a fresh card just
|
||||
works with no config present.
|
||||
|
||||
## Keys
|
||||
|
||||
| Key | Type | Default | Effect |
|
||||
| --- | --- | --- | --- |
|
||||
| `save_on_idle` | bool | `true` | Auto-save the current buffer on the idle typing-pause, so `:w` is optional. |
|
||||
| `format_on_save` | bool | `true` | Run `:fmt` on the buffer before an explicit `:w`/`:sync`. |
|
||||
| `line_numbers` | bool | `true` | Show the absolute line-number gutter. Off reclaims its columns for text. |
|
||||
| `auto_sync` | string | `"10m"` | Max-staleness cap for opportunistic auto-publish. **Schema only in v0.5 — no behaviour yet.** |
|
||||
|
||||
### Example
|
||||
|
||||
```toml
|
||||
# Typoena editor preferences — hand-editable, git-tracked.
|
||||
# Edit here, or toggle live from the Cmd-P palette (type `>`).
|
||||
save_on_idle = true
|
||||
format_on_save = true
|
||||
line_numbers = true
|
||||
auto_sync = "10m"
|
||||
```
|
||||
|
||||
### `save_on_idle`
|
||||
|
||||
When on, the firmware quietly persists a dirty, named buffer once typing has
|
||||
paused (~1.5 s), so a power pull can't cost more than the last couple of seconds
|
||||
of writing. It is a **safety net, not an action**:
|
||||
|
||||
- **Silent.** No snackbar, no forced screen refresh. A visible confirmation on
|
||||
every pause would cost a ~630 ms e-ink flash purely to say "saved" — exactly
|
||||
the gratuitous flashing the panel avoids elsewhere. `:w` remains the *loud*
|
||||
save (it posts `saved`).
|
||||
- **Unformatted.** The idle save never runs `:fmt` — see the
|
||||
[format_on_save](#format_on_save) note for why.
|
||||
- Fires **once per typing burst**; a failed save doesn't retry-storm (it's kept
|
||||
in RAM and re-attempted on the next burst, or on `:w`).
|
||||
|
||||
### `format_on_save`
|
||||
|
||||
Runs `:fmt` — table alignment, blank-line collapse, trailing-whitespace strip —
|
||||
on the buffer *before* it is persisted, so `:sync` is **fmt → save → commit →
|
||||
push** and `:w` saves formatted.
|
||||
|
||||
**Formatting only happens on an explicit `:w`/`:sync`.** The `save_on_idle`
|
||||
auto-save is deliberately left unformatted: if it reformatted on every idle
|
||||
pause, tables would reflow and blank lines collapse *mid-session*, with the caret
|
||||
jumping under you every time you paused to think. Formatting is a deliberate act;
|
||||
the safety-net save is not.
|
||||
|
||||
### `line_numbers`
|
||||
|
||||
Shows the absolute line-number gutter (built always-on in v0.2). Turning it off
|
||||
returns the gutter's columns to the text, so prose gets the full writing width.
|
||||
Applied **live** — toggling it from the palette redraws immediately with (or
|
||||
without) the gutter.
|
||||
|
||||
### `auto_sync`
|
||||
|
||||
A duration string (`"10m"`, `"2m"`, `"0"`/empty to disable) that will one day cap
|
||||
how stale the published copy is allowed to get — an *opportunistic, rate-limited*
|
||||
push, not a wall-clock timer. **In v0.5 this is schema + default only:** the value
|
||||
is parsed, preserved through a round-trip, and shown nowhere editable — **nothing
|
||||
reads it yet.** The periodic push itself rides the better-git work in v0.7 and
|
||||
must interact with sleep in v0.8. Rationale for the `"10m"` default:
|
||||
[`tradeoff-curves/wifi-auto-sync.md`](tradeoff-curves/wifi-auto-sync.md).
|
||||
|
||||
## Editing it
|
||||
|
||||
Two ways, both landing in the same file:
|
||||
|
||||
1. **By hand** — it's plain text on the card; edit it on your computer and reboot
|
||||
to apply. (The palette hides dotfiles, but you can still open it in-editor with
|
||||
`:e repo/.typoena.toml`.)
|
||||
2. **Live, from the device** — open the settings list either way:
|
||||
- **`:settings`** — drops you straight into it, or
|
||||
- **`Cmd-P`** then type **`>`** — switches the file palette to the command
|
||||
list (VS Code semantics).
|
||||
|
||||
The three boolean prefs appear as toggles carrying their current state:
|
||||
|
||||
```
|
||||
> save on idle: on
|
||||
format on save: on
|
||||
line numbers: on
|
||||
```
|
||||
|
||||
`Ctrl-N`/`Ctrl-P` move the selection; **Enter** flips the selected pref,
|
||||
applies it at once, writes the change back to `.typoena.toml`, and confirms
|
||||
the new state on the snackbar (e.g. `line numbers: off - saved`). **The list
|
||||
stays open** so you can flip several prefs in a row; **Esc** (or `Cmd-P`)
|
||||
closes it. Each change rides the next `:sync` to your other devices.
|
||||
|
||||
`auto_sync` is **not** a palette command in v0.5 (it has no behaviour to
|
||||
drive yet); it returns as a value command in v0.7.
|
||||
|
||||
## Parsing
|
||||
|
||||
The reader is a deliberately tiny **line-based** parser, not a general TOML
|
||||
library — the file is flat `key = value` pairs (a bool, or a quoted string) with
|
||||
`#` comments, so a full TOML crate isn't worth pulling onto the firmware build.
|
||||
It lives in the host-testable `editor` crate (`Prefs::parse` / `Prefs::to_toml`).
|
||||
Rules:
|
||||
|
||||
- A `#` starts a comment to end of line (whole-line or trailing).
|
||||
- Blank lines and lines without `=` are ignored.
|
||||
- An **unrecognized key** is ignored; an **unparseable value** (e.g.
|
||||
`save_on_idle = yes`) leaves *that key* at its default rather than reading as
|
||||
`false`.
|
||||
- Any key not present falls back to its default, so partial files are valid.
|
||||
|
||||
Because `Prefs::to_toml` round-trips with `Prefs::parse`, a palette edit rewrites
|
||||
the whole file in canonical form (with the header comment) — hand-added comments
|
||||
elsewhere in the file are not preserved across a palette toggle.
|
||||
|
||||
## See also
|
||||
|
||||
- [`macroplan.md`](macroplan.md) — v0.5 scope and the decisions behind these keys.
|
||||
- [`v0.1-mvp-product.md`](v0.1-mvp-product.md) — the `typoena.conf` device secrets
|
||||
this file is kept separate from.
|
||||
- [`tradeoff-curves/wifi-auto-sync.md`](tradeoff-curves/wifi-auto-sync.md) — why
|
||||
`auto_sync` defaults to 10 minutes.
|
||||
Reference in New Issue
Block a user