nEmacs v1.0 — Structural Editor + REPL Runtime Surface
First-party on-device program #4 (ADR-0042).
This document is the single landing page for nEmacs as it ships. It cross-references the canonical ADRs rather than restating their contents, per CLAUDE.md Spec Hygiene Rule 1. Its sibling program REPL (repl.md) is the same subsystem’s other buffer type (ADR-0016).
What ships in v1.0
Section titled “What ships in v1.0”A unified subsystem with two buffer types — structural editor and REPL — sharing a Lisp-resident keymap, a tree-cursor buffer model, context-polymorphic dispatch, and CIPHER-LINE rendering. Per ADR-0016 §1, the editor and REPL are not two subsystems: they are one.
| Capability | Status | Source | Tests |
|---|---|---|---|
| Lifecycle (init / shutdown / activation) | Shipped | nemacs_init, nemacs_shutdown, nemacs_enter_editor, nemacs_enter_repl, nemacs_exit | test_nemacs_runtime |
Five mode scopes (:nemacs-nav, :nemacs-literal, :repl-prompt, :repl-history, :grab) | Shipped | NemacsMode enum + nemacs_set_mode | test_nemacs_runtime |
Keymap as Fe alist, mode-scoped, with :tap / :double-tap / :long-press slots (ADR-0016 §2 + §9) | Shipped | nemacs_define_key, nemacs_lookup_key, nemacs_dispatch; Lisp surface keymap-modes / keymap-get / keymap-set / keymap-mode | test_nemacs_runtime, test_keymap_dispatch |
| Tree-cursor navigation (CAR / CDR / BACK; ADR-0016 §3) | Shipped | nemacs_nav_car, nemacs_nav_cdr, nemacs_nav_back | test_nemacs_runtime |
| Structural insertion (leaf, list, grab cut/paste) | Shipped | nemacs_insert_leaf, nemacs_insert_list, nemacs_grab_cut, nemacs_grab_paste | test_nemacs_runtime |
| Nokia multi-tap alpha entry (ADR-0016 §6) | Shipped | nokia.c; nemacs_literal_keypress, nemacs_literal_tick, nemacs_commit_literal | test_nokia, test_nemacs_runtime |
prompt-text modal FFI (ADR-0016 §8) | Shipped | nemacs_prompt_begin / _complete / _committed / _result / _end | test_nemacs_runtime |
| T9 prediction ranker with cart contributions (ADR-0009 + ADR-0016 §7) | Shipped | t9_rank.c; nemacs_palette_request, nemacs_extend_grammar, nemacs_extend_vocabulary, nemacs_record_token | test_t9_prediction |
| CIPHER-LINE composition (modeline + palette + echo) on rows 1–4 (ADR-0016 §4) | Shipped | nemacs_compose_modeline, _palette_a, _palette_b, _echo, nemacs_render | test_nemacs_runtime |
| REPL evaluation, prompt, history (ADR-0002, ADR-0016 §1) | Shipped | nemacs_repl_set_prompt, _eval, _last_result, _history_* | test_nemacs_runtime |
| Buffer persistence — serialize / load (GWP-316 ship gate) | Shipped | nemacs_buffer_serialize, nemacs_buffer_load | test_nemacs_persistence |
| Cipher pause/resume on entry/exit | Shipped | cipher_pause / cipher_resume wired in nemacs_enter_* / nemacs_exit | test_nemacs_runtime |
| Nokia double-tap suppression seam | Shipped | input_set_suppress_double_tap toggled on mode change | test_nemacs_runtime |
Persistence (GWP-316)
Section titled “Persistence (GWP-316)”The structural editor buffer lives in the editor Fe arena. To survive an nEmacs session boundary (SYS-save, cart unload, power cycle), the buffer must round-trip through a textual form: a printed s-expression. The host (cartridge save file, deck state blob, future SD-card .lsp file) owns the bytes; nEmacs only owns the (serialize, load) pair.
uint16_t nemacs_buffer_serialize(char *out, uint16_t out_cap);bool nemacs_buffer_load(const char *text);Contract:
- Serialize writes a NUL-terminated printable s-expression and returns the payload length.
- Empty buffer serializes as
()(length 2). - Overflow returns 0 and clears
outto the empty string. - Load parses with the editor Fe context’s reader, replaces the editor root, and resets the cursor to
(root, 0). Returnstrueon parse success. - A serialize → load round-trip preserves structural shape. Symbol identity is preserved by Fe’s intern-by-name semantics.
Cap: NEMACS_BUFFER_SERIALIZE_MAX = 4096 bytes — generous for any cart authored within the 32 KB editor arena (printed form is roughly 1:1 with arena form for typed code).
The host integration story (where the bytes end up — cart save file, deck state, SD card) is the consumer’s concern; the runtime exposes only the serialize/load pair.
Acceptance against ADR-0008 + ADR-0016 + ADR-0009
Section titled “Acceptance against ADR-0008 + ADR-0016 + ADR-0009”| AC | Source | Status |
|---|---|---|
| Structural navigation: CAR descends, CDR sibling, BACK ascends | ADR-0008 §Cursor Movement, ADR-0016 §3 | Pass — test_nemacs_runtime nav_* tests |
| Insertion via CONS opens a list; LAMBDA enters literal entry; ENT commits | ADR-0008 §Insertion + §Literal Entry | Pass — insert_leaf_extends_buffer, insert_list_descends_cursor, literal_* tests |
| Cut/copy via QUOTE (grab mode); CONS pastes | ADR-0008 §Cursor Movement, ADR-0016 §2 | Pass — nemacs_grab_cut + _paste; tested via direct API |
| Five mode scopes with Lisp-alist keymap | ADR-0016 §2 | Pass — keymap suite (8 tests) |
:tap / :double-tap / :long-press slots with fallback | ADR-0016 §9 | Pass — dispatch_*, lookup_* tests |
| Phone-style numpad (1-2-3 top, 0 bottom-center) | ADR-0016 §5 | Pass — implemented in keymap.c and keyboard_overlay.c (PR #168 / GWP-211) |
| Nokia multi-tap on 2–9 with timeout commit + ENT immediate | ADR-0016 §6 | Pass — test_nokia (23 tests) |
Case toggle on /, delete on * (in literal/prompt scopes) | ADR-0016 §6 + ADR-0022 §7 | Pass — nokia.c |
prompt-text FFI with begin / complete / committed / result / end | ADR-0016 §8 | Pass — prompt_* tests |
| T9 ranker: legal-form filter + domain (+5) + local (+3) + recency (0–10) + popularity (0–4) | ADR-0009 + ADR-0016 §7 | Pass — test_t9_prediction (25 tests) |
Cart contributions: emacs-extend-grammar, emacs-extend-vocabulary | ADR-0016 §7 | Pass — extend_* tests + reset on cart unload |
| CIPHER-LINE rows 1–4 composition with cipher pause | ADR-0016 §4 | Pass — modeline_*, echo_*, render_* tests |
| REPL prompt + eval + history (32-slot ring) | ADR-0002 §1, ADR-0016 §1 | Pass — repl_* tests |
| Buffer persistence: serialize/load round-trip | GWP-316 ship gate | Pass — test_nemacs_persistence (14 tests) |
No follow-on tasks deferred from the ship gate. If post-launch playtest surfaces tuning needs (multi-tap timeout, palette sort, recency window), those are firmware-tunable constants per ADR-0016 §“Known Unknowns”.
Test surface
Section titled “Test surface”tests/test_nemacs_runtime.c — 59 tests, foundation + integrationtests/test_nemacs_persistence.c — 14 tests, GWP-316 ship gatetests/test_nokia.c — 23 tests, multi-tap state machinetests/test_t9_prediction.c — 25 tests, ranker + explainertests/test_keymap_dispatch.c — keymap-as-Fe-data + Lisp primitivestests/test_keymap_rebind.c — define-key cart audit logtests/test_input_events.c — derived event detectiontests/test_prompt_text.c — prompt-text FFI from cart sideRun all from runtime/build:
cmake -S runtime -B runtime/build && cmake --build runtime/build && ctest --test-dir runtime/build --output-on-failureTotal nEmacs-related coverage: 121+ tests across 8 suites.
Cross-references
Section titled “Cross-references”- Canonical Hardware Specification — see
CLAUDE.md. CIPHER-LINE rows owned by nEmacs while active are 1–4; main grid rows 1–73 host the structural editor view. - Input dispatch — see
../runtime/input-dispatch.mdfor the 34-key event model and how derived events (TAP / DOUBLE_TAP / LONG_PRESS) are produced. nEmacs consumes those derived events; it never sees raw KEY_DOWN / KEY_UP in the keymap. - CIPHER voice integration — see
../runtime/cipher-voice.md. nEmacs pauses cipher scrollback on enter and resumes on exit so the operator gets a quiet OLED while editing. - Cartridge lifecycle — see
../runtime/cartridge-lifecycle.md.nemacs_reset_cart_scopeis the unmount hook that clears cart-contributed grammar + vocabulary.