Skip to content

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).


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.

CapabilityStatusSourceTests
Lifecycle (init / shutdown / activation)Shippednemacs_init, nemacs_shutdown, nemacs_enter_editor, nemacs_enter_repl, nemacs_exittest_nemacs_runtime
Five mode scopes (:nemacs-nav, :nemacs-literal, :repl-prompt, :repl-history, :grab)ShippedNemacsMode enum + nemacs_set_modetest_nemacs_runtime
Keymap as Fe alist, mode-scoped, with :tap / :double-tap / :long-press slots (ADR-0016 §2 + §9)Shippednemacs_define_key, nemacs_lookup_key, nemacs_dispatch; Lisp surface keymap-modes / keymap-get / keymap-set / keymap-modetest_nemacs_runtime, test_keymap_dispatch
Tree-cursor navigation (CAR / CDR / BACK; ADR-0016 §3)Shippednemacs_nav_car, nemacs_nav_cdr, nemacs_nav_backtest_nemacs_runtime
Structural insertion (leaf, list, grab cut/paste)Shippednemacs_insert_leaf, nemacs_insert_list, nemacs_grab_cut, nemacs_grab_pastetest_nemacs_runtime
Nokia multi-tap alpha entry (ADR-0016 §6)Shippednokia.c; nemacs_literal_keypress, nemacs_literal_tick, nemacs_commit_literaltest_nokia, test_nemacs_runtime
prompt-text modal FFI (ADR-0016 §8)Shippednemacs_prompt_begin / _complete / _committed / _result / _endtest_nemacs_runtime
T9 prediction ranker with cart contributions (ADR-0009 + ADR-0016 §7)Shippedt9_rank.c; nemacs_palette_request, nemacs_extend_grammar, nemacs_extend_vocabulary, nemacs_record_tokentest_t9_prediction
CIPHER-LINE composition (modeline + palette + echo) on rows 1–4 (ADR-0016 §4)Shippednemacs_compose_modeline, _palette_a, _palette_b, _echo, nemacs_rendertest_nemacs_runtime
REPL evaluation, prompt, history (ADR-0002, ADR-0016 §1)Shippednemacs_repl_set_prompt, _eval, _last_result, _history_*test_nemacs_runtime
Buffer persistence — serialize / load (GWP-316 ship gate)Shippednemacs_buffer_serialize, nemacs_buffer_loadtest_nemacs_persistence
Cipher pause/resume on entry/exitShippedcipher_pause / cipher_resume wired in nemacs_enter_* / nemacs_exittest_nemacs_runtime
Nokia double-tap suppression seamShippedinput_set_suppress_double_tap toggled on mode changetest_nemacs_runtime

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 out to the empty string.
  • Load parses with the editor Fe context’s reader, replaces the editor root, and resets the cursor to (root, 0). Returns true on 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”
ACSourceStatus
Structural navigation: CAR descends, CDR sibling, BACK ascendsADR-0008 §Cursor Movement, ADR-0016 §3Pass — test_nemacs_runtime nav_* tests
Insertion via CONS opens a list; LAMBDA enters literal entry; ENT commitsADR-0008 §Insertion + §Literal EntryPass — insert_leaf_extends_buffer, insert_list_descends_cursor, literal_* tests
Cut/copy via QUOTE (grab mode); CONS pastesADR-0008 §Cursor Movement, ADR-0016 §2Pass — nemacs_grab_cut + _paste; tested via direct API
Five mode scopes with Lisp-alist keymapADR-0016 §2Pass — keymap suite (8 tests)
:tap / :double-tap / :long-press slots with fallbackADR-0016 §9Pass — dispatch_*, lookup_* tests
Phone-style numpad (1-2-3 top, 0 bottom-center)ADR-0016 §5Pass — implemented in keymap.c and keyboard_overlay.c (PR #168 / GWP-211)
Nokia multi-tap on 2–9 with timeout commit + ENT immediateADR-0016 §6Pass — test_nokia (23 tests)
Case toggle on /, delete on * (in literal/prompt scopes)ADR-0016 §6 + ADR-0022 §7Pass — nokia.c
prompt-text FFI with begin / complete / committed / result / endADR-0016 §8Pass — prompt_* tests
T9 ranker: legal-form filter + domain (+5) + local (+3) + recency (0–10) + popularity (0–4)ADR-0009 + ADR-0016 §7Pass — test_t9_prediction (25 tests)
Cart contributions: emacs-extend-grammar, emacs-extend-vocabularyADR-0016 §7Pass — extend_* tests + reset on cart unload
CIPHER-LINE rows 1–4 composition with cipher pauseADR-0016 §4Pass — modeline_*, echo_*, render_* tests
REPL prompt + eval + history (32-slot ring)ADR-0002 §1, ADR-0016 §1Pass — repl_* tests
Buffer persistence: serialize/load round-tripGWP-316 ship gatePass — 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”.


tests/test_nemacs_runtime.c — 59 tests, foundation + integration
tests/test_nemacs_persistence.c — 14 tests, GWP-316 ship gate
tests/test_nokia.c — 23 tests, multi-tap state machine
tests/test_t9_prediction.c — 25 tests, ranker + explainer
tests/test_keymap_dispatch.c — keymap-as-Fe-data + Lisp primitives
tests/test_keymap_rebind.c — define-key cart audit log
tests/test_input_events.c — derived event detection
tests/test_prompt_text.c — prompt-text FFI from cart side

Run all from runtime/build:

Terminal window
cmake -S runtime -B runtime/build && cmake --build runtime/build && ctest --test-dir runtime/build --output-on-failure

Total nEmacs-related coverage: 121+ tests across 8 suites.


  • 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.md for 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_scope is the unmount hook that clears cart-contributed grammar + vocabulary.