|
82 | 82 | why "capture void baseline benches, set void regression budgets" |
83 | 83 | - todo [P6] diverge / upstream strategy |
84 | 84 | why "decide which changes feed back to ghostty-org vs stay void-only" |
85 | | -- wip [P7] sighup-resistant session preservation (Phase A1 landed 2026-04-29) |
86 | | - why "void window close (cmd+W or full-window close) → Exec.zig:1156 |
87 | | - killpg(pgid, SIGHUP) → child PTY group dies. iTerm reproduces |
88 | | - identical pattern (controlling pts close → SIGHUP cascade). |
89 | | - 2026-04-28 diagnosis: user lost all claude TUIs in one window |
90 | | - when window closed. root-cause fix at terminal-emulator host |
91 | | - level: detach PTY + child to long-lived session daemon, allow |
92 | | - reattach from new window. complement to P2 (restores |
93 | | - scrollback bytes after termination); P7 prevents termination |
94 | | - itself. user request 2026-04-28." |
95 | | - sketch |
96 | | - - zig: src/termio/SessionDaemon.zig — long-lived bg thread/process |
97 | | - owning detached PTY pool. registry at ~/.void/sessions/<sid>/ |
98 | | - { pty.fd via SCM_RIGHTS handoff, pid, env, cwd, bytes.ring }. |
99 | | - - zig: src/termio/Exec.zig — Termio.deinit hook: if config |
100 | | - detach_on_close=true (default false at first, opt-in), send |
101 | | - master_fd via Unix domain socket fd-passing to daemon, skip |
102 | | - killpg(SIGHUP). pid is reparented to daemon (already via |
103 | | - setsid, daemon picks up via session id registry). |
104 | | - - zig: src/apprt/action.zig — new actions detach_session, |
105 | | - attach_session, list_sessions. keybinds tbd. |
106 | | - - swift: BaseTerminalController menu — Window → Detach Session, |
107 | | - Window → Attach Session… (lists ~/.void/sessions/). |
108 | | - - cli: void --attach <sid> spawns new window bound to existing |
109 | | - pty master fd. void --list-sessions emits sid+cwd+age. |
110 | | - phase a1 (landed 2026-04-29) |
111 | | - - src/config/Config.zig: opt-in field `@"detach-on-close": bool=false` |
112 | | - with full doc. default keeps existing behavior intact. |
113 | | - - src/termio/Exec.zig: `detach_on_close` plumbed through Exec.Config |
114 | | - and Subprocess. `Subprocess.stop()` short-circuits killCommand and |
115 | | - logs `P7 detach-on-close: skipping killpg(SIGHUP) ...` when set. |
116 | | - - src/Surface.zig: Exec.Config init populates the new field from |
117 | | - `config.@"detach-on-close"`. |
118 | | - - phase a1 alone does NOT preserve the child across window close — |
119 | | - master pty fd still closes during `Subprocess.deinit` and the |
120 | | - kernel SIGHUPs the controlling-tty group. value is plumbing, |
121 | | - audit log path, and a verified branch for phase a2 to extend. |
122 | | - - phase a2 next: app-level DetachedSessionPool that takes ownership |
123 | | - of the master fd before deinit closes it. cross-app daemon = |
124 | | - phase a3. ring buffer + reattach = phase a4. |
125 | | - phase b1-prep (landed 2026-04-29) |
126 | | - - sister axis to phase a. phase a preserves LIVE PROCESSES across |
127 | | - surface close; phase b preserves CONTENT + TOPOLOGY across |
128 | | - abnormal void termination AND macos crash/reboot. |
129 | | - - hybrid persist policy: structural (split/tab/grid) atomic write |
130 | | - immediate; pty bytes mmap ring append per-read + msync(MS_ASYNC) |
131 | | - every 1s; screen state recomputable from byte stream replay |
132 | | - (5s timer OK because not load-bearing for recovery). |
133 | | - - data loss bounds: scenario A (void crash, mac up) <16KB per pane; |
134 | | - scenario B (mac panic/reboot) ≤ 1 second. |
135 | | - - landed: src/config/Config.zig opt-in flags `@"persist-grid-on-change"` |
136 | | - and `@"persist-bytes-mmap"` (both bool=false default). Two new |
137 | | - files: src/termio/PersistRing.zig (mmap ring buffer + open/append/ |
138 | | - msyncAsync/replay + 2 unit tests) and macos/Sources/Features/ |
139 | | - Terminal/SessionPersistManager.swift (atomic SplitTree/meta write, |
140 | | - enumeratePersistedWindows for B2). NO wiring yet — phase b1-impl |
141 | | - is the integration commit. |
142 | | - - phase b1-impl next: wire PersistRing into termio read path + |
143 | | - msync 1s timer; wire SessionPersistManager into TerminalController |
144 | | - structural events (split create/close, tab open/close, grid move). |
145 | | - - phase b2: cold-restart respawn (scan persistedWindows, decode |
146 | | - SplitTree, respawn shells at saved cwd, replay bytes.ring). |
147 | | - - phase b3: hot-reattach (check daemon pool, SCM_RIGHTS receive |
148 | | - live pty.fd, fallback to b2 cold respawn). Needs phase a2 + b1-impl + b2. |
149 | | - honest scope reset (2026-04-29) |
150 | | - - measurement: void/ghostty already has FULL window+grid+SplitTree |
151 | | - restoration via macos NSWindowRestoration. See |
152 | | - macos/Sources/Features/Terminal/TerminalRestorable.swift (193 LoC, |
153 | | - pre-existing, encodes surfaceTree). Activation: user sets |
154 | | - `window-save-state = always` in config — zero new code needed. |
155 | | - - so the "그리드 복구" half of the user request is already SOLVED by |
156 | | - existing code + one config line. Only PTY scrollback / live screen |
157 | | - content remains as the gap requiring new code. |
158 | | - - SessionPersistManager.swift (b1-prep, a3f6c7d37) is partially |
159 | | - redundant with TerminalRestorable. Cleanup pending. |
160 | | - - phase a1 detach-on-close (dcccc2b3e) is ORTHOGONAL to crash recovery |
161 | | - (it addresses surface close, not abnormal termination). Should be |
162 | | - classified separately from P7 crash-recovery axis. |
163 | | - phase b1-tool (landed 2026-04-29) |
164 | | - - bin/void-session-replay shell tool that reads PersistRing files |
165 | | - (validates PVER magic) and dumps recovered PTY bytes to stdout. |
166 | | - Supports --list, --latest, --all, <path>. Smoke-tested with |
167 | | - synthetic 4MB ring (PVER + write_offset=11 + "hello world" → |
168 | | - correctly recovers "hello world"). |
169 | | - - gives user immediate manual recovery path for content preserved |
170 | | - in rings while phase b2 (auto-replay-on-restore) is pending. |
171 | | - - phase b2 minimum is now ~150 LoC (smaller than original 500+150 |
172 | | - estimate) because TerminalRestorable already does most of the |
173 | | - work — just need UUID plumbing + Termio.init replay hook. |
| 85 | +- wip [P7] session preservation across abnormal termination |
| 86 | + why "user request 2026-04-28: void가 비정상 종료시 다시 실행 시 |
| 87 | + 그리드 + 터미널 내용 그대로 복구, macOS 크래시 후 재실행 포함. |
| 88 | + scenarios — A: void crash (segfault, OOM, jetsam SIGKILL), |
| 89 | + B: macOS panic / power loss. recovery goal on relaunch: |
| 90 | + same grid layout, same terminal content." |
| 91 | + current state (2026-04-29) |
| 92 | + - GRID + SPLITTREE restore: ALREADY SOLVED by existing code + |
| 93 | + one config line. macos/Sources/Features/Terminal/ |
| 94 | + TerminalRestorable.swift (193 LoC pre-existing) encodes |
| 95 | + surfaceTree via NSWindowRestoration. Activation: |
| 96 | + `window-save-state = always` in user config. ZERO new code. |
| 97 | + - PTY BYTES on disk: solved this session. |
| 98 | + src/termio/PersistRing.zig — mmap ring buffer per Termio, |
| 99 | + append in processOutput, 1s msync(MS_ASYNC) timer in |
| 100 | + Exec.threadEnter. Opt-in via `persist-bytes-mmap = true`. |
| 101 | + Loss bounds: scenario A < 16KB; scenario B ≤ 1s. |
| 102 | + - PTY BYTES manual recovery: tool/void-session-replay.sh |
| 103 | + reads ring files, validates PVER magic, dumps recovered |
| 104 | + bytes to stdout. --list / --latest / --all / <path>. |
| 105 | + phase b2 (NOT yet landed) — automatic replay on restore |
| 106 | + - ~150 LoC (smaller than original 500+150 because |
| 107 | + TerminalRestorable already does the heavy lifting). |
| 108 | + - scope: add surface_uuid: ?UUID to VD.SurfaceConfiguration |
| 109 | + (Swift) + extend C ABI surface-init struct + Surface.zig |
| 110 | + reads UUID + Termio.zig uses by-uuid/<uuid>.ring path + |
| 111 | + Termio.init replay-on-init hook. SplitTree Codable already |
| 112 | + preserves the UUID across launches. |
| 113 | + retired / over-coded — removed 2026-04-29 cleanup |
| 114 | + - phase a1 detach-on-close (dcccc2b3e) — addressed window |
| 115 | + close cascade-kill (different axis from crash recovery) |
| 116 | + and was non-functional alone (skipped our explicit killpg |
| 117 | + but kernel still SIGHUPed on master pty close in deinit). |
| 118 | + true window-close survival needs a DetachedSessionPool |
| 119 | + which is a separate larger work item. |
| 120 | + - SessionPersistManager.swift (b1-prep half) — redundant |
| 121 | + with TerminalRestorable. |
| 122 | + - @"persist-grid-on-change" config — dead, no callers. |
| 123 | + grid persistence handled by TerminalRestorable + window- |
| 124 | + save-state. |
| 125 | + - cleanup commit retains all real value: PersistRing.zig + |
| 126 | + Termio wire + msync timer + void-session-replay.sh. |
174 | 127 | open questions |
175 | 128 | - daemon lifecycle: lazy-spawn first detach + idle-reap, or |
176 | 129 | always-on launchd plist? lazy chosen for now (zero overhead |
|
0 commit comments