- commit
- d4fdb99
- parent
- eaf6c5a
- author
- Eric Bower
- date
- 2025-10-14 09:11:54 -0400 EDT
docs: fmt
9 files changed,
+175,
-64
+76,
-1
1@@ -2,7 +2,7 @@
2
3 The goal of this project is to create a way to attach and detach terminal sessions without killing the underlying linux process.
4
5-When researching `zmx`, also read the README.md in the root of this project directory to learn more about the features, documentation, prior art, etc.
6+When researching `zmx`, also read the @README.md in the root of this project directory to learn more about the features, documentation, prior art, etc.
7
8 ## tech stack
9
10@@ -48,3 +48,78 @@ To inspect the source code for zig's standard library, look inside the `zig_std_
11 ## find ghostty library source code
12
13 To inspect the source code for zig's standard library, look inside the `ghostty_src` folder.
14+
15+## Issue Tracking
16+
17+We use bd (beads, https://github.com/steveyegge/beads) for issue tracking instead of Markdown TODOs or external tools.
18+
19+### Quick Reference
20+
21+```bash
22+# Find ready work (no blockers)
23+bd ready --json
24+
25+# Create new issue
26+bd create "Issue title" -t bug|feature|task -p 0-4 -d "Description" --json
27+
28+# Create with explicit ID (for parallel workers)
29+bd create "Issue title" --id worker1-100 -p 1 --json
30+
31+# Update issue status
32+bd update <id> --status in_progress --json
33+
34+# Link discovered work (old way)
35+bd dep add <discovered-id> <parent-id> --type discovered-from
36+
37+# Create and link in one command (new way)
38+bd create "Issue title" -t bug -p 1 --deps discovered-from:<parent-id> --json
39+
40+# Complete work
41+bd close <id> --reason "Done" --json
42+
43+# Show dependency tree
44+bd dep tree <id>
45+
46+# Get issue details
47+bd show <id> --json
48+
49+# Import with collision detection
50+bd import -i .beads/issues.jsonl --dry-run # Preview only
51+bd import -i .beads/issues.jsonl --resolve-collisions # Auto-resolve
52+```
53+
54+### Workflow
55+
56+1. **Check for ready work**: Run `bd ready` to see what's unblocked
57+1. **Claim your task**: `bd update <id> --status in_progress`
58+1. **Work on it**: Implement, test, document
59+1. **Discover new work**: If you find bugs or TODOs, create issues:
60+ - Old way (two commands): `bd create "Found bug in auth" -t bug -p 1 --json` then `bd dep add <new-id> <current-id> --type discovered-from`
61+ - New way (one command): `bd create "Found bug in auth" -t bug -p 1 --deps discovered-from:<current-id> --json`
62+1. **Complete**: `bd close <id> --reason "Implemented"`
63+1. **Export**: Changes auto-sync to `.beads/issues.jsonl` (5-second debounce)
64+
65+### Issue Types
66+
67+- `bug` - Something broken that needs fixing
68+- `feature` - New functionality
69+- `task` - Work item (tests, docs, refactoring)
70+- `epic` - Large feature composed of multiple issues
71+- `chore` - Maintenance work (dependencies, tooling)
72+
73+### Priorities
74+
75+- `0` - Critical (security, data loss, broken builds)
76+- `1` - High (major features, important bugs)
77+- `2` - Medium (nice-to-have features, minor bugs)
78+- `3` - Low (polish, optimization)
79+- `4` - Backlog (future ideas)
80+
81+### Dependency Types
82+
83+- `blocks` - Hard dependency (issue X blocks issue Y)
84+- `related` - Soft relationship (issues are connected)
85+- `parent-child` - Epic/subtask relationship
86+- `discovered-from` - Track issues discovered during work
87+
88+Only `blocks` dependencies affect the ready work queue.
+5,
-5
1@@ -14,11 +14,11 @@
2 - The `daemon` is managed by a supervisor like `systemd`
3 - We provide a `systemd` unit file that users can install that manages the `daemon` process
4 - The cli tool supports the following commands:
5- - `attach {session}`: attach to the pty process
6- - `detach`: detach from the pty process without killing it
7- - `kill {session}`: kill the pty process
8- - `list`: show all sessions and what clients are currently attached
9- - `daemon`: the background process that manages all sessions
10+ - `attach {session}`: attach to the pty process
11+ - `detach`: detach from the pty process without killing it
12+ - `kill {session}`: kill the pty process
13+ - `list`: show all sessions and what clients are currently attached
14+ - `daemon`: the background process that manages all sessions
15 - This project does **NOT** provide windows, tabs, or window splits
16 - It supports all the terminal features that the client's terminal emulator supports
17 - The current version only works on linux
+5,
-5
1@@ -15,11 +15,11 @@ This document outlines the plan for implementing the CLI scaffolding for the `zm
2
3 - Use `zig-clap` to define the command structure specified in `specs/cli.md`.
4 - This includes the global options (`-h`, `-v`) and the subcommands:
5- - `daemon`
6- - `list`
7- - `attach <session>`
8- - `detach <session>`
9- - `kill <session>`
10+ - `daemon`
11+ - `list`
12+ - `attach <session>`
13+ - `detach <session>`
14+ - `kill <session>`
15 - For each command, define the expected arguments and options.
16
17 ## 4. Integrate with `src/main.zig`
+13,
-13
1@@ -15,11 +15,11 @@ This document outlines the plan for implementing the `zmx daemon` subcommand, ba
2 ## 3. Implement Session Management
3
4 - Define a `Session` struct to manage the state of each PTY process. This struct will include:
5- - The session name.
6- - The file descriptor for the PTY.
7- - A buffer for the terminal output (scrollback).
8- - The terminal state, managed by `libghostty-vt`.
9- - A list of connected client IDs.
10+ - The session name.
11+ - The file descriptor for the PTY.
12+ - A buffer for the terminal output (scrollback).
13+ - The terminal state, managed by `libghostty-vt`.
14+ - A list of connected client IDs.
15 - Use a `std.StringHashMap(Session)` to store and manage all active sessions.
16
17 ## 4. Implement PTY Management
18@@ -31,19 +31,19 @@ This document outlines the plan for implementing the `zmx daemon` subcommand, ba
19 ## 5. Implement the Main Event Loop
20
21 - The core of the daemon will be an event loop (using `libxev` on Linux) that concurrently handles:
22- 1. New client connections on the main Unix socket.
23- 2. Incoming requests from connected clients.
24- 3. Output from the PTY processes.
25+ 1. New client connections on the main Unix socket.
26+ 1. Incoming requests from connected clients.
27+ 1. Output from the PTY processes.
28 - This will allow the daemon to be single-threaded and highly concurrent.
29
30 ## 6. Implement Protocol Handlers
31
32 - For each message type defined in `protocol.md`, create a handler function:
33- - `handle_list_sessions_request`: Responds with a list of all active sessions.
34- - `handle_attach_session_request`: Adds the client to the session's list of connected clients and sends them the scrollback buffer.
35- - `handle_detach_session_request`: Removes the client from the session's list.
36- - `handle_kill_session_request`: Terminates the PTY process and removes the session.
37- - `handle_pty_input`: Writes the received data to the corresponding PTY.
38+ - `handle_list_sessions_request`: Responds with a list of all active sessions.
39+ - `handle_attach_session_request`: Adds the client to the session's list of connected clients and sends them the scrollback buffer.
40+ - `handle_detach_session_request`: Removes the client from the session's list.
41+ - `handle_kill_session_request`: Terminates the PTY process and removes the session.
42+ - `handle_pty_input`: Writes the received data to the corresponding PTY.
43 - When there is output from a PTY, the daemon will create a `pty_output` message and send it to all attached clients.
44
45 ## 7. Integrate with `main.zig`
+24,
-14
1@@ -5,9 +5,10 @@ This document outlines the plan for implementing session restore functionality i
2 ## Overview
3
4 When a client detaches and later reattaches to a session, we need to restore the terminal to its exact visual state without replaying all historical output. We achieve this by:
5+
6 1. Parsing all PTY output through libghostty-vt to maintain an up-to-date terminal grid
7-2. Proxying raw bytes to attached clients (no latency impact)
8-3. Rendering the terminal grid to ANSI on reattach
9+1. Proxying raw bytes to attached clients (no latency impact)
10+1. Rendering the terminal grid to ANSI on reattach
11
12 ## 1. Add libghostty-vt Dependency
13
14@@ -18,6 +19,7 @@ When a client detaches and later reattaches to a session, we need to restore the
15 ## 2. Extend the Session Struct
16
17 Add to `Session` struct in `daemon.zig`:
18+
19 ```zig
20 const Session = struct {
21 name: []const u8,
22@@ -38,6 +40,7 @@ const Session = struct {
23 ## 3. Initialize Terminal Emulator on Session Creation
24
25 In `createSession()`:
26+
27 - After forking PTY, initialize libghostty-vt instance
28 - Configure terminal size (rows, cols) - query from PTY or use defaults (e.g., 24x80)
29 - Configure scrollback buffer size (make this configurable, default 10,000 lines)
30@@ -64,6 +67,7 @@ fn createSession(allocator: std.mem.Allocator, session_name: []const u8) !*Sessi
31 ## 4. Parse PTY Output Through Terminal Emulator
32
33 Modify `readPtyCallback()`:
34+
35 - Feed all PTY output bytes to libghostty-vt first
36 - Check if there are attached clients
37 - If clients attached: proxy raw bytes directly to them (existing behavior)
38@@ -101,6 +105,7 @@ fn readPtyCallback(...) xev.CallbackAction {
39 ## 5. Render Terminal State on Reattach
40
41 Create new function `renderTerminalSnapshot()`:
42+
43 - Get current grid from libghostty-vt
44 - Serialize grid to ANSI escape sequences
45 - Send rendered output to reattaching client
46@@ -151,12 +156,13 @@ fn renderTerminalSnapshot(session: *Session, allocator: std.mem.Allocator) ![]u8
47 ## 6. Modify handleAttachSession()
48
49 Update attach logic to:
50+
51 1. Check if session exists, create if not
52-2. If reattaching (session already exists):
53+1. If reattaching (session already exists):
54 - Render current terminal state using libghostty-vt
55 - Send rendered snapshot to client
56-3. Add client to session's attached_clients set
57-4. Start proxying raw PTY output
58+1. Add client to session's attached_clients set
59+1. Start proxying raw PTY output
60
61 ```zig
62 fn handleAttachSession(ctx: *ServerContext, client: *Client, session_name: []const u8) !void {
63@@ -203,6 +209,7 @@ fn handleAttachSession(ctx: *ServerContext, client: *Client, session_name: []con
64 ## 7. Handle Window Resize Events
65
66 Add support for window size changes:
67+
68 - When client sends window resize event, update libghostty-vt
69 - Update PTY window size with ioctl TIOCSWINSZ
70 - libghostty-vt will handle reflow automatically
71@@ -229,6 +236,7 @@ fn handleWindowResize(client: *Client, rows: u16, cols: u16) !void {
72 ## 8. Track Attached Clients Per Session
73
74 Modify session management:
75+
76 - Remove client from session.attached_clients on detach
77 - On disconnect, automatically detach client
78 - Keep session alive even when no clients attached
79@@ -248,6 +256,7 @@ fn handleDetachSession(client: *Client, session_name: []const u8, target_client_
80 ## 9. Clean Up Terminal Emulator on Session Destroy
81
82 In session deinit:
83+
84 - Free libghostty-vt resources
85 - Clean up attached_clients map
86
87@@ -265,6 +274,7 @@ fn deinit(self: *Session) void {
88 ## 10. Configuration Options
89
90 Add configurable options (future work):
91+
92 - Scrollback buffer size
93 - Default terminal dimensions
94 - Maximum grid memory usage
95@@ -272,15 +282,15 @@ Add configurable options (future work):
96 ## Implementation Order
97
98 1. ✅ Add libghostty-vt C bindings and build integration
99-2. ✅ Extend Session struct with vt fields
100-3. ✅ Initialize vt in createSession()
101-4. ✅ Feed PTY output to vt in readPtyCallback()
102-5. ✅ Implement renderTerminalSnapshot()
103-6. ✅ Modify handleAttachSession() to render on reattach
104-7. ✅ Track attached_clients per session
105-8. ✅ Handle window resize events
106-9. ✅ Clean up vt resources in session deinit
107-10. ✅ Test with multiple attach/detach cycles
108+1. ✅ Extend Session struct with vt fields
109+1. ✅ Initialize vt in createSession()
110+1. ✅ Feed PTY output to vt in readPtyCallback()
111+1. ✅ Implement renderTerminalSnapshot()
112+1. ✅ Modify handleAttachSession() to render on reattach
113+1. ✅ Track attached_clients per session
114+1. ✅ Handle window resize events
115+1. ✅ Clean up vt resources in session deinit
116+1. ✅ Test with multiple attach/detach cycles
117
118 ## Testing Strategy
119
+3,
-3
1@@ -54,7 +54,7 @@ The `list` command will output a table with the following columns:
2 - `CLIENTS`: The number of clients currently attached to the session.
3 - `CREATED_AT`: The date when the session was created
4
5----
6+______________________________________________________________________
7
8 #### `attach`
9
10@@ -71,7 +71,7 @@ zmx attach <session>
11 - `<session>`: The name of the session to attach to. This is a required argument.
12 - `<socket>`: The location of the unix socket file.
13
14----
15+______________________________________________________________________
16
17 #### `detach`
18
19@@ -88,7 +88,7 @@ zmx detach <session>
20 - `<session>`: The name of the session to detach from. This is a required argument.
21 - `<socket>`: The location of the unix socket file.
22
23----
24+______________________________________________________________________
25
26 #### `kill`
27
+5,
-5
1@@ -10,11 +10,11 @@ The `zmx daemon` subcommand starts the long-running background process that mana
2
3 The daemon is responsible for:
4
5-1. **PTY Management**: Creating, managing, and destroying PTY processes using `fork` or `forkpty`.
6-2. **Session State Management**: Maintaining the terminal state and a buffer of text output for each active session. This ensures that when a client re-attaches, they see the previous output and the correct terminal state.
7-3. **Client Communication**: Facilitating communication between multiple `zmx` client instances and the managed PTY processes via a Unix socket.
8-4. **Session Lifecycle**: Handling the lifecycle of sessions, including creation, listing, attachment, detachment, and termination (killing).
9-5. **Resource Management**: Managing system resources associated with each session.
10+1. **PTY Management**: Creating, managing, and destroying PTY processes using `fork` or `forkpty`.
11+1. **Session State Management**: Maintaining the terminal state and a buffer of text output for each active session. This ensures that when a client re-attaches, they see the previous output and the correct terminal state.
12+1. **Client Communication**: Facilitating communication between multiple `zmx` client instances and the managed PTY processes via a Unix socket.
13+1. **Session Lifecycle**: Handling the lifecycle of sessions, including creation, listing, attachment, detachment, and termination (killing).
14+1. **Resource Management**: Managing system resources associated with each session.
15
16 ## usage
17
+42,
-15
1@@ -13,6 +13,7 @@ All messages are currently serialized using **newline-delimited JSON (NDJSON)**.
2 ### Implementation
3
4 The protocol implementation is centralized in `src/protocol.zig`, which provides:
5+
6 - Typed message structs for all payloads
7 - `MessageType` enum for type-safe dispatching
8 - Helper functions: `writeJson()`, `parseMessage()`, `parseMessageType()`
9@@ -23,15 +24,18 @@ The protocol implementation is centralized in `src/protocol.zig`, which provides
10 The protocol uses a hybrid approach: JSON for control messages and binary frames for PTY output to avoid encoding overhead and improve throughput.
11
12 **Frame Format:**
13+
14 ```
15 [4-byte length (little-endian)][2-byte type (little-endian)][payload...]
16 ```
17
18 **Frame Types:**
19+
20 - Type 1 (`json_control`): JSON control messages (not currently used in framing)
21 - Type 2 (`pty_binary`): Raw PTY output bytes
22
23 **Current Usage:**
24+
25 - Control messages (attach, detach, kill, etc.): NDJSON format
26 - PTY output from daemon to client: Binary frames (type 2)
27 - PTY input from client to daemon: Binary frames (type 2)
28@@ -56,14 +60,19 @@ Responses are sent from the daemon to the client in response to a request. Every
29 ### `list_sessions`
30
31 - **Direction**: Client -> Daemon
32+
33 - **Request Type**: `list_sessions_request`
34+
35 - **Request Payload**: (empty)
36
37 - **Direction**: Daemon -> Client
38+
39 - **Response Type**: `list_sessions_response`
40+
41 - **Response Payload**:
42- - `status`: `ok`
43- - `sessions`: An array of session objects.
44+
45+ - `status`: `ok`
46+ - `sessions`: An array of session objects.
47
48 **Session Object:**
49
50@@ -75,43 +84,61 @@ Responses are sent from the daemon to the client in response to a request. Every
51 ### `attach_session`
52
53 - **Direction**: Client -> Daemon
54+
55 - **Request Type**: `attach_session_request`
56+
57 - **Request Payload**:
58- - `session_name`: string
59- - `rows`: u16 (terminal height in rows)
60- - `cols`: u16 (terminal width in columns)
61+
62+ - `session_name`: string
63+ - `rows`: u16 (terminal height in rows)
64+ - `cols`: u16 (terminal width in columns)
65
66 - **Direction**: Daemon -> Client
67+
68 - **Response Type**: `attach_session_response`
69+
70 - **Response Payload**:
71- - `status`: `ok` or `error`
72- - `error_message`: string (if status is `error`)
73+
74+ - `status`: `ok` or `error`
75+ - `error_message`: string (if status is `error`)
76
77 ### `detach_session`
78
79 - **Direction**: Client -> Daemon
80+
81 - **Request Type**: `detach_session_request`
82+
83 - **Request Payload**:
84- - `session_name`: string
85+
86+ - `session_name`: string
87
88 - **Direction**: Daemon -> Client
89+
90 - **Response Type**: `detach_session_response`
91+
92 - **Response Payload**:
93- - `status`: `ok` or `error`
94- - `error_message`: string (if status is `error`)
95+
96+ - `status`: `ok` or `error`
97+ - `error_message`: string (if status is `error`)
98
99 ### `kill_session`
100
101 - **Direction**: Client -> Daemon
102+
103 - **Request Type**: `kill_session_request`
104+
105 - **Request Payload**:
106- - `session_name`: string
107+
108+ - `session_name`: string
109
110 - **Direction**: Daemon -> Client
111+
112 - **Response Type**: `kill_session_response`
113+
114 - **Response Payload**:
115- - `status`: `ok` or `error`
116- - `error_message`: string (if status is `error`)
117+
118+ - `status`: `ok` or `error`
119+ - `error_message`: string (if status is `error`)
120
121 ### `pty_in`
122
123@@ -119,7 +146,7 @@ Responses are sent from the daemon to the client in response to a request. Every
124 - **Message Type**: `pty_in`
125 - **Format**: NDJSON
126 - **Payload**:
127- - `text`: string (raw UTF-8 text from terminal input)
128+ - `text`: string (raw UTF-8 text from terminal input)
129
130 This message is sent when a client wants to send user input to the PTY. It is a fire-and-forget message with no direct response. The input is forwarded to the shell running in the session's PTY.
131
132@@ -129,7 +156,7 @@ This message is sent when a client wants to send user input to the PTY. It is a
133 - **Message Type**: `pty_out`
134 - **Format**: NDJSON (used only for control sequences like screen clear)
135 - **Payload**:
136- - `text`: string (escape sequences or control output)
137+ - `text`: string (escape sequences or control output)
138
139 This JSON message is sent for special control output like initial screen clearing. Regular PTY output uses binary frames (see below).
140
+2,
-3
1@@ -4,7 +4,7 @@ This document outlines the specification how we are going to preserve session st
2
3 ## purpose
4
5-The `zmx attach` subcommand starts re-attaches to a previously created session. When doing this we want to restore the session to its current state, displaying the last working screen text, layout, text wrapping, etc. This will include a configurable scrollback buffer size that will also be restored upon reattach.
6+The `zmx attach` subcommand starts re-attaches to a previously created session. When doing this we want to restore the session to its current state, displaying the last working screen text, layout, text wrapping, etc. This will include a configurable scrollback buffer size that will also be restored upon reattach.
7
8 ## technical details
9
10@@ -15,8 +15,7 @@ The `zmx attach` subcommand starts re-attaches to a previously created session.
11 - When you reattach, the daemon does not send the historic byte stream; instead it renders the current grid into a fresh ANSI sequence and ships that down the Unix-domain socket to the new shpool attach client.
12 - The client simply write()s that sequence to stdout—your local terminal sees it and redraws the screen instantly.
13
14-So the emulator is not “between” client and daemon in the latency sense; it is alongside, maintaining state.
15-The only time it interposes is on re-attach: it briefly synthesizes a single frame so your local terminal can show the exact session image without having to replay minutes or hours of output.
16+So the emulator is not “between” client and daemon in the latency sense; it is alongside, maintaining state. The only time it interposes is on re-attach: it briefly synthesizes a single frame so your local terminal can show the exact session image without having to replay minutes or hours of output.
17
18 ## using libghostty-vt
19