Skip to content
v0.9 - Daemon & Everyday Ergonomics

v0.9 - Daemon & Everyday Ergonomics

Two releases. v0.9.0 extracts the headless daemon (gptyd) with attach and reattach, and puts the control surface behind a permission the user grants. v0.9.1 is everyday ergonomics: international input, sensitive workspaces, mouse and clipboard, links, shell integration, notifications, pane zoom and move, keybindings, project workspaces, colour schemes, latency and window fit.

v0.9.0 - Headless Daemon & Reattach

Done Area Item
⬜ CORE gptyd extraction spike - move WorkspaceEngine + IpcServer + event socket into a standalone Rust daemon; the Godot app becomes a rendering client over the same JSON-RPC (Godot-headless mode is the interim only, not the destination). PTYs survive GUI close.
⬜ CORE Grid wire protocol - term_get_diff / term_input over the socket, reusing TermGrid’s packed flat arrays as the wire format.
⬜ CORE Attach/reattach - GUI close leaves the daemon running; reopen attaches to the live workspace; gpty attach over SSH; HistoryStore serves pane read and scrollback across restarts.
⬜ SEC Panes are separated from the control surface by a permission the user grants - the enforcement half of the control-socket finding; the documentation half is closed under v0.7.1. Today a program in any pane can read and drive every other pane (SECURITY.md, Known limitations, has the mechanism), and gPTY’s stated goal is that it cannot do so without permission. It is sequenced here, after attach/reattach, because the daemon changes who the clients are: a headless daemon or gpty attach over SSH has no GUI to show a consent dialog, so the consent channel has to be designed with the client model, not before it. Design: (1) classify the caller - a connection whose peer process descends from one of this daemon’s pane shells (a parent-chain walk: /proc/<pid>/stat on Linux, proc_pidinfo on macOS, a process snapshot on Windows; an unsupported platform keeps today’s behaviour rather than failing closed) is a pane client, anything else is an outside client with today’s authority. On Linux prefer cgroup membership to the parent walk: run each pane in its own cgroup scope (systemd-run --user --scope, or a delegated subtree) and classify the peer by /proc/<pid>/cgroup, which survives a process reparenting to init or to a systemd --user subreaper; keep the parent walk as the fallback where no scope can be created; (2) tier every method - an explicit table next to the method registry puts each IPC method in observe (list-panes, pane-status, the pane’s own state declaration), read (pane-read, pane-wait, the vault verbs) or control (inject, pane-run, broadcast, new-pane, kill-pane, layout and concept changes, plugin install), and a test fails when a registered method has no tier, so a new verb cannot ship unclassified; (3) a user-owned grant - a pane client gets observe by default and needs a grant for the higher tiers, stored in a user-owned store keyed by attachment id (the PaneEnvStore pattern: a file, profile or plugin can never grant it), shown on the pane’s row and in its settings and revocable there; (4) a policy of allow (today), ask or deny - ask raises a consent dialog naming the pane, the method and what it would reach, once, remembered per pane; for a headless daemon the grant is pre-set through the user’s own settings, never answered by a pane client. Which policy is the default is the owner’s decision at pickup; ask is the recommendation, because it keeps the agent workflow (an agent in a pane calling gpty inject or gpty mcp, v1.0.0’s launch demo) one click away while ending silent cross-pane control. (5) scope is the workspace, with no grant that widens it - a pane client’s reach is its own workspace and nothing else: a command that names a pane in another workspace is answered exactly as a nonexistent pane (“not found”, so the other workspace cannot be probed), and there is deliberately no grant, setting or ask prompt that crosses the boundary. Rejected alternative: a cross-workspace grant. The workspace is the unit the user already reasons about (“root things here, ordinary things there”); a permission that crosses it is the confused-deputy path, and it multiplies the grant matrix from pane × tier to pane × tier × workspace, which is how a permission model becomes impossible to explain or audit. The legitimate cases do not need it: an agent that runs tests in a pane while the user watches does so inside one workspace, and anything that really spans workspaces is done by the user from the GUI, by an outside client (their own terminal’s gpty), or by moving the pane (v0.9.1). Outside clients keep today’s reach across workspaces. A workspace marked sensitive (v0.9.1) is excluded from the socket altogether, for outside clients too. State the limit in SECURITY.md when it ships: a process that double-forks and reparents to init (or to a systemd --user subreaper) escapes the parent walk, and on systems without restrictive ptrace defaults a same-user process can also attach to the daemon or read its memory, so this is a speed bump against opportunistic scripts and injected agents, not a boundary against a determined local attacker. Tests: a pane-descendant client is refused at each tier it has not been granted and served at each it has; an outside client is unaffected; ask prompts once and remembers; a revoked grant is refused again; every registered method has a tier; a pane client naming a pane in another workspace, by id or by label, gets “not found” and nothing is delivered; the audit’s two-pane probe, kept as a script by the v0.7.1 re-audit item, now fails to inject. On landing, rewrite the Known limitations entry and the in-scope line in SECURITY.md to say what is enforced.
⬜ IPC Rust-side IPC param validation - deserialize and validate IPC request params in Rust (gpty-ipc) before dispatch. Promoted from Future: the daemon refactor is the natural home (Rust owns the request path end-to-end), and validating the pre-daemon GUI queue would be throwaway work. GDScript handlers remain untyped.

