docs(vterm): fold architecture decisions

Record the chosen C-c escape, resize and exit behavior, additive v19
compatibility, complete-frame wire, unchanged Style, and shared-session
ownership. Specify the Emacs-style PID/outcome line retained after exit.
This commit is contained in:
Levi Neuwirth 2026-07-21 13:26:36 -04:00
parent 2ff67e7236
commit f26aa0b38b
1 changed files with 46 additions and 35 deletions

View File

@ -1,6 +1,13 @@
# Vterm — framing (Arc 5 stage 2, three-PR delivery)
**Revision 1 — 2026-07-21. Status: framing for discussion; no implementation.**
**Revision 2 — 2026-07-21. Status: decisions folded; awaiting approval, no implementation.**
Revision 2 records the architecture discussion: `C-c` is the terminal editor
escape (`C-c C-c` sends interrupt); main-screen resize reflows while alternate
screen clips/pads; exited buffers remain with an Emacs-style process message;
protocol v19 is additive with complete frames; shared `Style` stays unchanged;
and one `BufferId` owns one shared process/screen whose most recently active
frontend controls size.
This framing follows the compile-mode terminal substrate that landed in PR
#113. `src/process.rs` already owns PTY creation, process groups, bounded
@ -125,6 +132,7 @@ pub struct TerminalSelectionSpan {
pub start_col: u32, // inclusive
pub end_col: u32, // exclusive
}
pub enum TerminalProcessState {
Running,
Exited(i32),
@ -132,7 +140,6 @@ pub enum TerminalProcessState {
Crashed(String),
}
pub struct TerminalSnapshot {
pub buffer_id: BufferId,
pub size: CellSize,
@ -143,6 +150,7 @@ pub struct TerminalSnapshot {
pub selection: Vec<TerminalSelectionSpan>,
pub scroll_offset: u32,
pub at_bottom: bool,
pub pid: u32,
pub process: TerminalProcessState,
}
```
@ -183,8 +191,9 @@ Lua `ansi = true` process option retain the line-oriented profile; terminal
session construction is the only initial full-screen caller. On `finish()`,
`LineOriented` preserves today's synthetic alternate-screen/style balancing.
`FullScreen` flushes pending text but does not invent an alternate-screen exit
or clear cells: a child that crashes in alternate screen leaves its final
visible screen inspectable. Parser internals reset in both profiles.
or clear cells. The session manager, after applying the actual final parser
events, adds the process-exit annotation described in §4.1. Parser internals
reset in both profiles.
The shared `Cell::Style` remains unchanged in this arc. Existing support covers
indexed/truecolor foreground/background, bold, italic, underline variants,
@ -299,12 +308,19 @@ cannot steal batches from each other.
3. the existing `process.after-tick` hook runs;
4. LSP/MCP retain their existing later ticks.
At process exit, final drained output is applied before the terminal becomes
`exited`. The buffer remains visible and copyable with a terminal-local exit
status; it is not automatically killed. Killing the buffer terminates the
process/session. A periodic prune handles all buffer-removal paths, including
Lua callers that bypass the friendly terminal close API. Editor shutdown uses
the existing supervisor shutdown path and cannot restart a terminal.
At process exit, final drained output is applied first. The manager then writes
one synthetic, default-style hard line into the active terminal screen:
`Process <PID> exited normally with code 0` for zero; `Process <PID> exited
abnormally with code <code>` for a non-zero code; or `Process <PID> exited
abnormally with signal <signal>` for signal termination. It emits CRLF first
when needed so the annotation never overwrites child text. The annotation is
terminal-owned (not parser output and not rope text), visible and copyable like
the rest of the screen.
The buffer remains until the user kills it. Killing the buffer terminates a
still-live process/session. A periodic prune handles all buffer-removal paths,
including Lua callers that bypass the friendly terminal close API. Editor
shutdown uses the existing supervisor shutdown path and cannot restart a
terminal.
The wire-visible process state has only the four variants above: synchronous
spawn failure never publishes a session, and `terminate` remains `Running`
@ -441,10 +457,10 @@ The normalized protocol does not distinguish number-row digits from numeric
keypad digits, so application-keypad mode is tracked but cannot transform
those ambiguous `Key::Char` events.
`C-]` is the fixed stage-2 terminal escape prefix. It is consumed and makes the
next key run through the ordinary editor dispatcher, allowing `C-] C-x ...`
for editor commands. `C-] C-]` sends a literal Ctrl-] byte. This is an
intentional fixed stage-2 policy.
`C-c` is the fixed stage-2 terminal escape prefix. It is consumed and makes the
next key run through the ordinary editor dispatcher, allowing `C-c C-x ...`
for editor commands. `C-c C-c` sends the literal Ctrl-C byte required to
interrupt the child. This is an intentional fixed stage-2 policy.
Paste sends exact bytes, wrapped in `ESC[200~` / `ESC[201~` only while the
child enabled bracketed paste. It never passes through a command shell or Lua.
@ -506,6 +522,7 @@ InstanceMessage::TerminalFrame {
selection: Vec<TerminalSelectionSpan>,
scroll_offset: u32,
at_bottom: bool,
pid: u32,
process: TerminalProcessState,
}
@ -678,8 +695,8 @@ base-branch deletion/auto-close risk and makes each PR's gate evidence honest.
11. Strict owned `TerminalSpec` values reject before spawn and are
mutation-independent after spawn.
12. A real PTY child prints cursor-addressed/alternate-screen content in
adversarial chunks; the headless snapshot matches and final output lands
before exit state.
adversarial chunks; final output lands first, then exact normal/non-zero/
signal process annotations are visible and copyable before exit state.
13. Spawn failure leaves no buffer/session/process residue. Killing a live
terminal buffer terminates it; editor shutdown leaves no child/reader.
14. Every rope mutation path (ordinary, intercept-skipping, undo/redo, local
@ -697,7 +714,7 @@ base-branch deletion/auto-close risk and makes each PR's gate evidence honest.
and scrolled-back hiding are exact.
18. Printable, Ctrl, Alt, arrows, Home/End, function keys, application cursor,
focus reporting, and unknown keys produce the specified PTY bytes.
19. `C-]` dispatches one editor key; `C-] C-]` sends Ctrl-]; modal minibuffer,
19. `C-c` dispatches one editor key; `C-c C-c` sends Ctrl-C; modal minibuffer,
search, menu, and query-replace remain authoritative.
20. Paste is byte-exact with bracketed wrappers only when enabled.
21. Mouse-reporting modes receive translated SGR reports. With reporting off,
@ -766,22 +783,16 @@ Not part of these three PRs:
Deferral means graceful ignore or documented absence, never escape leakage,
panic, unbounded allocation, or child leak.
## 12. Review questions
## 12. Resolved decisions
1. **Escape prefix:** accept fixed `C-]` for the first TUI stage, with
`C-] C-]` for literal Ctrl-], or choose a different fixed prefix before
implementation?
2. **Resize:** accept logical-line reflow on the main screen and no reflow on
alternate screen?
3. **Exit:** accept keeping an exited terminal buffer visible/copyable until
the user kills it, rather than auto-killing?
4. **Compatibility:** accept v18 grid support but no terminal surface for v18
semantic/GPU peers, while normal v18 document editing remains compatible?
5. **Wire shape:** accept complete visible `TerminalFrame` replacements for
stage 3, with complete-payload compare suppression, instead of introducing
a terminal-delta cache in the first GPU PR?
6. **Style scope:** accept preserving the current shared `Style` encoding and
explicitly deferring faint/blink/conceal/strikethrough?
7. **Session identity:** accept one screen/process per terminal `BufferId`,
shared across splits/frontends, with the most recently active frontend's
active view controlling PTY size?
The 2026-07-21 architecture discussion resolved every Revision 1 question:
1. Fixed terminal editor escape: `C-c`; `C-c C-c` sends literal Ctrl-C.
2. Resize: reflow main-screen soft wraps; clip/pad alternate screen.
3. Exit: retain the buffer and append the process PID/outcome line from §4.1.
4. Compatibility: additive v19; v18 grid remains supported, v18 semantic has
no terminal surface.
5. GPU wire: complete visible frames with complete-payload suppression.
6. Style: preserve the shared encoding and defer unsupported attributes.
7. Identity: one process/screen per terminal `BufferId`; the most recently
active frontend's active view controls PTY size.