Files
typewriter/README.md
Julien Calixte 3128bca4ad docs(readme): sync to bench reality and slim via links
Fix stale claims: hardware IS on bench; git is libgit2/git2 as an
esp-idf component (ADR-004 kill-switch fired), not gix; display is the
in-tree SSD1683 driver; TLS handshake heap is the measured ~35 KB; v0.1
auth is build-time TW_* env vars (ADR-011 open). Replace the hardware
table, Gantt, and "why not" sections with links (docs/hardware.md,
docs/roadmap.md, ADR-001/002); update repo layout from planned to
actual; prune resolved open questions and link postmortems.
2026-07-08 00:04:49 +02:00

152 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Typoena
A distraction-free, hackable, DIY writing machine. ESP32-S3 + e-ink + a real
mechanical keyboard. You write Markdown, you commit, you push. Nothing else
runs on it.
> **Status: pre-MVP, hardware on bench.** Display, USB keyboard, live typing
> with partial refresh, Wi-Fi + TLS, and on-device git push are all verified
> in spikes. No release has shipped yet — v0.1 is blocked on SD (waiting on a
> compatible card), the boot splash, and wiring the save/publish path into the
> app binary. Live per-item status: [`docs/roadmap.md`](docs/roadmap.md) ·
> failure write-ups: [`docs/postmortems/`](docs/postmortems/).
---
## Vision
A single-purpose appliance that boots into a text editor with a Vim keymap,
edits Markdown files, and (optionally) pushes them to a git remote (GitHub
first) over Wi-Fi. No browser, no notifications, no apps. Open lid → write →
push (or don't) → close lid.
Two file scopes coexist on the SD card — formal definitions in
[`CONTEXT.md`](CONTEXT.md):
- **Tracked** — lives in the git working copy, gets **Published** when the
user presses `Ctrl-G`.
- **Local** — never leaves the device. Permanently-private: journal entries,
scratch, things that aren't anyone else's business. There is no "promote
to Tracked" gesture — scope is fixed at file creation.
Same editor, same keymap; the difference is just whether `Ctrl-G` (publish to
the remote) is offered.
---
## Hardware
**ESP32-S3-N16R8** (16 MB flash, 8 MB PSRAM) · **GDEY0579T93** 5.79″ e-ink
strip (792×272, ~2.9:1 — biases the UX toward "current line + recent context",
the writing posture we want) · **Nuphy wired USB keyboard** with the S3 as USB
host · **microSD over SPI** · **USB-C wall power** for the MVP, battery in
v0.8.
Full part table, rationale, and bench status:
[`docs/hardware.md`](docs/hardware.md).
---
## Software stack
**Language: Rust on `esp-idf-rs` (std).** Every stack decision — language, UI
strategy, display, git lib, auth, concurrency, storage, power, keyboard
transport — has an ADR in [`docs/adr.md`](docs/adr.md), including the rejected
alternatives (Ratatui, Gleam + Shore on AtomVM, C/Arduino — ADR-001/002). How
each decision is weighted against the user-facing requirements lives in
[`docs/qfd.md`](docs/qfd.md); the ontology those docs use is defined in
[`GLOSSARY.md`](GLOSSARY.md).
| Layer | Choice | Notes |
| ------------- | --------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| HAL / runtime | `esp-idf-svc`, `esp-idf-hal` | std build: heap, threads, VFS, mbedtls, Wi-Fi stack. |
| Display | Custom SSD1683 driver (`src/epd.rs`) + `embedded-graphics` | Dual-controller 792×272 panel; dirty-rect partial refresh (~630 ms measured). |
| UI layer | Custom thin widget layer | Ratatui's API _shape_ without its char-grid terminal model ([ADR-002](docs/adr.md#adr-002-ui-strategy--custom-widgets-on-embedded-graphics-not-ratatui)). |
| Editor core | Custom, in-tree (`src/editor.rs`) | Modal (Normal / Insert / View / Command), motions, operators + text objects. Plain-ASCII buffer until the v0.2 UTF-8 work. |
| USB host | `esp-idf` TinyUSB bindings | Boot-protocol HID; verified on hardware (Spike 4). |
| Git | **libgit2 via `git2`**, built as an esp-idf component with mbedTLS (`firmware/components/libgit2/`) | `gix` was the original pick but can't push over HTTPS — the [ADR-004](docs/adr.md#adr-004-git-implementation--gitoxide-gix) kill-switch fired. On-device add → commit → push verified. |
| TLS | `mbedtls` via `esp-idf` | GitHub HTTPS with the chain checked against embedded roots; ≈35 KB heap measured during handshake (Spike 6). |
| Auth | HTTPS + GitHub PAT | v0.1 bakes credentials in at build time via `TW_*` env vars; provisioning + at-rest protection is [ADR-011](docs/adr.md#adr-011-credential-provisioning--how-the-pat-reaches-the-device-and-is-protected-at-rest) (open), on-device settings land in v0.9. |
| Filesystem | FAT on SD (`esp_vfs_fat`) | Working copy lives here. Internal LittleFS holds config. |
---
## UX boundaries set by the medium
E-ink is a brutal honesty filter on UI choices. Hard constraints we design
around, not against:
- **No cursor blink.** Kills the panel and the battery.
- **Typing latency target: ≤ 200 ms** from keypress to glyph on screen, using
partial refresh on the affected line only.
- **Full refresh every ~20 partials** to clear ghosting. User-visible flash —
schedule it on pauses (>1 s of no input).
- **No smooth scrolling.** Page-style jumps only.
- **No animations.** Anywhere.
- **Render only changed lines**, not the viewport.
---
## Roadmap
Frequent releases. Each version is a usable artifact, not a checkpoint.
Per-version scope, current `[x]`/`[~]` marks, and the macro-plan Gantt live in
[`docs/roadmap.md`](docs/roadmap.md).
| Version | Theme | One-liner |
| ------------------------------------------------------- | ------------ | -------------------------------------------- |
| [v0.1](docs/roadmap.md#v01--mvp-it-writes-it-pushes--) | MVP | Boots, edits one file, `Ctrl-G` pushes. |
| [v0.2](docs/roadmap.md#v02--vim-navigation--) | Vim nav | Normal/Insert, motions, line numbers. |
| [v0.2.5](docs/roadmap.md#v025--international-input--) | Intl input | US-Intl dead keys: à é ê ç, `'`+space = `'`. |
| [v0.3](docs/roadmap.md#v03--vim-editing--) | Vim edit | `dd yy p`, undo/redo, counts. |
| [v0.4](docs/roadmap.md#v04--visual-mode--ex-commands--) | Visual + ex | `v V`, `:w :q :e` command line. |
| [v0.5](docs/roadmap.md#v05--file-palette--multi-file--) | Files | `Ctrl-P` over `/repo` + `/local`, buffers. |
| [v0.6](docs/roadmap.md#v06--markdown-affordances--) | Markdown | Headings, list continuation, soft-wrap. |
| [v0.7](docs/roadmap.md#v07--search--better-git--) | Search + git | `/` search, `:Gpull`. |
| [v0.8](docs/roadmap.md#v08--power-battery--sleep--) | Power | 18650 + sleep + lid switch. |
| [v0.9](docs/roadmap.md#v09--robustness--) | Robustness | Crash-safe writes, reconnect, settings. |
| [v1.0](docs/roadmap.md#v10--polish--) | Polish | Boot ≤ 3 s, fonts, themes, enclosure, guide. |
| [v1.x](docs/roadmap.md#v1x--stretch--nice-to-have) | Stretch | 10.3″ panel, multi-remote, stats, BLE. |
---
## Repo layout
```
/firmware Rust crate, esp-idf-rs target
(SD card mounted at runtime contains /repo and /local)
/src
main.rs app binary — editor + display + USB (SD/git not wired yet)
editor.rs modal editor core: buffer, modes, keymap, :commands
epd.rs SSD1683 dual-controller e-ink driver
usb_kbd.rs TinyUSB host glue, HID → key events
/bin on-device spike binaries (sd_fat, wifi_tls, git_push,
git_sync, git_smoke)
/components/libgit2 libgit2 as an esp-idf CMake component (mbedTLS);
source vendored as a git submodule
build.rs bakes TW_* env vars (Wi-Fi, PAT, author) — v0.1 config path
/spikes desktop spikes (spike7 git push proof, pre-device)
/docs ADRs, QFD, hardware, roadmap, per-version specs,
spikes.md, postmortems/, notes/
CONTEXT.md project glossary — Tracked / Local / Save / Publish, and
the principles that fall out of them
GLOSSARY.md methodology glossary — the WHAT / Function /
Characteristic / Metric / Target ontology layers
package.json pnpm + oxfmt — formatting toolchain for docs/JSON
```
---
## Open questions / risks (tracked, not yet resolved)
- [ ] SD on the bench: the 133 GB card rejects CMD59; waiting on a genuine
≤32 GB card ([postmortem](docs/postmortems/2026-07-05-spike3-sd-cmd59.md)).
- [ ] Heap fragmentation over a long writing session with the PSRAM allocator.
- [ ] Real-world e-ink ghosting with the current partial-refresh cadence.
Retired risks (gix push, TinyUSB HID stability, TLS heap, libgit2-on-xtensa)
and how they died: [`docs/spikes.md`](docs/spikes.md) and
[`docs/postmortems/`](docs/postmortems/).
These get resolved by writing code, not by deciding harder.