a73x

5c38fd57

Update design spec with review feedback

a73x   2026-04-07 19:11

Commit message
Update design spec with review feedback

- Flesh out vt.zig API: all 8 opaque handle types, setter pattern,
  iterator lifecycle, encoder sync, required effect callbacks
- Add key repeat handling, focus events, cursor shape, DPI scaling
- Fix glyph atlas to R8 (not RGBA), defer harfbuzz to phase 2
- Clarify libghostty-vt build story (fetched via build.zig.zon)
- Add clipboard as explicit phase 2 gap
- Add TERM env var, CSD/SSD stance, cell metrics derivation
- Add phased features table

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

docs/superpowers/specs/2026-04-07-waystty-design.md
Old New
@@ -36,18 +36,30 @@ renderer.zig — Vulkan setup, pipeline, instanced quad rendering
36 36
37 Protocols: 37 Protocols:
38 - `wl_compositor` + `wl_surface` — drawing surface 38 - `wl_compositor` + `wl_surface` — drawing surface
39 - `xdg_wm_base` + `xdg_surface` + `xdg_toplevel` — window management 39 - `xdg_wm_base` + `xdg_surface` + `xdg_toplevel` — window management (SSD only, no CSD)
40 - `wl_seat` → `wl_keyboard` + `wl_pointer` — input 40 - `wl_seat` → `wl_keyboard` + `wl_pointer` — input
41 - `wp_cursor_shape_v1` — set I-beam cursor over terminal area
42 - `wl_output` — monitor scale factor for DPI awareness
43 - `wp_fractional_scale_v1` — fractional DPI scaling (if available)
41 44
42 Vulkan surface via `VK_KHR_wayland_surface` (`VkSurfaceKHR` from `wl_display` + `wl_surface`). 45 Vulkan surface via `VK_KHR_wayland_surface` (`VkSurfaceKHR` from `wl_display` + `wl_surface`).
43 46
44 Keyboard input: keycodes from `wl_keyboard.key`, mapped via xkbcommon to keysyms/UTF-8, then handed to vt.zig's key encoder. 47 Keyboard input: keycodes from `wl_keyboard.key`, mapped via xkbcommon to keysyms/UTF-8, then handed to vt.zig's key encoder.
45 48
49 Key repeat: client-side implementation using `wl_keyboard.repeat_info` (rate + delay). Wayland does not repeat keys for you — we must track the last pressed key and fire repeats on a timer in the event loop.
50
51 Focus events: track `wl_keyboard.enter` / `wl_keyboard.leave`, encode via `ghostty_focus_encode()` when terminal has `GHOSTTY_MODE_FOCUS_EVENT` set.
52
46 Event loop: `wl_display_get_fd()` polled alongside PTY fd. Single-threaded. 53 Event loop: `wl_display_get_fd()` polled alongside PTY fd. Single-threaded.
47 54
55 #### Clipboard (phase 2)
56
57 Not implemented in v1. Requires `wl_data_device_manager`, `wl_data_device`, `wl_data_source`, `wl_data_offer` — significant protocol work. Will be the first feature added after the terminal is functional. Without clipboard, the terminal is usable but inconvenient.
58
48 ### pty.zig 59 ### pty.zig
49 60
50 - `forkpty()` to spawn child shell (`$SHELL` or `/bin/sh`) 61 - `forkpty()` to spawn child shell (`$SHELL` or `/bin/sh`)
62 - Set `TERM=xterm-256color` in child environment
51 - Non-blocking master fd 63 - Non-blocking master fd
52 - `read()` / `write()` helpers 64 - `read()` / `write()` helpers
53 - `SIGCHLD` handling for child exit 65 - `SIGCHLD` handling for child exit
@@ -55,18 +67,33 @@ Event loop: `wl_display_get_fd()` polled alongside PTY fd. Single-threaded.
55 67
56 ### vt.zig 68 ### vt.zig
57 69
58 Zig wrapper around `<ghostty/vt.h>` via `@cImport`: 70 Zig wrapper around `<ghostty/vt.h>` via `@cImport`. The C API is opaque-handle-based — all types are pointers created/freed with `_new`/`_free` pairs. Configuration uses a setter pattern: `ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_*, value)`.
59 - `Terminal` — init, feed bytes, resize, scroll, get render state 71
60 - `KeyEncoder` / `MouseEncoder` — encode input events to VT sequences 72 Wrapped handle types:
61 - `RenderState` — snapshot, iterate rows/cells, get colors/cursor 73 - `Terminal` — `ghostty_terminal_new` / `_free`. Init with cols, rows, max_scrollback. Feed bytes via `ghostty_terminal_vt_write`. Resize via `ghostty_terminal_resize`. Scroll viewport via `ghostty_terminal_scroll_viewport`.
62 - Effect callbacks: write-to-pty, device-attributes, title-changed, etc. 74 - `RenderState` — `ghostty_render_state_new` / `_free`. Snapshot terminal state via `ghostty_render_state_update(render_state, terminal)`. Must be called after feeding PTY data, before iterating cells. Query colors via `ghostty_render_state_colors_get`. Check dirty flag via `GHOSTTY_RENDER_STATE_OPTION_DIRTY`.
75 - `RowIterator` — `ghostty_render_state_row_iterator_new` / `_free`. Iterate rows via `_next`. Each row yields a `RowCells`.
76 - `RowCells` — `ghostty_render_state_row_cells_new` / `_free`. Iterate cells via `_next`. Per-cell getters for: graphemes buffer, fg/bg colors, style flags (bold, italic, inverse, underline). Check row dirty via `GHOSTTY_RENDER_STATE_ROW_OPTION_DIRTY`.
77 - `KeyEncoder` — `ghostty_key_encoder_new` / `_free`. Before each encode, sync terminal modes via `ghostty_key_encoder_setopt_from_terminal`. Then create `KeyEvent` (`_new` / `_free`), populate, and encode.
78 - `MouseEncoder` — `ghostty_mouse_encoder_new` / `_free`. Before each encode, sync via `ghostty_mouse_encoder_setopt_from_terminal`. Create `MouseEvent` (`_new` / `_free`), populate, and encode.
79
80 Required effect callbacks (set via `ghostty_terminal_set`):
81 - `GHOSTTY_TERMINAL_OPT_WRITE_PTY` — terminal sends responses (device status, cursor position) back to shell. **Mandatory**.
82 - `GHOSTTY_TERMINAL_OPT_DEVICE_ATTRIBUTES` — DA1/DA2/DA3 query responses. Reports VT220 conformance.
83 - `GHOSTTY_TERMINAL_OPT_SIZE` — reports terminal dimensions to querying applications.
84 - `GHOSTTY_TERMINAL_OPT_XTVERSION` — returns "waystty" for XTVERSION queries.
85 - `GHOSTTY_TERMINAL_OPT_TITLE_CHANGED` — updates the xdg_toplevel title.
86 - `GHOSTTY_TERMINAL_OPT_COLOR_SCHEME` — stub (returns false).
87
88 System init: call `ghostty_sys_set(GHOSTTY_SYS_OPT_DECODE_PNG, decode_png)` before creating terminal if Kitty graphics support is desired (phase 2).
63 89
64 ### font.zig 90 ### font.zig
65 91
66 - fontconfig: find system monospace font at startup 92 - fontconfig: find system monospace font at startup
67 - freetype: load font face, rasterize glyphs to bitmaps 93 - freetype: load font face, rasterize glyphs to single-channel (R8) bitmaps
68 - harfbuzz: shape text runs (ligatures, combining characters) 94 - harfbuzz: deferred to phase 2. For v1, render single codepoints per cell — libghostty already handles grapheme clustering and provides codepoints per cell via `GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_BUF`. Ligature/shaping support can be added later by shaping runs of same-styled cells.
69 - Glyph atlas: single RGBA texture (e.g. 1024x1024), row-based packing, returns UV coords per glyph 95 - Glyph atlas: single R8 texture (e.g. 1024x1024), row-based packing, returns UV coords per glyph. Fragment shader applies fg color to the single-channel alpha. Colored emoji would need a separate RGBA atlas (phase 2).
96 - Cell metrics: `cell_width` derived from font advance width of 'M', `cell_height` from font ascent + descent + line gap. These are computed once at font load and used for grid calculations.
70 97
71 ### renderer.zig 98 ### renderer.zig
72 99
@@ -95,9 +122,12 @@ loop:
95 if pty_fd readable: 122 if pty_fd readable:
96 read pty → feed to terminal via ghostty_terminal_vt_write() 123 read pty → feed to terminal via ghostty_terminal_vt_write()
97 124
98 if terminal dirty or resize: 125 if key repeat timer expired:
99 snapshot render state 126 re-send last key event to pty
100 rebuild instance buffer 127
128 if terminal dirty (GHOSTTY_RENDER_STATE_OPTION_DIRTY) or resize:
129 ghostty_render_state_update(render_state, terminal)
130 rebuild instance buffer (skip clean rows via ROW_OPTION_DIRTY)
101 render frame 131 render frame
102 132
103 if child exited: 133 if child exited:
@@ -136,8 +166,9 @@ Zig package dependencies (via `build.zig.zon`):
136 - zig-wayland — Wayland protocol bindings 166 - zig-wayland — Wayland protocol bindings
137 - vulkan-zig — Vulkan bindings generator 167 - vulkan-zig — Vulkan bindings generator
138 168
169 **libghostty-vt acquisition:** Built from source. The ghostty repo is fetched via `build.zig.zon` (pinned commit hash). A custom build step invokes `zig build lib-vt` within the fetched source to produce the shared library, similar to how ghostling's CMakeLists.txt handles it.
170
139 System C library linkage: 171 System C library linkage:
140 - libghostty-vt
141 - freetype2 172 - freetype2
142 - harfbuzz 173 - harfbuzz
143 - fontconfig 174 - fontconfig
@@ -201,3 +232,18 @@ End-to-end: spawn waystty with `echo "hello"; exit`, verify clean exit. Can run
201 232
202 - No premature optimization — get it working, then profile 233 - No premature optimization — get it working, then profile
203 - No threading unless profiling proves the single thread is the bottleneck 234 - No threading unless profiling proves the single thread is the bottleneck
235
236 ## Phased Features
237
238 Features deferred from v1 to keep initial scope minimal:
239
240 | Feature | Phase | Notes |
241 |---------|-------|-------|
242 | Clipboard (copy/paste) | 2 | `wl_data_device_manager` protocol work. First post-v1 priority. |
243 | Harfbuzz text shaping | 2 | Ligatures, complex scripts. v1 renders single codepoints per cell. |
244 | Kitty graphics protocol | 2 | Image display in terminal. Requires PNG decode callback + placement rendering. |
245 | Colored emoji | 2 | Separate RGBA atlas alongside the R8 glyph atlas. |
246 | Scrollbar rendering | 2 | Visual scrollbar. Viewport scrolling via `ghostty_terminal_scroll_viewport` is in v1. |
247 | Config file | future | `~/.config/waystty/config` for fonts, colors, keybindings. |
248 | URL detection/opening | future | Click-to-open URLs. |
249 | Multiple windows/tabs | future | Out of scope for a minimal terminal. |