a73x

5c38fd57

Update design spec with review feedback

a73x   2026-04-07 19:11

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

diff --git a/docs/superpowers/specs/2026-04-07-waystty-design.md b/docs/superpowers/specs/2026-04-07-waystty-design.md
index 2791c61..c6317cd 100644
--- a/docs/superpowers/specs/2026-04-07-waystty-design.md
+++ b/docs/superpowers/specs/2026-04-07-waystty-design.md
@@ -36,18 +36,30 @@ renderer.zig      — Vulkan setup, pipeline, instanced quad rendering

Protocols:
- `wl_compositor` + `wl_surface` — drawing surface
- `xdg_wm_base` + `xdg_surface` + `xdg_toplevel` — window management
- `xdg_wm_base` + `xdg_surface` + `xdg_toplevel` — window management (SSD only, no CSD)
- `wl_seat` → `wl_keyboard` + `wl_pointer` — input
- `wp_cursor_shape_v1` — set I-beam cursor over terminal area
- `wl_output` — monitor scale factor for DPI awareness
- `wp_fractional_scale_v1` — fractional DPI scaling (if available)

Vulkan surface via `VK_KHR_wayland_surface` (`VkSurfaceKHR` from `wl_display` + `wl_surface`).

Keyboard input: keycodes from `wl_keyboard.key`, mapped via xkbcommon to keysyms/UTF-8, then handed to vt.zig's key encoder.

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.

Focus events: track `wl_keyboard.enter` / `wl_keyboard.leave`, encode via `ghostty_focus_encode()` when terminal has `GHOSTTY_MODE_FOCUS_EVENT` set.

Event loop: `wl_display_get_fd()` polled alongside PTY fd. Single-threaded.

#### Clipboard (phase 2)

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.

### pty.zig

- `forkpty()` to spawn child shell (`$SHELL` or `/bin/sh`)
- Set `TERM=xterm-256color` in child environment
- Non-blocking master fd
- `read()` / `write()` helpers
- `SIGCHLD` handling for child exit
@@ -55,18 +67,33 @@ Event loop: `wl_display_get_fd()` polled alongside PTY fd. Single-threaded.

### vt.zig

Zig wrapper around `<ghostty/vt.h>` via `@cImport`:
- `Terminal` — init, feed bytes, resize, scroll, get render state
- `KeyEncoder` / `MouseEncoder` — encode input events to VT sequences
- `RenderState` — snapshot, iterate rows/cells, get colors/cursor
- Effect callbacks: write-to-pty, device-attributes, title-changed, etc.
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)`.

Wrapped handle types:
- `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`.
- `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`.
- `RowIterator` — `ghostty_render_state_row_iterator_new` / `_free`. Iterate rows via `_next`. Each row yields a `RowCells`.
- `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`.
- `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.
- `MouseEncoder` — `ghostty_mouse_encoder_new` / `_free`. Before each encode, sync via `ghostty_mouse_encoder_setopt_from_terminal`. Create `MouseEvent` (`_new` / `_free`), populate, and encode.

Required effect callbacks (set via `ghostty_terminal_set`):
- `GHOSTTY_TERMINAL_OPT_WRITE_PTY` — terminal sends responses (device status, cursor position) back to shell. **Mandatory**.
- `GHOSTTY_TERMINAL_OPT_DEVICE_ATTRIBUTES` — DA1/DA2/DA3 query responses. Reports VT220 conformance.
- `GHOSTTY_TERMINAL_OPT_SIZE` — reports terminal dimensions to querying applications.
- `GHOSTTY_TERMINAL_OPT_XTVERSION` — returns "waystty" for XTVERSION queries.
- `GHOSTTY_TERMINAL_OPT_TITLE_CHANGED` — updates the xdg_toplevel title.
- `GHOSTTY_TERMINAL_OPT_COLOR_SCHEME` — stub (returns false).

System init: call `ghostty_sys_set(GHOSTTY_SYS_OPT_DECODE_PNG, decode_png)` before creating terminal if Kitty graphics support is desired (phase 2).

### font.zig

- fontconfig: find system monospace font at startup
- freetype: load font face, rasterize glyphs to bitmaps
- harfbuzz: shape text runs (ligatures, combining characters)
- Glyph atlas: single RGBA texture (e.g. 1024x1024), row-based packing, returns UV coords per glyph
- freetype: load font face, rasterize glyphs to single-channel (R8) bitmaps
- 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.
- 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).
- 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.

### renderer.zig

@@ -95,9 +122,12 @@ loop:
    if pty_fd readable:
        read pty → feed to terminal via ghostty_terminal_vt_write()

    if terminal dirty or resize:
        snapshot render state
        rebuild instance buffer
    if key repeat timer expired:
        re-send last key event to pty

    if terminal dirty (GHOSTTY_RENDER_STATE_OPTION_DIRTY) or resize:
        ghostty_render_state_update(render_state, terminal)
        rebuild instance buffer (skip clean rows via ROW_OPTION_DIRTY)
        render frame

    if child exited:
@@ -136,8 +166,9 @@ Zig package dependencies (via `build.zig.zon`):
- zig-wayland — Wayland protocol bindings
- vulkan-zig — Vulkan bindings generator

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

System C library linkage:
- libghostty-vt
- freetype2
- harfbuzz
- fontconfig
@@ -201,3 +232,18 @@ End-to-end: spawn waystty with `echo "hello"; exit`, verify clean exit. Can run 

- No premature optimization — get it working, then profile
- No threading unless profiling proves the single thread is the bottleneck

## Phased Features

Features deferred from v1 to keep initial scope minimal:

| Feature | Phase | Notes |
|---------|-------|-------|
| Clipboard (copy/paste) | 2 | `wl_data_device_manager` protocol work. First post-v1 priority. |
| Harfbuzz text shaping | 2 | Ligatures, complex scripts. v1 renders single codepoints per cell. |
| Kitty graphics protocol | 2 | Image display in terminal. Requires PNG decode callback + placement rendering. |
| Colored emoji | 2 | Separate RGBA atlas alongside the R8 glyph atlas. |
| Scrollbar rendering | 2 | Visual scrollbar. Viewport scrolling via `ghostty_terminal_scroll_viewport` is in v1. |
| Config file | future | `~/.config/waystty/config` for fonts, colors, keybindings. |
| URL detection/opening | future | Click-to-open URLs. |
| Multiple windows/tabs | future | Out of scope for a minimal terminal. |