v0.9.1 - Everyday Ergonomics

Same evidence as v0.8.1 (the October 2026 daily-driver gap assessment; items are marked verified, code or not tested). Where v0.8.1 makes gPTY render and answer like a terminal, this release makes it pleasant to live in for eight hours: selecting, linking, knowing what finished, rearranging, rebinding, and opening a project. It sits after the daemon deliberately. These items matter most once sessions survive GUI close; project workspaces and notifications-while-detached depend on the attach model; and the OSC 133 marks must be designed with HistoryStore in mind, so that they persist with scrollback and are served again after a reattach rather than living only in the GUI’s memory. The items are independent of each other, so any of them can be pulled forward if the daemon slips. Boundary rules hold: display and user-initiated actions only, no state machine over terminal output, and every new OSC consumer is bounded, sanitised and security-reviewed before merge.

Done Area Item
⬜ TERM International input works in a terminal pane: AltGr, dead keys and IME composition - not tested; first in the queue because it is a hard blocker for anyone who needs it and invisible to a developer on a US layout. Probe first on Linux (X11 and Wayland), Windows and macOS: type é through a dead key, @ and € through AltGr on a German or Czech layout, and compose CJK through an IME into a pane. Check that key_event_to_bytes (keymap.rs) does not swallow composed text, that AltGr is not treated as Ctrl+Alt, and that the IME candidate window is positioned at the cursor cell. Fix whatever the probe finds and add keymap unit cases for the layouts; the platform half goes into the release verification, like the v0.8.0 assessment’s platform note. S to probe, M–L to fix depending on findings.
⬜ SEC A workspace can be marked sensitive: nothing from it is persisted and the socket cannot see it - design, resting on the v0.9.0 permission item and on HistoryStore. Root shells, production SSH sessions and password-manager CLIs produce output that today lands in the plaintext history database (SECURITY.md: scrollback is plaintext in user://), and every pane’s programs can reach the control surface; the only advice gPTY can give now is “do not mix them”. This makes “root things in one workspace, ordinary things in another” a feature that can be explained. What it is: a flag on a workspace, set and cleared by the user (workspace menu or settings), stored in the user-owned WorkspaceStore - a profile, layout, plugin or IPC call can never set or clear it, so a shared file cannot quietly turn a sensitive workspace ordinary. What it does: (1) no persistence - its panes write no HistoryStore rows (and no shell-integration marks), and flagging a workspace offers to delete the rows its panes already wrote; (2) GUI-only - its panes are not listed and cannot be targeted by inject, pane-run, pane-read, pane-wait, broadcast, kill-pane or focus-pane, for pane clients and outside clients alike, so the keyboard in the GUI is the only way in (no exception: an outside client is refused exactly as a pane client is, no setting, grant or flag allows it, and the rule is a permanent invariant recorded in AGENTS.md - the owner decided this on 2026-10-20, so it is not a choice to revisit at pickup); (3) a visible marker - a distinct border or title tint, a lock glyph on the sidebar row and in the window title while active, so a secret is not typed or pasted into the wrong workspace; (4) notifications and activity badges for it carry no output, as they already must. (5) one choke point, and every other path closed - a sensitive pane is treated as nonexistent by the single target resolver (_find_pane_by_label, the v0.9.0 scope check sits there too), by list-panes, by broadcast, by pane-wait’s pending matches and by pane-status, and it is left out of the event stream and of concept matching, so neither subscribe/eventsPoll nor a matched line can carry its output or its id to another client; the ambiguous-label error from v0.7.1 must skip its panes too, so a sensitive workspace’s existence and ids are never disclosed. What it is not: it is not encryption and not a sandbox, and a same-user process that reads the GUI’s memory or the terminal’s PTY is not stopped - state that in SECURITY.md, with the same speed-bump caveat as the permission model. Tests: with the flag set, no HistoryStore rows are appended for its panes and existing rows are removed when the user accepts; list-panes, inject, pane-run, pane-read, pane-wait, pane-status, kill-pane, focus-pane and broadcast do not see or reach its panes by id or label from a pane client or an outside client (both get “not found”); a test enumerates every registered method that takes a pane id or label and fails if one reaches a sensitive pane, so a verb added later cannot bypass the rule; an event subscriber and the ambiguous-label error never mention its panes; layoutLoad, a profile and layoutSave cannot set or clear the flag; the marker is present and the flag survives a restart through WorkspaceStore.
⬜ TERM Mouse and clipboard basics: context menu, primary selection, copy-on-select, file drop - code: a terminal pane has no right-click menu, no primary-selection use (clipboard_get_primary / clipboard_set_primary, i.e. Linux middle-click paste) and no files_dropped handler; the popup menus that exist belong to pane swap and position. Fix: a right-click menu on terminal panes - Copy, Paste, Select All, Search, Clear scrollback, Open link (when over one), and the pane actions (Zoom, Duplicate, Settings, Close) - that respects mouse reporting exactly as today (Shift bypasses); on Linux, selecting sets PRIMARY and middle-click pastes it (a no-op elsewhere); a Copy on select setting whose default follows platform convention (on for Linux, off elsewhere); dropping a file on a terminal inserts its path, quoted for the pane’s shell (POSIX single quotes, PowerShell '…', cmd "…"), through the bracketed-paste path so a newline or control byte in a file name cannot submit a line, and never executes anything. Tests: a quoting table covering spaces, single and double quotes, a newline, a leading - and non-ASCII names.
⬜ TERM A multi-line paste asks first when the shell cannot tell it from typing - code: Ctrl+Shift+V sends build_paste_payload(clipboard, _is_bracketed_paste()) straight to the PTY. With bracketed paste (DECSET 2004, which zsh, fish and current bash enable at the prompt) the shell holds the text for review. Without it, which covers a plain sh, many SSH targets and any program that has not asked for it, every newline in the text is Enter, so a pasted block of commands runs before the user has read the last line. The existing guard strips control bytes and keeps newlines on purpose, because a multi-line paste into a plain shell is a normal thing to want, so this item adds a pause and does not remove the capability. Fix: when the pane is not in bracketed-paste mode and the payload contains a newline, show a confirmation with the line count, the byte count and a preview (at most 8 lines of 120 characters, control characters escaped, under the Workspace Trust text rules) and two buttons, Paste and Cancel. Cancel has focus and Escape cancels, so the Enter that habitually follows a paste cannot approve it. It covers every GUI paste route, so the middle-click PRIMARY paste added by the mouse item above arrives with the guard and not before it; this item lands no later than that one. Settings → Terminal gets Confirm multi-line paste: when the shell has no bracketed paste (default) / always / never. A single-line paste never asks. Nothing here touches inject, pane-run or any IPC path: those are not pastes and are governed by their own targeting rules, so the sensitive-workspace rule is unaffected. Tests: a table over bracketed or plain, one line, a trailing newline and several lines, and the three settings, for whether the dialog appears; Cancel sends no bytes; Paste sends exactly the bytes build_paste_payload returns; the default focus and the Escape behaviour.
⬜ TERM Links: URLs, OSC 8 hyperlinks and path:line open from the terminal - code: the terminal pane has no URL handling and OSC 8 is not surfaced. Fix: detect http(s):// and mailto: in the visible rows (underline on hover, Ctrl+click) and surface OSC 8 hyperlinks; open through the Markdown view’s existing confirm-then-OS.shell_open path with the same scheme allow-list and nothing else (plugin [[link_handlers]] stay unconsulted, per the 2026-10 decision). A path:line[:col] that resolves to an existing regular file opens in a Code Viewer pane, scrolled to the line, through the file tree’s in-app route (ViewableFiles), resolved against the pane’s cwd from the v0.8.1 OSC 7 item. Detection runs Rust regex (linear time) over visible rows only. Because an OSC 8 label can differ from its target, the confirmation shows the real target, escaped and capped under the Workspace Trust text rules.
⬜ TERM Shell integration: prompt marks (OSC 133), last-output actions and command status - the highest-leverage item in the release: output is one undifferentiated stream today. Fix: a bounded tap for OSC 133 (A prompt start, B command start, C output start, D;<exit> end) records marks against grid rows, persisted with scrollback in HistoryStore so they work after a restart and, with v0.9.0, after a reattach; opt-in snippets for bash, zsh and fish in the docs, and gpty shell-integration <shell> that prints the snippet and never installs it. Features: jump to the previous or next prompt, select or copy the last command’s output, an exit-status marker in the gutter with the command’s duration, and a user-initiated “Send last output to Inspector” using the existing capture-to-Inspector route (display only; it never injects input). Security: marks are display metadata and a program can forge them, so the worst case is a wrong highlight (state it in SECURITY.md) and nothing may make a decision from them, the same rule as paneStatus; the D field parses as a bounded integer; the tap obeys the alt-screen, capture-replay and resize suppression; a security review is required before merge.
⬜ GUI Activity and notifications: unread badges, command-finished alerts and OS attention - today the sidebar can show agent state, but nothing signals that the build finished in a pane or workspace you are not looking at, and the code deliberately avoids OS.alert (toasts are in-window). Builds on the v0.8.1 bell badge and the OSC 133 durations. Fix: an unseen-output and unseen-event marker per pane row and workspace row, cleared on activation; a setting Notify when a command finishes after N seconds while the window is unfocused (requires the marks; no timing heuristic without them); the agent states already declared through gpty state (needs-attention, failed) feed the same badge, display only, no orchestration; and an attention request (taskbar flash or dock bounce) in the first cut. True desktop notifications need a per-platform shim, since Godot has no native notification API - decide at pickup whether to build it. A notification never carries terminal output, only the pane label and status, so nothing sensitive lands on a lock screen.
⬜ GUI Pane zoom, duplicate and move - code: terminal panes have no zoom (the zoom code that exists is the link graph’s). Fix: Ctrl+Shift+Z toggles a zoomed pane that fills the grid (the others stay alive and hidden, the sidebar row marks it zoomed, any new-pane action unzooms, and a saved layout stores the unzoomed geometry); Duplicate creates the same type with the same settings and cwd under a fresh attachment_id, copying the user’s own per-pane env only because the user asked for the duplicate; Move to workspace re-parents the live pane without touching its PTY. Zoom is pure view state with no IPC surface. Tests: geometry after unzoom equals geometry before (reusing the tile-geometry helpers in test_terminal_manager.gd), and Duplicate and Move leave ids unique.
⬜ GUI Move mode: pick a pane up with the mouse and drop it somewhere else - panes can change place only through the swap button’s menu, and the model is a 60-unit grid of tiles. Fix: a Move mode toggled from the palette and a chord (the default is chosen at pickup against the registered set; Ctrl+Shift+R, Ctrl+Shift+M and the Alt+arrow focus keys are taken, and Ctrl+Shift+arrow collides with TUIs), shown in the status bar as Move - Esc to exit and off at every launch. In the mode a grab overlay covers each pane and takes the mouse, so a terminal never sees a click and a program using mouse reporting is not disturbed; the cursor becomes a drag cursor; a press-and-drag moves a ghost outline of the pane (not a live terminal) with the target pane highlighted under it. Dropping on the middle of another pane swaps the two tiles’ positions, as the existing swap does. Dropping on the outer quarter of a pane docks the dragged pane on that side: the tile is removed the way a close removes it, the freed space goes to its neighbours, and the target splits to make room. A drop that would leave a tile under MIN_TILE is refused with a toast and the layout is unchanged. Escape cancels a drag in progress, and a second Escape leaves the mode. The panes keep their processes, attachment_id and scrollback; only geometry moves, and a saved layout stores it like any other. Limits: it works inside one workspace and one window, since a drag cannot leave the window, and moving a pane to another workspace stays with the item above. It has no IPC, CLI or MCP surface. Land it in two steps, swap on drop first and edge docking second, since docking needs a tile-removal and split path that the grid model does not have today. Tests: a swap keeps both panes’ ids and sizes; after any dock the tiles cover the grid exactly once with no overlap (a property test over random layouts and drops); a refused drop leaves the geometry equal to before; Escape restores it; the mode is not persisted; and a click in the mode never reaches the terminal.
⬜ GUI Resize panes from the keyboard - the only way to move a divider is the mouse. Fix: a Resize mode on its own chord (the default is chosen at pickup against the registered set, and it becomes rebindable once the Keybindings item below lands, without waiting for it). In the mode the arrow keys move the active pane’s divider on that side: an arrow grows the pane toward that side, Shift+arrow shrinks it from that side, one grid unit per press and five with Alt, and key repeat works. A side with no divider (the window edge) ignores the key. Enter or Escape leaves the mode, and the status bar shows Resize - arrows grow, Shift shrinks, Esc to exit while it is on. Every key is taken by the mode and none is sent to the terminal, and the mode ends when the pane closes, focus moves to another pane or the workspace changes. It reuses the pointer drag’s divider-line geometry (_tiles_on_divider, the travel limits and the same unit commit) through a single nudge(wrapper, edge, units) on TileDrag, so a divider is a line that moves every tile on it, whichever side was grabbed, and a keypress emits tiles_resized once, like a drag. Tests: a nudge produces the same tiles as a pointer drag of the same step; it clamps at MIN_TILE and at the window edge; a side without a divider is ignored; a T-junction moves every tile on the line; keys never reach the PTY while the mode is on; and the mode ends on each of the cases above.
⬜ GUI Keybindings are configurable - code: ShortcutManager.register takes a default bind and nothing stores or edits it; there is no rebind UI. Fix: Settings → Keys lists every registered action (new pane per type, close, sidebar, palette, fullscreen, workspace next/previous, focus movement, search), rebinds by capture, flags conflicts between actions, and resets per row or all; bindings persist in the settings file with per-entry validation (a corrupt entry falls back to that action’s default only). The v0.8.0 palette and shortcut list read the current binding, making this the single source of truth, and a short note lists the chords terminal apps commonly use, since Ctrl+Shift+… chords collide with some TUIs. Bindings are user-owned: profiles and plugin manifests cannot carry them.
⬜ GUI Project workspaces: a workspace bound to a directory and a profile - gPTY’s own take on opening a project, and the feature that gives the daemon a daily-use story. gpty open <dir> (and gpty .) creates or focuses a workspace named for the folder, spawns its profile’s panes with that directory as cwd, and persists the binding in WorkspaceStore; with v0.9.0 the workspace survives GUI close and reattaches. The cwd is user-originated (the command line or the file picker), never a profile or plugin field. It goes to the running GUI through the single-instance IPC path as a new workspaceOpen method; like pluginInstall it is routed but not an MCP tool by default, since an agent opening a workspace in a chosen directory is a spawn surface (decide explicitly at pickup). Depends on the v0.8.1 cwd item; layout restore works without the daemon, survival across GUI close needs it. Workspace directory editing landed 2026-10-21: each workspace row has a folder button that picks an absolute directory (the file picker; Use default clears it), the cwd persists in workspaces.json, and every terminal spawned or restored in that workspace starts there - so the same lazygit plugin profile can be activated into different repositories. That is this item’s file-picker half; gpty open and the profile binding remain.
⬜ GUI Workspaces can be restored: reopen a closed one, return one to its profile - code: closing a workspace (_close_workspace) asks only when a pane is busy, then _close_workspace_now resets its terminals and frees it, so a whole arranged workspace is gone and nothing keeps it. Session auto-save brings the open workspaces back on launch and is no help once one is closed. A workspace also does not know which profile it came from: _active_profile is one app-wide string used for the sidebar accent, set by the last activation and not persisted, so after panes have been added, closed and resized the only way back to the starting arrangement is to find and activate the profile by hand. Fix, two actions over the same saved-layout machinery. (1) Reopen closed workspace: on close, keep the workspace’s saved layout state (the tiles session auto-save already writes, so there is no new schema) in a bounded list in the user-owned WorkspaceStore, the last 5 with the oldest dropped; a workspace marked sensitive is never added, because nothing from it may be persisted. The workspace’s sidebar actions and the palette offer Reopen "<name>", which restores through the normal restore path: the trust gate and the user-owned per-pane environment apply exactly as at app restart, each terminal starts a fresh shell in its last directory (the v0.8.1 OSC 7 item) with its scrollback reloaded from HistoryStore by attachment_id, and no process is resumed, which the entry says. Retention: a closed workspace’s scrollback is kept while it is on the list and deleted at the first of these: it falls off the list, 7 days pass since it was closed, or the user chooses Forget on the entry. The closing time is stored in the list entry, because stored lines carry no timestamps, and is checked at launch and at each close. Reopening deletes nothing, since the panes reuse their ids. The launch sweep counts the list’s pane ids as open, otherwise it would delete them at the next launch, and a pane’s environment entry follows the same rule. This is the one exception to the immediate deletion on close in the v0.8.0 scrollback item. (2) Restore to profile: a workspace activated from a profile records the profile’s name in WorkspaceStore, the same binding the project-workspaces item above adds, and Restore to <profile> re-activates it through the existing ask-then-activate walk, so every busy pane is named in one dialog before anything closes; a binding whose profile has been deleted or renamed hides the action. Neither action is reachable from IPC, the CLI or MCP, a layout or profile cannot carry the closed list or the binding, and the existing rule that an untrusted profile is refused over layoutLoad is unchanged. Tests: close then reopen round-trips the tiles in order; the list is capped at 5 and dropping the oldest deletes its scrollback; an entry older than 7 days is deleted at launch; the launch sweep keeps the listed panes’ rows; Forget deletes them; a sensitive workspace is not kept; reopening a layout that needs trust still prompts; restoring to a profile names busy panes first and is refused when the profile is gone.
⬜ GUI Recent directories in the palette: insert a cd into a place you have been - the saved-commands item (v0.8.0) covers the directories you chose to save, and this covers the ones you did not. The first command after a launch is usually a cd, and where you actually worked is already known once the v0.8.1 OSC 7 item gives each terminal a working directory. Fix: record each terminal’s reported directory in a user-owned store (recent_dirs.json, the BasePersistenceManager pattern, owner-only): at most 20 distinct absolute paths, most recent first, a path already listed moves to the top. After the v0.8.0 palette and saved-commands items land, the palette gets a Recent directories section listing cd <path> entries, filterable by any part of the path. Choosing one writes cd and the path, quoted for the active pane’s shell, to that terminal and never sends Enter; there is no run flag here, and a saved command is how you opt in to that. A Pin action on an entry copies it into the saved commands, where run can be switched on. Quoting is one helper shared with the file-drop item above (POSIX single quotes, PowerShell '…', cmd cd /d "…"), whichever lands first, and the text goes through the bracketed-paste path so a directory name containing a newline cannot submit a line. Trust: a program can emit OSC 7 for any path, so an entry can be forged. The worst case is a misleading entry, which is why the full path is always shown, nothing is sent until the user chooses it, and nothing is executed. Paths with a non-local host (an ssh session), a control byte or a relative form are never recorded, and a directory that no longer exists when the palette opens is not shown. The store is not readable over IPC, the CLI, MCP, a profile or a plugin, and a workspace marked sensitive (the v0.9.1 item above) records nothing into it, since a root shell’s directories are what that flag exists to keep out. When the active pane is not a terminal the entries are disabled with a reason, as for saved commands. Tests: the cap and the move-to-top order, each rejected form above, the quoting table (a space, a single quote, a double quote, a newline, non-ASCII), the exact bytes written with no Enter, and the sensitive-workspace exclusion.
⬜ GUI Colour schemes: import, and light mode that follows the system - only three ship (dracula, solarized-dark, vivid under godot/schemes/). Fix: import Alacritty (TOML/YAML), iTerm2 (.itermcolors) and Base16 palettes through a validating parser (closed colour keys, 16 palette entries plus foreground, background, cursor and selection, capped size, sanitised name - a scheme is untrusted file input); ship a few more with permissive licences (gruvbox, nord, tokyo night, catppuccin, one-light, solarized-light - record each licence, see LICENSE-EXCEPTIONS.md); and an Appearance: follow system mode that switches a configured light/dark scheme pair using the OS dark-mode state. The active scheme also feeds the v0.8.1 colour-query replies and sits beside the v0.8.0 theme tokens, so keep the terminal palette distinct from UI chrome colours.
⬜ TERM Input latency and throughput are measured and gated - godot/tests/bench/draw_bench.gd measures draw cost, but nothing measures keystroke-to-pixel latency or sustained throughput, so “feels fast” is unevidenced. Fix: a harness that records (a) key event to grid change to the first frame that draws it, (b) MB/s through a flood with the flood throttle active (cat of a large file, yes), and (c) memory and SQLite size with 16 panes at full scrollback; record the numbers in this item and the CHANGELOG, add a loose CI regression gate (thresholds generous enough not to flake on shared runners, the lesson of the parallel-test item), and publish the figures in the docs. Make no claim about other terminals unless they are run under the same method.
⬜ GUI Window fit: opacity and inner padding - code: no opacity or transparency setting exists, and text sits flush against the pane edge. Fix: a window-opacity control (hidden rather than broken where the compositor or platform cannot honour it) and a terminal inner-padding setting, applied by shrinking the grid rather than offsetting the draw, so row and column counts stay truthful. The Linux .desktop entry, icon and “Open gPTY here” file-manager action ship with v1.0.0’s Distribution item, not here.