Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
All notable changes to this project will be documented in this file.

The format follows [Keep a Changelog](https://keepachangelog.com/). Releases are
cut with `/release:release vX.Y.Z` — it curates the section below, commits, tags
cut with `/release-workflows:release vX.Y.Z` — it curates the section below, commits, tags
`vX.Y.Z`, and pushes; the tag triggers `.github/workflows/release.yml`, which
builds the DMG + `.deb`s and publishes to the apt repo. Bump
`[workspace.package].version` in `Cargo.toml` to match before tagging (the
Expand Down
43 changes: 26 additions & 17 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,31 @@
## Direction (read first)

Roost is a cross-platform (Mac + Linux) desktop terminal multiplexer
built around libghostty-vt. The architecture is two native UIs that
each embed the workspace + PTY supervisor in-process and serve a JSON
IPC socket for external tooling (`roostctl`, Claude hooks). No daemon.
built around libghostty-vt. It ships **two platform products** — Swift
+ AppKit on macOS and Rust + iced on Linux — that each embed the
workspace + PTY supervisor in-process and serve a JSON IPC socket for
external tooling (`roostctl`, Claude hooks). No daemon. **Three UI
implementations** live in the repo: `mac/` (Swift + AppKit),
`crates/roost-iced/` (iced, what the Linux package ships), and
`crates/roost-linux/` (gtk4-rs, the in-repo Linux development/parity
implementation).

* Mac UI: Swift + AppKit, `mac/` (bundle id `ai.stridelabs.Roost`).
* Linux UI: gtk4-rs + libadwaita, `crates/roost-linux/`.
* Linux UI (shipped): Rust + iced, `crates/roost-iced/` (packaged as `/usr/bin/roost`).
* Linux UI (in-repo dev/parity): gtk4-rs + libadwaita, `crates/roost-linux/`.
* CLI: `crates/roost-cli/` (binary `roostctl`).
* IPC + path resolution: `crates/roost-ipc/`.
* libghostty-vt FFI + OSC: `crates/roost-vt/`, `crates/roost-osc/`.

**North star.** Every surface — UI clicks, hotkeys, `roostctl`, and Lua
scripts — routes through **one core: the workspace operation set**; the
UI is a *reaction* to the core's events, never its own source of truth.
One contract (`roost-ipc`'s op set), two implementations (Swift + AppKit,
Rust + GTK) kept at behavioral parity. Optimize for **testability,
One contract (`roost-ipc`'s op set), three implementations (Swift + AppKit,
Rust + GTK, Rust + iced) kept at behavioral parity. Optimize for **testability,
programmability, clean architecture**: adding a capability is "add an op
+ thin adapters", not per-surface logic. When in doubt, ask: *does this
route through the one op set, keep the UI reactive, and stay at parity
across both implementations?*
across all three implementations?*

See [docs/development/vision.md](docs/development/vision.md) for the full
architecture, principles, and decision log;
Expand Down Expand Up @@ -56,16 +62,18 @@ See `docs/development/vision.md` for the design rationale and

## Architecture

- Two UIs (Swift Mac, gtk4-rs Linux). Each embeds the workspace + PTY
- Three UIs (Swift Mac; gtk4-rs Linux, in-repo dev/parity; Rust + iced
Linux, what the package ships). Each embeds the workspace + PTY
supervisor in-process.
- libghostty-vt is the terminal engine on both UIs — VT parsing,
- libghostty-vt is the terminal engine on all three UIs — VT parsing,
screen state, OSC parsing, key/mouse encoding.
- The renderer is ours on both sides: AppKit + Core Graphics on Mac,
Cairo + Pango on `GtkDrawingArea` on Linux. We walk
libghostty-vt's render state and draw cell-aligned rects + text.
- The renderer is ours across all three: AppKit + Core Graphics on
Mac, Cairo + Pango on `GtkDrawingArea` for the GTK dev UI, and iced +
wgpu for the shipped Linux UI. We walk libghostty-vt's render state
and draw cell-aligned rects + text.
- The PTY is ours — `forkpty(3)` directly on Mac (`mac/Sources/Roost/
PtySupervisor.swift`), `portable-pty` on Linux (`crates/roost-linux/
src/daemon/pty.rs`). One PTY per tab.
PtySupervisor.swift`), `portable-pty` on Linux (`crates/roost-engine/
src/pty.rs`, shared by both Linux UIs). One PTY per tab.
- External tools dial the running UI process at the bundle profile's
socket path (`~/Library/Caches/Roost/roost.sock` for Mac,
`$XDG_RUNTIME_DIR/roost/roost.sock` for Linux — fallback
Expand Down Expand Up @@ -157,10 +165,11 @@ wrapper small.
`<private>` by default; the file appender uses `privacy: .public`
to defeat that. For raw values without redaction, prefer the file
log.
- **Linux UI logs**: `roost-linux` writes `$XDG_STATE_HOME/roost/roost.log`
- **Linux UI logs**: the shipped iced UI writes `$XDG_STATE_HOME/roost/roost.log`
(default `~/.local/state/roost/roost.log`) **and** tees to stdout
(synchronous file appender in `crates/roost-linux/src/main.rs`, so
entries survive a crash). `tail -f` it while reproducing; set
(synchronous file appender in `crates/roost-iced/src/main.rs`, so
entries survive a crash) — the in-repo GTK UI's own log lives at the
same profile path under its `Gtk` bundle profile. `tail -f` it while reproducing; set
`RUST_LOG=info,roost_ipc=debug` to adjust. On macOS the Gtk dev
profile writes `~/Library/Logs/Roost-gtk/roost.log` — a **distinct**
file from the Swift app's `~/Library/Logs/Roost/roost.log`, so both UIs
Expand Down
7 changes: 4 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Roost — common dev tasks. Run `make` (or `make help`) to list them.
#
# Three native UIs around libghostty-vt: Swift + AppKit (mac/),
# Rust + gtk4-rs (crates/roost-linux), and the isolated Iced POC
# (crates/roost-iced), plus the roostctl CLI. See
# docs/development/vision.md for the architecture + north star.
# Rust + gtk4-rs (crates/roost-linux, in-repo dev/parity), and Rust +
# iced (crates/roost-iced, what the Linux package ships), plus the
# roostctl CLI. See docs/development/vision.md for the architecture +
# north star.

.DEFAULT_GOAL := help

Expand Down
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ libghostty-vt terminal per tab. The
`roostctl` companion CLI surfaces notifications when an agent in a tab needs
attention.

Two native UIs — **Swift + AppKit on macOS** (`Roost.app`) and **Rust + gtk4-rs
Two native UIs — **Swift + AppKit on macOS** (`Roost.app`) and **Rust + iced
on Linux** (`roost`) — each embed the workspace + PTY supervisor + a JSON-IPC
server **in-process** (no daemon). External tooling (`roostctl`, Claude Code
hooks) talks to the running UI over newline-delimited JSON on a Unix-domain
socket; the wire contract is in [`docs/reference/ipc.md`](docs/reference/ipc.md).
server **in-process** (no daemon). The Linux package ships the iced UI; GTK
(`crates/roost-linux`, gtk4-rs) remains in-repo as the development/parity
implementation. External tooling (`roostctl`, Claude Code hooks) talks to
the running UI over newline-delimited JSON on a Unix-domain socket; the wire
contract is in [`docs/reference/ipc.md`](docs/reference/ipc.md).

## Install

Expand Down Expand Up @@ -41,9 +43,12 @@ cd roost
mise install # Rust (rust-toolchain.toml) + Zig 0.15.x
./third_party/ghostty/build.sh # clones Ghostty at the pinned SHA, builds libghostty-vt

# Linux UI (needs: sudo apt install libgtk-4-dev libadwaita-1-dev pkg-config):
# Linux UI — iced, what the packaged .deb ships (needs: sudo apt install libclang-dev pkg-config):
cargo build --release -p roost-iced -p roost-cli # → target/release/{roost-iced,roostctl}
./linux/scripts/build-deb.sh 0.0.1-dev # …or build an installable .deb (stages it as /usr/bin/roost)

# Linux UI — gtk4-rs, the in-repo development/parity UI (needs: sudo apt install libgtk-4-dev libadwaita-1-dev pkg-config):
cargo build --release -p roost-linux -p roost-cli # → target/release/{roost,roostctl}
./linux/scripts/build-deb.sh 0.0.1-dev # …or build an installable .deb

# macOS UI (needs: brew install gtk4 libadwaita):
cd mac && swift build # or: ./mac/scripts/bundle.sh release → mac/build/Roost.app
Expand Down
5 changes: 4 additions & 1 deletion crates/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ Rust workspace for the Roost UIs and supporting crates:
- `roost-cli` — shell-integration CLI; binary is `roostctl`.
- `roost-engine` — toolkit-neutral workspace, persistence, PTY runtime, events, and IPC dispatch.
- `roost-ui-model` — toolkit-neutral config, themes, keybinds, palettes, providers, and projections.
- `roost-linux` — gtk4-rs + libadwaita adapter over the shared Rust engine.
- `roost-linux` — gtk4-rs + libadwaita adapter over the shared Rust engine; the in-repo
Linux development/parity implementation.
- `roost-iced` — iced adapter over the shared Rust engine; what the packaged Linux `.deb`
ships as `/usr/bin/roost`.
- `roost-vt` / `roost-osc` — libghostty-vt FFI wrapper + OSC scanner.

The daemon-era crates (`roost-core`, `roost-proto`, `roost-common`,
Expand Down
2 changes: 1 addition & 1 deletion docs/development/claude-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ above tests:
# macOS (Swift Roost.app)
tail -f ~/Library/Logs/Roost/roost.log

# Linux (gtk4-rs roost) — also tees to stdout
# Linux (iced roost, the packaged UI) — also tees to stdout
tail -f "${XDG_STATE_HOME:-$HOME/.local/state}/roost/roost.log"
```

Expand Down
13 changes: 8 additions & 5 deletions docs/development/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Roost has two active development surfaces:

1. The Rust workspace at `crates/` (`roost-ipc`, `roost-vt`, `roost-osc`, `roost-cli`, `roost-linux`).
1. The Rust workspace at `crates/` (`roost-ipc`, `roost-vt`, `roost-osc`, `roost-cli`, `roost-linux`, `roost-iced`).
2. The Swift package at `mac/` (the macOS UI, `Roost.app`).

Both link the same vendored `libghostty-vt` static archive built from `third_party/ghostty/`.
Expand Down Expand Up @@ -35,19 +35,21 @@ mise install # Rust + Zig

| Goal | Command |
|---|---|
| Run the Linux UI | `~/.cargo/bin/cargo run -p roost-linux` |
| Run the Linux UI (iced, what the package ships) | `~/.cargo/bin/cargo run -p roost-iced` |
| Run the Linux UI (GTK, in-repo dev/parity) | `~/.cargo/bin/cargo run -p roost-linux` |
| Run the Mac UI | `./mac/scripts/bundle.sh debug && open mac/build/Roost.app` |
| Smoke-test the CLI | `~/.cargo/bin/cargo run -p roost-cli -- identify` |
| Rust unit tests | `~/.cargo/bin/cargo test --workspace --exclude roost-linux` |
| Linux UI tests | `~/.cargo/bin/cargo test -p roost-linux` (needs GTK) |
| Linux UI tests (GTK) | `~/.cargo/bin/cargo test -p roost-linux` (needs GTK) |
| Linux UI tests (iced) | `~/.cargo/bin/cargo test -p roost-iced` |
| Mac unit tests | `cd mac && swift test` |
| Rust formatting | `~/.cargo/bin/cargo fmt --all` |
| Rust lint | `~/.cargo/bin/cargo clippy --workspace --all-targets` |
| Build the docs site | `make docs` (or `make docs-serve` for live-reload at `http://127.0.0.1:7070`) |

The IPC sockets live at:

| OS | Swift/AppKit | GTK | Iced POC |
| OS | Swift/AppKit | GTK | Iced (dev build) |
|---|---|---|---|
| macOS | `~/Library/Caches/Roost/roost.sock` | `~/Library/Caches/Roost-gtk/roost.sock` | `~/Library/Caches/Roost-iced/roost.sock` |
| Linux | n/a | `$XDG_RUNTIME_DIR/roost/roost.sock` | `$XDG_RUNTIME_DIR/roost-iced/roost.sock` |
Expand All @@ -73,7 +75,8 @@ Rust tests live next to the code they exercise. Major coverage:
| `roost-vt` | FFI smoke tests against the vendored `libghostty-vt` archive (gated on `--features ffi`) |
| `roost-engine` | Workspace, PTY supervision, persistence, events, IPC dispatch, instance lock |
| `roost-ui-model` | Config, theme, keybind, palette, provider, and agent projection models |
| `roost-linux` | GTK presentation, native ports, input, and terminal rendering adapter |
| `roost-linux` | GTK presentation, native ports, input, and terminal rendering adapter — in-repo dev/parity UI |
| `roost-iced` | iced presentation, native ports, input, and terminal rendering adapter — what the Linux package ships |
| `roost-cli` | Escape decoder, shell quoter, target arg mapping |

Mac tests are under `mac/Tests/RoostTests/`; they cover the workspace state machine, PTY supervisor lifecycle, IPC server framing, single-instance flock, renderer, OSC scanner, key encoder, drag/drop math, and tab pill state machine. They run in headless `swift test` (no NSWindow required for any covered surface).
Expand Down
3 changes: 2 additions & 1 deletion docs/development/shared-rust-engine.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Shared Rust engine

`roost-engine` is Roost's toolkit-neutral authoritative Rust application
engine. GTK consumes it today; the Iced POC consumes it directly. A future
engine. Both Linux UIs consume it directly — the shipped iced UI and the
in-repo GTK development/parity UI. A future
Swift adapter can adopt the same boundary incrementally without exposing Rust
layouts across an ABI.

Expand Down
38 changes: 21 additions & 17 deletions docs/development/vision.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ the north star every PR is measured against.
A single-window, cross-platform terminal multiplexer: a sidebar of
projects, tabs per project, one terminal per tab. The differentiator is
the multi-project workspace with **notification routing for AI coding
agents** (Claude Code, Codex, …). It ships as **two native UIs that each
embed the workspace + PTY supervisor in-process** — Swift + AppKit on
macOS (`Roost.app`), Rust + gtk4-rs on Linux (`roost-linux`).
`libghostty-vt` is vendored once and linked into both for in-process VT
parsing and rendering. There is no daemon.
agents** (Claude Code, Codex, …). It ships **two platform products, each
embedding the workspace + PTY supervisor in-process** — Swift + AppKit on
macOS (`Roost.app`), Rust + iced on Linux (`roost`, the packaged `.deb`).
A third UI implementation, Rust + gtk4-rs (`roost-linux`), lives in the
repo as the Linux development/parity implementation. `libghostty-vt` is
vendored once and linked into all three for in-process VT parsing and
rendering. There is no daemon.

## The command core (north star)

Expand All @@ -39,15 +41,17 @@ truth.**
- A hotkey (`Cmd+Shift+T`), a `roostctl` call, and a Lua script all
invoke the **same** command — e.g. "run action" or "open tab".

**One contract, two implementations.** There is no shared *codebase*
core — Swift and Rust can't share one. There is one shared **contract**
— the IPC op set in [`crates/roost-ipc`](../reference/ipc.md) —
implemented by **Swift `Workspace` + AppKit** and **Rust `Workspace` +
GTK**. "Same interface" means same op contract + behavioral parity,
which the cross-platform E2E suite ([test-automation.md](test-automation.md))
exists to enforce. Per platform: identical command surface,
platform-specific guts (`forkpty` vs `portable-pty`, Core Graphics vs
Cairo).
**One contract, three implementations.** There is no shared *codebase*
core across languages — Swift and Rust can't share one. There is one
shared **contract** — the IPC op set in
[`crates/roost-ipc`](../reference/ipc.md) — implemented by **Swift
`Workspace` + AppKit**, **Rust + GTK**, and **Rust + iced** (the two
Rust UIs additionally share `roost-engine`). "Same interface" means same
op contract + behavioral parity, which the cross-platform E2E suite
([test-automation.md](test-automation.md)) exists to enforce. Per
implementation: identical command surface, platform-specific guts
(`forkpty` vs `portable-pty`; Core Graphics vs Cairo + Pango vs
iced + wgpu).

**Two seams** connect the surfaces to the core:

Expand Down Expand Up @@ -75,7 +79,7 @@ for at once:

Every decision below — and every new feature — is measured against it:
*does it route through the one op set, keep the UI reactive, and stay at
parity across both implementations?*
parity across all three implementations?*

## Why this shape

Expand Down Expand Up @@ -131,8 +135,8 @@ flowchart LR
macWS --- macIPC
end

subgraph LinuxApp["roost-linux (Rust + gtk4-rs)"]
linuxView["Cell renderer<br/>(Cairo + Pango)"]
subgraph LinuxApp["roost (Rust + iced) — packaged Linux UI"]
linuxView["Cell renderer<br/>(iced + wgpu)"]
linuxVT["libghostty-vt<br/>(in-process VT parse)"]
linuxWS["Workspace + PtySupervisor<br/>(tokio + portable-pty)"]
linuxIPC["JSON IPC server<br/>(tokio UnixListener)"]
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Roost is a desktop terminal multiplexer for AI coding agents. It runs on macOS and Linux, presents a sidebar of projects with tabs inside each project, and notifies you when an agent in a tab needs your attention.

The terminal engine is [libghostty-vt](https://ghostty.org), the parser/screen-state library extracted from Ghostty. Roost ships two native UIs — Swift + AppKit on macOS (`Roost.app`) and Rust + gtk4-rs on Linux (`roost`). There is no daemon: each UI embeds the workspace, the PTY supervisor, and a JSON IPC server in-process. External tooling (`roostctl`, Claude Code hooks) reaches the running UI over a Unix domain socket speaking newline-delimited JSON; see [IPC](reference/ipc.md) for the wire format. Persistence is a small `state.json` written atomically.
The terminal engine is [libghostty-vt](https://ghostty.org), the parser/screen-state library extracted from Ghostty. Roost ships two native UIs — Swift + AppKit on macOS (`Roost.app`) and Rust + iced on Linux (`roost`). GTK (`crates/roost-linux`, gtk4-rs) remains in the repo as the Linux development/parity implementation, but the packaged `.deb` ships iced. There is no daemon: each UI embeds the workspace, the PTY supervisor, and a JSON IPC server in-process. External tooling (`roostctl`, Claude Code hooks) reaches the running UI over a Unix domain socket speaking newline-delimited JSON; see [IPC](reference/ipc.md) for the wire format. Persistence is a small `state.json` written atomically.

## Install

Expand Down
Loading
Loading