diff --git a/AGENTS.md b/AGENTS.md index ca22970..d4b4947 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -30,15 +30,15 @@ Surface hierarchy (strict — do not blur these in code, docs, or UI copy): ## Stack - Native UI: GPUI with `alacritty_terminal` as the terminal model and render buffer. -- Application core: Rust, SQLite via `rusqlite`, exposed by `crates/runner-app`. +- Application core: Rust, SQLite via `rusqlite`, exposed by `crates/runner-backend`. - PTY runtime: `portable-pty`. - Event transport: append-only NDJSON logs watched through `notify`. - Bundled CLI: `runner`, built from the `cli/` workspace member. ## Project Map -- `crates/runner-native/`: GPUI application, terminal renderer, composer, and terminal fixture corpus. -- `crates/runner-app/`: UI-agnostic application core, including SQLite, session manager, event bus, router, and MCP server. +- `crates/runner-app/`: GPUI application, terminal renderer, and terminal fixture corpus. +- `crates/runner-backend/`: UI-agnostic application core, including SQLite, session manager, event bus, router, and MCP server. - `cli/`: bundled `runner` CLI used by spawned agents. - `crates/runner-core/`: shared event-log primitives. - `design/`: Pencil source files. @@ -56,7 +56,7 @@ Surface hierarchy (strict — do not blur these in code, docs, or UI copy): - Workspace tests: `make test`. - Everything CI runs: `make verify` (check + test + clippy + fmt-check). -Prefer the smallest check that covers the change. For native UI changes, run the `runner-native` tests plus workspace clippy; for core behavior, run the relevant crate tests. +Prefer the smallest check that covers the change. For native UI changes, run the `runner-app` tests plus workspace clippy; for core behavior, run the relevant crate tests. ## Engineering Conventions diff --git a/Cargo.lock b/Cargo.lock index 5a1017b..ad3c17c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4873,6 +4873,24 @@ checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" [[package]] name = "runner-app" version = "0.3.17" +dependencies = [ + "alacritty_terminal", + "anyhow", + "base64", + "futures", + "gpui-ce", + "runner-backend", + "runner-terminal", + "serde", + "serde_json", + "tempfile", + "tokio", + "unicode-segmentation", +] + +[[package]] +name = "runner-backend" +version = "0.3.17" dependencies = [ "base64", "chrono", @@ -4927,24 +4945,6 @@ dependencies = [ "ulid", ] -[[package]] -name = "runner-native" -version = "0.3.17" -dependencies = [ - "alacritty_terminal", - "anyhow", - "base64", - "futures", - "gpui-ce", - "runner-app", - "runner-terminal", - "serde", - "serde_json", - "tempfile", - "tokio", - "unicode-segmentation", -] - [[package]] name = "runner-terminal" version = "0.3.17" @@ -4952,7 +4952,7 @@ dependencies = [ "alacritty_terminal", "anyhow", "base64", - "runner-app", + "runner-backend", "serde", "serde_json", "tempfile", diff --git a/Cargo.toml b/Cargo.toml index 32075fa..500af30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [workspace] resolver = "2" members = [ - "crates/runner-app", + "crates/runner-backend", "crates/runner-core", - "crates/runner-native", + "crates/runner-app", "crates/runner-terminal", "cli", ] diff --git a/Makefile b/Makefile index 57cfc38..94c9172 100644 --- a/Makefile +++ b/Makefile @@ -21,4 +21,4 @@ test: verify: check test clippy fmt-check run: - cargo run -p runner-native + cargo run -p runner-app diff --git a/crates/runner-app/Cargo.toml b/crates/runner-app/Cargo.toml index f50b538..9f577b4 100644 --- a/crates/runner-app/Cargo.toml +++ b/crates/runner-app/Cargo.toml @@ -1,58 +1,27 @@ [package] name = "runner-app" version = "0.3.17" -description = "UI-agnostic application core for Runner: command bodies, session manager, event bus, router, and MCP server" edition.workspace = true authors.workspace = true +publish = false +default-run = "runner-app" -[lib] -name = "runner_app" +[[bin]] +name = "runner-app" +path = "src/main.rs" [dependencies] -runner-core = { path = "../runner-core" } -log = "0.4" -serde = { workspace = true } -serde_json = { workspace = true } +alacritty_terminal = "0.26" +anyhow = "1" base64 = "0.22" -rusqlite = { version = "0.32", features = ["bundled", "chrono", "serde_json"] } -# Derived row<->struct mapping for the repo layer (impl 0021). 0.36.x is -# the release whose rusqlite dependency is 0.32, matching the workspace -# rusqlite exactly so the Row types unify. -serde_rusqlite = "0.36" -r2d2 = "0.8" -r2d2_sqlite = "0.25" -chrono = { workspace = true } -thiserror = { workspace = true } -ulid = { workspace = true } -uuid = { workspace = true } -# Regular dep (not dev-only) because the sidecar writer needs -# NamedTempFile::persist for a Windows-safe atomic replace. -tempfile = "3" -# Filesystem watcher for the per-mission event-log tail (C7). The -# `macos_fsevent` backend is the default on macOS; on Linux it falls back to -# inotify, on Windows to ReadDirectoryChangesW. -notify = "6" -# MCP server (impl 0013): Unix socket listener for external clients. -# The `runner-mcp` sidecar bridges stdio ↔ this socket. -rmcp = { version = "1.7", features = ["server"] } -schemars = "1" -tokio = { version = "1", features = ["net", "rt", "io-util", "sync"] } -tokio-util = { version = "0.7", features = ["rt"] } -# Codex MCP config lives in ~/.codex/config.toml; toml_edit preserves -# user-authored comments + formatting on round-trip writes. -toml_edit = "0.25" - -# Unix-only deps: -# - libc: FIFO mkfifo + poll() inside session::tmux_runtime. -# - portable-pty: backing crate for session::pty_runtime, the -# in-process replacement for tmux (impl 0011). Windows keeps the -# tmux fallback until a named-pipe equivalent lands. -[target.'cfg(unix)'.dependencies] -libc = "0.2" -portable-pty = "0.9" +futures = "0.3" +gpui = { package = "gpui-ce", version = "0.3" } +runner-backend = { path = "../runner-backend" } +runner-terminal = { path = "../runner-terminal" } +serde.workspace = true +serde_json.workspace = true +tokio = { version = "1", features = ["sync"] } +unicode-segmentation = "1" [dev-dependencies] -# Used by issue #124's forwarder-contention regression test to hold the -# event-log flock from a second fd; matches the version `runner-core` -# pulls in transitively so the trait impl resolves identically. -fs2 = "0.4" +tempfile = "3" diff --git a/crates/runner-native/README.md b/crates/runner-app/README.md similarity index 87% rename from crates/runner-native/README.md rename to crates/runner-app/README.md index 064ddc4..89a4946 100644 --- a/crates/runner-native/README.md +++ b/crates/runner-app/README.md @@ -1,6 +1,6 @@ -# runner-native +# runner-app -Phase 3 walking skeleton for [the gpui-rewrite plan](../../docs/impls/gpui-rewrite/plan.md). It opens Runner's existing direct chats from the same SQLite database as the released app, spawns or resumes them through `runner_app::session::SessionManager`, and renders the manager's PTY byte stream with `alacritty_terminal` on GPUI. +Phase 3 walking skeleton for [the gpui-rewrite plan](../../docs/impls/gpui-rewrite/plan.md). It opens Runner's existing direct chats from the same SQLite database as the released app, spawns or resumes them through `runner_backend::session::SessionManager`, and renders the manager's PTY byte stream with `alacritty_terminal` on GPUI. ## Run @@ -17,8 +17,8 @@ GPUI requires the Xcode 26 Metal Toolchain component, already installed on the d Recorded PTY byte logs in `fixtures/*.ndjson` (header line + base64 data/input/exit events), replayed into a headless `Term` and compared against blessed `*.snapshot.txt` grids: ```sh -cargo test -p runner-native -UPDATE_SNAPSHOTS=1 cargo test -p runner-native +cargo test -p runner-app +UPDATE_SNAPSHOTS=1 cargo test -p runner-app ``` Current corpus: `claude-session` (real interactive TUI boot → prompt → streamed reply → /exit palette), `top-busy` (full-screen redraw churn), `width-torture` (CJK/emoji/ZWJ/box-drawing/SGR glyph classes). diff --git a/crates/runner-native/src/bootstrap.rs b/crates/runner-app/src/bootstrap.rs similarity index 96% rename from crates/runner-native/src/bootstrap.rs rename to crates/runner-app/src/bootstrap.rs index 37be842..5742aad 100644 --- a/crates/runner-native/src/bootstrap.rs +++ b/crates/runner-app/src/bootstrap.rs @@ -2,7 +2,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::{bail, Context as _, Result}; -use runner_app::{db, event_bus, events, mcp, repo, session, shell_path, windows, AppCore}; +use runner_backend::{db, event_bus, events, mcp, repo, session, shell_path, windows, AppCore}; pub const APP_IDENTIFIER: &str = "com.wycstudios.runner"; @@ -62,7 +62,7 @@ pub fn boot_core(paths: &NativePaths) -> Result { app_data_dir: paths.app_data_dir.clone(), sessions, buses: event_bus::BusRegistry::new(), - routers: runner_app::router::RouterRegistry::new(), + routers: runner_backend::router::RouterRegistry::new(), mcp: Arc::new(mcp::McpHandle::new()), windows: window_registry, events: events::EventChannel::new(), @@ -148,7 +148,7 @@ mod tests { app_data_dir: PathBuf::new(), sessions: session::SessionManager::new(shell_path::LoginShellEnv::default(), runtime), buses: event_bus::BusRegistry::new(), - routers: runner_app::router::RouterRegistry::new(), + routers: runner_backend::router::RouterRegistry::new(), mcp: Arc::new(mcp::McpHandle::new()), windows: Arc::new(windows::WindowRegistry::new()), events: events::EventChannel::new(), diff --git a/crates/runner-native/src/chat.rs b/crates/runner-app/src/chat.rs similarity index 97% rename from crates/runner-native/src/chat.rs rename to crates/runner-app/src/chat.rs index 2516149..c2c4ccf 100644 --- a/crates/runner-native/src/chat.rs +++ b/crates/runner-app/src/chat.rs @@ -33,7 +33,7 @@ impl NativeRoot { window: &mut Window, cx: &mut Context, ) -> Result<()> { - let _entry = runner_app::ops::session::session_get(&self.core, session_id)? + let _entry = runner_backend::ops::session::session_get(&self.core, session_id)? .with_context(|| format!("direct chat not found: {session_id}"))?; let pane_id = layout .root @@ -194,14 +194,14 @@ impl NativeRoot { return; }; let keystroke = &event.keystroke; - if runner_native::terminal_ime::terminal_key_route( + if runner_app::terminal_ime::terminal_key_route( chat.terminal_input.read(cx).is_composing(), keystroke.modifiers.platform, keystroke.modifiers.control, keystroke.modifiers.alt, keystroke.modifiers.function, &keystroke.key, - ) != runner_native::terminal_ime::TerminalKeyRoute::Raw + ) != runner_app::terminal_ime::TerminalKeyRoute::Raw { return; } @@ -310,7 +310,7 @@ impl NativeRoot { }; let mut spawned_id = None; let result = (|| -> Result { - let spawned = runner_app::ops::session::session_start_direct( + let spawned = runner_backend::ops::session::session_start_direct( &self.core, runner_id.to_owned(), None, @@ -371,7 +371,7 @@ impl NativeRoot { .get(session_id) .map(|chat| chat.terminal.size()) .unwrap_or_else(|| self.estimated_terminal_size(&layout, pane_id, window)); - runner_app::ops::session::session_resume( + runner_backend::ops::session::session_resume( &self.core, session_id, Some(size.0), @@ -434,7 +434,7 @@ impl NativeRoot { .active() .context("active tab is missing")? .upsert_input()?; - runner_app::ops::node::node_tab_upsert(&self.core, input)?; + runner_backend::ops::node::node_tab_upsert(&self.core, input)?; Ok(()) } diff --git a/crates/runner-app/src/lib.rs b/crates/runner-app/src/lib.rs index e806645..1ba78fe 100644 --- a/crates/runner-app/src/lib.rs +++ b/crates/runner-app/src/lib.rs @@ -1,91 +1,5 @@ -// Runner's UI-agnostic application core (impl 0031 Phase 2). -// -// Everything a frontend needs to run Runner lives here: the SQLite layer, -// the PTY session manager, the per-mission event bus + signal router, the -// MCP server, and the command bodies (`ops`). Frontends — the Tauri app -// today, the native GPUI binary in Phase 3 — are thin adapters: they build -// an `AppCore`, subscribe to its event channel, and delegate their command -// surface to `ops::*`. - -pub mod cli_install; -pub mod db; -pub mod error; -pub mod event_bus; -pub mod events; -pub mod mcp; -pub mod model; -pub mod ops; -pub mod repo; -pub mod router; -pub mod session; -pub mod shell_path; -pub mod windows; - -use std::path::PathBuf; -use std::sync::Arc; - -use events::EventChannel; -use session::manager::CoreSessionEvents; - -/// Shared application state. One instance per process, cheap to clone -/// (every field is an `Arc` or small value) — the Tauri layer stores it in -/// `app.manage`, the MCP handler clones it per connection. -#[derive(Clone)] -pub struct AppCore { - pub db: Arc, - /// Root of the app's per-user data tree — `$APPDATA/runner/` on real - /// installs, a tempdir in tests. Mission commands resolve event-log paths - /// relative to this via `runner_core::event_log::path`. - pub app_data_dir: PathBuf, - /// Live per-mission session manager. Created at app - /// start, shared across all frontends and the per-session - /// forwarder threads it spawns. - pub sessions: Arc, - /// Live per-mission event-bus watchers. Mounted by `mission_start` once - /// the opening events are durable; unmounted by `mission_stop` and on - /// any rollback path. - pub buses: Arc, - /// Live per-mission signal routers. Mounted alongside the bus so the - /// router observes the bootstrap `mission_goal` event during initial - /// replay and pushes the launch prompt into the lead's stdin. - pub routers: Arc, - /// MCP server lifecycle handle (impl 0013). Unix socket listener - /// that external clients connect to via the `runner-mcp` bridge. - pub mcp: Arc, - /// Cross-window coordination map (impl 0018). Tracks which subject - /// (mission / direct chat) each window is looking at + when it - /// was last focused, so exactly one window owns a duplicated subject's - /// PTY. - pub windows: Arc, - /// Broadcast channel every app-observable event flows through. The - /// frontend subscribes and forwards to its own event surface (the - /// Tauri layer re-emits to the webview verbatim). - pub events: EventChannel, - /// The application's user-facing version (the Tauri crate's - /// `CARGO_PKG_VERSION`, which the release bump updates). Advertised by - /// the MCP server's `ServerInfo`. - pub app_version: String, -} - -impl AppCore { - /// Session-event sink for spawn/resume/inject call sites. Holds the - /// manager as `Weak` because instances get stored inside the manager's - /// own session state (codex capture context) — a strong ref would cycle. - pub fn session_events(&self) -> CoreSessionEvents { - CoreSessionEvents::new( - Arc::clone(&self.db), - Arc::downgrade(&self.sessions), - Arc::clone(&self.windows), - self.events.clone(), - ) - } - - /// Broadcast the current window→subject map. Called after every window - /// registry mutation so all windows converge on a consistent picture of - /// who owns what. Broadcast, not targeted: each window filters by its - /// own subject (spec decision 5). - pub fn broadcast_focus_map(&self) { - self.events - .emit("window_focus_map", &self.windows.snapshot()); - } -} +pub mod bootstrap; +pub mod pane_layout; +pub mod terminal_ime; +pub mod terminal_resize; +pub mod text_util; diff --git a/crates/runner-native/src/main.rs b/crates/runner-app/src/main.rs similarity index 92% rename from crates/runner-native/src/main.rs rename to crates/runner-app/src/main.rs index 9158ada..a584cc4 100644 --- a/crates/runner-native/src/main.rs +++ b/crates/runner-app/src/main.rs @@ -12,21 +12,21 @@ use gpui::{ MouseButton, ScrollDelta, ScrollWheelEvent, SharedString, Subscription, TitlebarOptions, Window, WindowBounds, WindowOptions, }; -use runner_app::model::{Runner, SessionStatus}; -use runner_app::ops::session::DirectSessionEntry; -use runner_app::AppCore; -use runner_native::bootstrap::{ +use runner_backend::model::{Runner, SessionStatus}; +use runner_backend::ops::session::DirectSessionEntry; +use runner_backend::AppCore; +use runner_app::bootstrap::{ boot_core, native_paths, stop_running_sessions_on_quit, NativePaths, }; -use runner_native::pane_layout::{ +use runner_app::pane_layout::{ PaneLayout, PaneLeaf, PaneNode, PresetKind, SplitOrientation, TabSet, }; -use runner_native::terminal_ime::TerminalInput; +use runner_app::terminal_ime::TerminalInput; use runner_terminal::terminal::{TerminalBridge, TerminalSession}; use terminal_element::TerminalElement; -actions!(runner_native_ui, [Quit, TermPaste, NewTab]); +actions!(runner_app_ui, [Quit, TermPaste, NewTab]); mod chat; mod panes; @@ -103,21 +103,21 @@ impl NativeRoot { .detach(); let mut errors = Vec::new(); - let sessions = match runner_app::ops::session::session_list_recent_direct(&core) { + let sessions = match runner_backend::ops::session::session_list_recent_direct(&core) { Ok(sessions) => sessions, Err(error) => { errors.push(error.to_string()); Vec::new() } }; - let runners = match runner_app::ops::runner::runner_list(&core) { + let runners = match runner_backend::ops::runner::runner_list(&core) { Ok(runners) => runners, Err(error) => { errors.push(error.to_string()); Vec::new() } }; - let tabs = match runner_app::ops::node::node_list(&core) + let tabs = match runner_backend::ops::node::node_list(&core) .map_err(anyhow::Error::from) .and_then(|rows| TabSet::from_rows(&rows)) { @@ -151,14 +151,14 @@ impl NativeRoot { } fn refresh_sessions(&mut self) { - match runner_app::ops::session::session_list_recent_direct(&self.core) { + match runner_backend::ops::session::session_list_recent_direct(&self.core) { Ok(sessions) => self.sessions = sessions, Err(error) => self.error = Some(error.to_string()), } } fn reload_tabs(&mut self) -> Result<()> { - let rows = runner_app::ops::node::node_list(&self.core)?; + let rows = runner_backend::ops::node::node_list(&self.core)?; self.tabs.replace_rows(&rows) } diff --git a/crates/runner-native/src/pane_layout.rs b/crates/runner-app/src/pane_layout.rs similarity index 99% rename from crates/runner-native/src/pane_layout.rs rename to crates/runner-app/src/pane_layout.rs index 631ae8d..b7bd1fa 100644 --- a/crates/runner-native/src/pane_layout.rs +++ b/crates/runner-app/src/pane_layout.rs @@ -1,8 +1,8 @@ use std::collections::BTreeMap; use anyhow::{bail, Context as _, Result}; -use runner_app::ops::node::NodeTabUpsertInput; -use runner_app::repo::node::{NodeRow, NodeType}; +use runner_backend::ops::node::NodeTabUpsertInput; +use runner_backend::repo::node::{NodeRow, NodeType}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] diff --git a/crates/runner-native/src/panes.rs b/crates/runner-app/src/panes.rs similarity index 100% rename from crates/runner-native/src/panes.rs rename to crates/runner-app/src/panes.rs diff --git a/crates/runner-native/src/sidebar.rs b/crates/runner-app/src/sidebar.rs similarity index 100% rename from crates/runner-native/src/sidebar.rs rename to crates/runner-app/src/sidebar.rs diff --git a/crates/runner-native/src/terminal_element.rs b/crates/runner-app/src/terminal_element.rs similarity index 99% rename from crates/runner-native/src/terminal_element.rs rename to crates/runner-app/src/terminal_element.rs index 54491f2..a6a7595 100644 --- a/crates/runner-native/src/terminal_element.rs +++ b/crates/runner-app/src/terminal_element.rs @@ -22,8 +22,8 @@ use gpui::{ Window, }; -use runner_native::terminal_ime::TerminalInput; -use runner_native::terminal_resize::{ +use runner_app::terminal_ime::TerminalInput; +use runner_app::terminal_resize::{ size_push_verdict, terminal_grid_size, SizePushVerdict, TerminalGridSize, }; use runner_terminal::palette; diff --git a/crates/runner-native/src/terminal_ime.rs b/crates/runner-app/src/terminal_ime.rs similarity index 96% rename from crates/runner-native/src/terminal_ime.rs rename to crates/runner-app/src/terminal_ime.rs index 5e8eca5..c8b011c 100644 --- a/crates/runner-native/src/terminal_ime.rs +++ b/crates/runner-app/src/terminal_ime.rs @@ -175,7 +175,7 @@ impl TerminalInput { self.write_result.take() } - pub fn commit_text(&mut self, text: &str) -> runner_app::error::Result<()> { + pub fn commit_text(&mut self, text: &str) -> runner_backend::error::Result<()> { self.composition.clear(); if text.is_empty() { return Ok(()); @@ -185,12 +185,12 @@ impl TerminalInput { Ok(()) } - fn commit_marked_text(&mut self) -> runner_app::error::Result<()> { + fn commit_marked_text(&mut self) -> runner_backend::error::Result<()> { let text = self.marked_text().unwrap_or_default().to_owned(); self.commit_text(&text) } - fn record_write_result(&mut self, result: runner_app::error::Result<()>) { + fn record_write_result(&mut self, result: runner_backend::error::Result<()>) { self.write_result = Some(result.map_err(|error| error.to_string())); } } diff --git a/crates/runner-native/src/terminal_resize.rs b/crates/runner-app/src/terminal_resize.rs similarity index 100% rename from crates/runner-native/src/terminal_resize.rs rename to crates/runner-app/src/terminal_resize.rs diff --git a/crates/runner-native/src/text_util.rs b/crates/runner-app/src/text_util.rs similarity index 100% rename from crates/runner-native/src/text_util.rs rename to crates/runner-app/src/text_util.rs diff --git a/crates/runner-native/src/theme.rs b/crates/runner-app/src/theme.rs similarity index 100% rename from crates/runner-native/src/theme.rs rename to crates/runner-app/src/theme.rs diff --git a/crates/runner-native/tests/pane_layout.rs b/crates/runner-app/tests/pane_layout.rs similarity index 97% rename from crates/runner-native/tests/pane_layout.rs rename to crates/runner-app/tests/pane_layout.rs index 4de4c0e..49a62ee 100644 --- a/crates/runner-native/tests/pane_layout.rs +++ b/crates/runner-app/tests/pane_layout.rs @@ -1,5 +1,5 @@ -use runner_app::repo::node::{NodeRow, NodeType}; -use runner_native::pane_layout::{PaneLayout, PaneNode, PresetKind, SplitOrientation, TabSet}; +use runner_backend::repo::node::{NodeRow, NodeType}; +use runner_app::pane_layout::{PaneLayout, PaneNode, PresetKind, SplitOrientation, TabSet}; fn row(id: &str, position: i64, layout: &PaneLayout) -> NodeRow { NodeRow { diff --git a/crates/runner-native/tests/session_manager_integration.rs b/crates/runner-app/tests/session_manager_integration.rs similarity index 85% rename from crates/runner-native/tests/session_manager_integration.rs rename to crates/runner-app/tests/session_manager_integration.rs index 0825f7a..aba5457 100644 --- a/crates/runner-native/tests/session_manager_integration.rs +++ b/crates/runner-app/tests/session_manager_integration.rs @@ -3,10 +3,10 @@ use std::sync::Arc; use std::thread; use std::time::{Duration, Instant}; -use runner_app::ops::runner::CreateRunnerInput; -use runner_app::router::runtime::PermissionMode; -use runner_native::bootstrap::{boot_core, NativePaths}; -use runner_native::terminal_ime::TerminalInput; +use runner_backend::ops::runner::CreateRunnerInput; +use runner_backend::router::runtime::PermissionMode; +use runner_app::bootstrap::{boot_core, NativePaths}; +use runner_app::terminal_ime::TerminalInput; use runner_terminal::replay::visible_lines; use runner_terminal::terminal::{TerminalBridge, TerminalSession}; @@ -30,7 +30,7 @@ fn direct_chat_flows_from_app_core_session_manager_into_terminal_grid() { let temp = tempfile::tempdir().unwrap(); let paths = NativePaths::new(temp.path().join("app-data"), temp.path().join("logs")); let core = boot_core(&paths).unwrap(); - let runner = runner_app::ops::runner::runner_create( + let runner = runner_backend::ops::runner::runner_create( &core, CreateRunnerInput { handle: "phase3-seam".into(), @@ -47,7 +47,7 @@ fn direct_chat_flows_from_app_core_session_manager_into_terminal_grid() { }, ) .unwrap(); - let spawned = runner_app::ops::session::session_start_direct( + let spawned = runner_backend::ops::session::session_start_direct( &core, runner.id, None, @@ -69,7 +69,7 @@ fn direct_chat_flows_from_app_core_session_manager_into_terminal_grid() { terminal.submit_text("manager-owned-pty").unwrap(); let rendered = wait_for_text(&terminal, "manager-owned-pty"); - runner_app::ops::session::session_kill(&core, &spawned.id).unwrap(); + runner_backend::ops::session::session_kill(&core, &spawned.id).unwrap(); assert!( rendered, "manager output never reached the alacritty terminal grid" @@ -81,7 +81,7 @@ fn terminal_ime_commit_forwards_utf8_through_session_manager() { let temp = tempfile::tempdir().unwrap(); let paths = NativePaths::new(temp.path().join("app-data"), temp.path().join("logs")); let core = boot_core(&paths).unwrap(); - let runner = runner_app::ops::runner::runner_create( + let runner = runner_backend::ops::runner::runner_create( &core, CreateRunnerInput { handle: "terminal-ime".into(), @@ -98,7 +98,7 @@ fn terminal_ime_commit_forwards_utf8_through_session_manager() { }, ) .unwrap(); - let spawned = runner_app::ops::session::session_start_direct( + let spawned = runner_backend::ops::session::session_start_direct( &core, runner.id, None, @@ -124,7 +124,7 @@ fn terminal_ime_commit_forwards_utf8_through_session_manager() { visible_lines(&*term).join("\n") }; - runner_app::ops::session::session_kill(&core, &spawned.id).unwrap(); + runner_backend::ops::session::session_kill(&core, &spawned.id).unwrap(); assert!(rendered, "committed UTF-8 did not reach the terminal grid"); assert!(!visible.contains("pinyin"), "marked text reached the PTY"); } @@ -134,7 +134,7 @@ fn bridge_keeps_multiple_tab_sessions_attached_with_independent_geometry() { let temp = tempfile::tempdir().unwrap(); let paths = NativePaths::new(temp.path().join("app-data"), temp.path().join("logs")); let core = boot_core(&paths).unwrap(); - let runner = runner_app::ops::runner::runner_create( + let runner = runner_backend::ops::runner::runner_create( &core, CreateRunnerInput { handle: "phase4-tabs".into(), @@ -151,7 +151,7 @@ fn bridge_keeps_multiple_tab_sessions_attached_with_independent_geometry() { }, ) .unwrap(); - let first = runner_app::ops::session::session_start_direct( + let first = runner_backend::ops::session::session_start_direct( &core, runner.id.clone(), None, @@ -161,7 +161,7 @@ fn bridge_keeps_multiple_tab_sessions_attached_with_independent_geometry() { Some(24), ) .unwrap(); - let second = runner_app::ops::session::session_start_direct( + let second = runner_backend::ops::session::session_start_direct( &core, runner.id, None, @@ -190,6 +190,6 @@ fn bridge_keeps_multiple_tab_sessions_attached_with_independent_geometry() { assert_eq!(first_terminal.size(), (80, 24)); assert_eq!(second_terminal.size(), (120, 40)); - runner_app::ops::session::session_kill(&core, &first.id).unwrap(); - runner_app::ops::session::session_kill(&core, &second.id).unwrap(); + runner_backend::ops::session::session_kill(&core, &first.id).unwrap(); + runner_backend::ops::session::session_kill(&core, &second.id).unwrap(); } diff --git a/crates/runner-native/tests/terminal_ime.rs b/crates/runner-app/tests/terminal_ime.rs similarity index 96% rename from crates/runner-native/tests/terminal_ime.rs rename to crates/runner-app/tests/terminal_ime.rs index f537539..7f2cc60 100644 --- a/crates/runner-native/tests/terminal_ime.rs +++ b/crates/runner-app/tests/terminal_ime.rs @@ -1,4 +1,4 @@ -use runner_native::terminal_ime::{terminal_key_route, TerminalComposition, TerminalKeyRoute}; +use runner_app::terminal_ime::{terminal_key_route, TerminalComposition, TerminalKeyRoute}; #[test] fn composition_replaces_and_clears_marked_text_with_utf16_selection_math() { diff --git a/crates/runner-native/tests/text_util.rs b/crates/runner-app/tests/text_util.rs similarity index 98% rename from crates/runner-native/tests/text_util.rs rename to crates/runner-app/tests/text_util.rs index 9b8cb1a..b78725d 100644 --- a/crates/runner-native/tests/text_util.rs +++ b/crates/runner-app/tests/text_util.rs @@ -2,7 +2,7 @@ //! selection offsets are relative to the marked string, and cursor //! navigation must respect grapheme clusters, not Unicode scalars. -use runner_native::text_util::{ +use runner_app::text_util::{ marked_selection, next_grapheme_boundary, offset_from_utf16, offset_to_utf16, prev_grapheme_boundary, range_from_utf16, }; diff --git a/crates/runner-backend/Cargo.toml b/crates/runner-backend/Cargo.toml new file mode 100644 index 0000000..cf6386f --- /dev/null +++ b/crates/runner-backend/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "runner-backend" +version = "0.3.17" +description = "UI-agnostic application core for Runner: command bodies, session manager, event bus, router, and MCP server" +edition.workspace = true +authors.workspace = true + +[lib] +name = "runner_backend" + +[dependencies] +runner-core = { path = "../runner-core" } +log = "0.4" +serde = { workspace = true } +serde_json = { workspace = true } +base64 = "0.22" +rusqlite = { version = "0.32", features = ["bundled", "chrono", "serde_json"] } +# Derived row<->struct mapping for the repo layer (impl 0021). 0.36.x is +# the release whose rusqlite dependency is 0.32, matching the workspace +# rusqlite exactly so the Row types unify. +serde_rusqlite = "0.36" +r2d2 = "0.8" +r2d2_sqlite = "0.25" +chrono = { workspace = true } +thiserror = { workspace = true } +ulid = { workspace = true } +uuid = { workspace = true } +# Regular dep (not dev-only) because the sidecar writer needs +# NamedTempFile::persist for a Windows-safe atomic replace. +tempfile = "3" +# Filesystem watcher for the per-mission event-log tail (C7). The +# `macos_fsevent` backend is the default on macOS; on Linux it falls back to +# inotify, on Windows to ReadDirectoryChangesW. +notify = "6" +# MCP server (impl 0013): Unix socket listener for external clients. +# The `runner-mcp` sidecar bridges stdio ↔ this socket. +rmcp = { version = "1.7", features = ["server"] } +schemars = "1" +tokio = { version = "1", features = ["net", "rt", "io-util", "sync"] } +tokio-util = { version = "0.7", features = ["rt"] } +# Codex MCP config lives in ~/.codex/config.toml; toml_edit preserves +# user-authored comments + formatting on round-trip writes. +toml_edit = "0.25" + +# Unix-only deps: +# - libc: FIFO mkfifo + poll() inside session::tmux_runtime. +# - portable-pty: backing crate for session::pty_runtime, the +# in-process replacement for tmux (impl 0011). Windows keeps the +# tmux fallback until a named-pipe equivalent lands. +[target.'cfg(unix)'.dependencies] +libc = "0.2" +portable-pty = "0.9" + +[dev-dependencies] +# Used by issue #124's forwarder-contention regression test to hold the +# event-log flock from a second fd; matches the version `runner-core` +# pulls in transitively so the trait impl resolves identically. +fs2 = "0.4" diff --git a/crates/runner-app/migrations/0001_init.sql b/crates/runner-backend/migrations/0001_init.sql similarity index 100% rename from crates/runner-app/migrations/0001_init.sql rename to crates/runner-backend/migrations/0001_init.sql diff --git a/crates/runner-app/migrations/0002_persona_only_seeds.sql b/crates/runner-backend/migrations/0002_persona_only_seeds.sql similarity index 100% rename from crates/runner-app/migrations/0002_persona_only_seeds.sql rename to crates/runner-backend/migrations/0002_persona_only_seeds.sql diff --git a/crates/runner-app/migrations/0003_session_runtime.sql b/crates/runner-backend/migrations/0003_session_runtime.sql similarity index 100% rename from crates/runner-app/migrations/0003_session_runtime.sql rename to crates/runner-backend/migrations/0003_session_runtime.sql diff --git a/crates/runner-app/migrations/0004_mission_archived_at.sql b/crates/runner-backend/migrations/0004_mission_archived_at.sql similarity index 100% rename from crates/runner-app/migrations/0004_mission_archived_at.sql rename to crates/runner-backend/migrations/0004_mission_archived_at.sql diff --git a/crates/runner-app/migrations/0005_crew_system_prompt_addendum.sql b/crates/runner-backend/migrations/0005_crew_system_prompt_addendum.sql similarity index 100% rename from crates/runner-app/migrations/0005_crew_system_prompt_addendum.sql rename to crates/runner-backend/migrations/0005_crew_system_prompt_addendum.sql diff --git a/crates/runner-app/migrations/0006_drop_crews_signal_types.sql b/crates/runner-backend/migrations/0006_drop_crews_signal_types.sql similarity index 100% rename from crates/runner-app/migrations/0006_drop_crews_signal_types.sql rename to crates/runner-backend/migrations/0006_drop_crews_signal_types.sql diff --git a/crates/runner-app/migrations/0007_direct_runtime_sessions.sql b/crates/runner-backend/migrations/0007_direct_runtime_sessions.sql similarity index 100% rename from crates/runner-app/migrations/0007_direct_runtime_sessions.sql rename to crates/runner-backend/migrations/0007_direct_runtime_sessions.sql diff --git a/crates/runner-app/migrations/0008_drop_crews_orchestrator_policy.sql b/crates/runner-backend/migrations/0008_drop_crews_orchestrator_policy.sql similarity index 100% rename from crates/runner-app/migrations/0008_drop_crews_orchestrator_policy.sql rename to crates/runner-backend/migrations/0008_drop_crews_orchestrator_policy.sql diff --git a/crates/runner-app/migrations/0009_folders_tabs.sql b/crates/runner-backend/migrations/0009_folders_tabs.sql similarity index 100% rename from crates/runner-app/migrations/0009_folders_tabs.sql rename to crates/runner-backend/migrations/0009_folders_tabs.sql diff --git a/crates/runner-app/migrations/0010_tab_attention.sql b/crates/runner-backend/migrations/0010_tab_attention.sql similarity index 100% rename from crates/runner-app/migrations/0010_tab_attention.sql rename to crates/runner-backend/migrations/0010_tab_attention.sql diff --git a/crates/runner-app/migrations/0011_projects.sql b/crates/runner-backend/migrations/0011_projects.sql similarity index 100% rename from crates/runner-app/migrations/0011_projects.sql rename to crates/runner-backend/migrations/0011_projects.sql diff --git a/crates/runner-app/migrations/0012_drop_collapsed_view_state.sql b/crates/runner-backend/migrations/0012_drop_collapsed_view_state.sql similarity index 100% rename from crates/runner-app/migrations/0012_drop_collapsed_view_state.sql rename to crates/runner-backend/migrations/0012_drop_collapsed_view_state.sql diff --git a/crates/runner-app/migrations/0013_slot_runtime_override.sql b/crates/runner-backend/migrations/0013_slot_runtime_override.sql similarity index 100% rename from crates/runner-app/migrations/0013_slot_runtime_override.sql rename to crates/runner-backend/migrations/0013_slot_runtime_override.sql diff --git a/crates/runner-app/migrations/0014_nodes.sql b/crates/runner-backend/migrations/0014_nodes.sql similarity index 100% rename from crates/runner-app/migrations/0014_nodes.sql rename to crates/runner-backend/migrations/0014_nodes.sql diff --git a/crates/runner-app/migrations/0015_retire_folders.sql b/crates/runner-backend/migrations/0015_retire_folders.sql similarity index 100% rename from crates/runner-app/migrations/0015_retire_folders.sql rename to crates/runner-backend/migrations/0015_retire_folders.sql diff --git a/crates/runner-app/migrations/0016_session_last_size.sql b/crates/runner-backend/migrations/0016_session_last_size.sql similarity index 100% rename from crates/runner-app/migrations/0016_session_last_size.sql rename to crates/runner-backend/migrations/0016_session_last_size.sql diff --git a/crates/runner-app/migrations/0017_session_resume_on_launch.sql b/crates/runner-backend/migrations/0017_session_resume_on_launch.sql similarity index 100% rename from crates/runner-app/migrations/0017_session_resume_on_launch.sql rename to crates/runner-backend/migrations/0017_session_resume_on_launch.sql diff --git a/crates/runner-app/migrations/0018_slot_model_override.sql b/crates/runner-backend/migrations/0018_slot_model_override.sql similarity index 100% rename from crates/runner-app/migrations/0018_slot_model_override.sql rename to crates/runner-backend/migrations/0018_slot_model_override.sql diff --git a/crates/runner-app/migrations/0019_session_agent_options.sql b/crates/runner-backend/migrations/0019_session_agent_options.sql similarity index 100% rename from crates/runner-app/migrations/0019_session_agent_options.sql rename to crates/runner-backend/migrations/0019_session_agent_options.sql diff --git a/crates/runner-app/migrations/0020_slot_effort_override.sql b/crates/runner-backend/migrations/0020_slot_effort_override.sql similarity index 100% rename from crates/runner-app/migrations/0020_slot_effort_override.sql rename to crates/runner-backend/migrations/0020_slot_effort_override.sql diff --git a/crates/runner-app/src/cli_install.rs b/crates/runner-backend/src/cli_install.rs similarity index 100% rename from crates/runner-app/src/cli_install.rs rename to crates/runner-backend/src/cli_install.rs diff --git a/crates/runner-app/src/db.rs b/crates/runner-backend/src/db.rs similarity index 100% rename from crates/runner-app/src/db.rs rename to crates/runner-backend/src/db.rs diff --git a/crates/runner-app/src/error.rs b/crates/runner-backend/src/error.rs similarity index 100% rename from crates/runner-app/src/error.rs rename to crates/runner-backend/src/error.rs diff --git a/crates/runner-app/src/event_bus/mod.rs b/crates/runner-backend/src/event_bus/mod.rs similarity index 100% rename from crates/runner-app/src/event_bus/mod.rs rename to crates/runner-backend/src/event_bus/mod.rs diff --git a/crates/runner-app/src/events.rs b/crates/runner-backend/src/events.rs similarity index 100% rename from crates/runner-app/src/events.rs rename to crates/runner-backend/src/events.rs diff --git a/crates/runner-backend/src/lib.rs b/crates/runner-backend/src/lib.rs new file mode 100644 index 0000000..e806645 --- /dev/null +++ b/crates/runner-backend/src/lib.rs @@ -0,0 +1,91 @@ +// Runner's UI-agnostic application core (impl 0031 Phase 2). +// +// Everything a frontend needs to run Runner lives here: the SQLite layer, +// the PTY session manager, the per-mission event bus + signal router, the +// MCP server, and the command bodies (`ops`). Frontends — the Tauri app +// today, the native GPUI binary in Phase 3 — are thin adapters: they build +// an `AppCore`, subscribe to its event channel, and delegate their command +// surface to `ops::*`. + +pub mod cli_install; +pub mod db; +pub mod error; +pub mod event_bus; +pub mod events; +pub mod mcp; +pub mod model; +pub mod ops; +pub mod repo; +pub mod router; +pub mod session; +pub mod shell_path; +pub mod windows; + +use std::path::PathBuf; +use std::sync::Arc; + +use events::EventChannel; +use session::manager::CoreSessionEvents; + +/// Shared application state. One instance per process, cheap to clone +/// (every field is an `Arc` or small value) — the Tauri layer stores it in +/// `app.manage`, the MCP handler clones it per connection. +#[derive(Clone)] +pub struct AppCore { + pub db: Arc, + /// Root of the app's per-user data tree — `$APPDATA/runner/` on real + /// installs, a tempdir in tests. Mission commands resolve event-log paths + /// relative to this via `runner_core::event_log::path`. + pub app_data_dir: PathBuf, + /// Live per-mission session manager. Created at app + /// start, shared across all frontends and the per-session + /// forwarder threads it spawns. + pub sessions: Arc, + /// Live per-mission event-bus watchers. Mounted by `mission_start` once + /// the opening events are durable; unmounted by `mission_stop` and on + /// any rollback path. + pub buses: Arc, + /// Live per-mission signal routers. Mounted alongside the bus so the + /// router observes the bootstrap `mission_goal` event during initial + /// replay and pushes the launch prompt into the lead's stdin. + pub routers: Arc, + /// MCP server lifecycle handle (impl 0013). Unix socket listener + /// that external clients connect to via the `runner-mcp` bridge. + pub mcp: Arc, + /// Cross-window coordination map (impl 0018). Tracks which subject + /// (mission / direct chat) each window is looking at + when it + /// was last focused, so exactly one window owns a duplicated subject's + /// PTY. + pub windows: Arc, + /// Broadcast channel every app-observable event flows through. The + /// frontend subscribes and forwards to its own event surface (the + /// Tauri layer re-emits to the webview verbatim). + pub events: EventChannel, + /// The application's user-facing version (the Tauri crate's + /// `CARGO_PKG_VERSION`, which the release bump updates). Advertised by + /// the MCP server's `ServerInfo`. + pub app_version: String, +} + +impl AppCore { + /// Session-event sink for spawn/resume/inject call sites. Holds the + /// manager as `Weak` because instances get stored inside the manager's + /// own session state (codex capture context) — a strong ref would cycle. + pub fn session_events(&self) -> CoreSessionEvents { + CoreSessionEvents::new( + Arc::clone(&self.db), + Arc::downgrade(&self.sessions), + Arc::clone(&self.windows), + self.events.clone(), + ) + } + + /// Broadcast the current window→subject map. Called after every window + /// registry mutation so all windows converge on a consistent picture of + /// who owns what. Broadcast, not targeted: each window filters by its + /// own subject (spec decision 5). + pub fn broadcast_focus_map(&self) { + self.events + .emit("window_focus_map", &self.windows.snapshot()); + } +} diff --git a/crates/runner-app/src/mcp/mod.rs b/crates/runner-backend/src/mcp/mod.rs similarity index 100% rename from crates/runner-app/src/mcp/mod.rs rename to crates/runner-backend/src/mcp/mod.rs diff --git a/crates/runner-app/src/mcp/server.rs b/crates/runner-backend/src/mcp/server.rs similarity index 100% rename from crates/runner-app/src/mcp/server.rs rename to crates/runner-backend/src/mcp/server.rs diff --git a/crates/runner-app/src/mcp/tools/crew.rs b/crates/runner-backend/src/mcp/tools/crew.rs similarity index 100% rename from crates/runner-app/src/mcp/tools/crew.rs rename to crates/runner-backend/src/mcp/tools/crew.rs diff --git a/crates/runner-app/src/mcp/tools/mission.rs b/crates/runner-backend/src/mcp/tools/mission.rs similarity index 100% rename from crates/runner-app/src/mcp/tools/mission.rs rename to crates/runner-backend/src/mcp/tools/mission.rs diff --git a/crates/runner-app/src/mcp/tools/mod.rs b/crates/runner-backend/src/mcp/tools/mod.rs similarity index 100% rename from crates/runner-app/src/mcp/tools/mod.rs rename to crates/runner-backend/src/mcp/tools/mod.rs diff --git a/crates/runner-app/src/mcp/tools/project.rs b/crates/runner-backend/src/mcp/tools/project.rs similarity index 100% rename from crates/runner-app/src/mcp/tools/project.rs rename to crates/runner-backend/src/mcp/tools/project.rs diff --git a/crates/runner-app/src/mcp/tools/runner.rs b/crates/runner-backend/src/mcp/tools/runner.rs similarity index 100% rename from crates/runner-app/src/mcp/tools/runner.rs rename to crates/runner-backend/src/mcp/tools/runner.rs diff --git a/crates/runner-app/src/mcp/tools/session.rs b/crates/runner-backend/src/mcp/tools/session.rs similarity index 100% rename from crates/runner-app/src/mcp/tools/session.rs rename to crates/runner-backend/src/mcp/tools/session.rs diff --git a/crates/runner-app/src/mcp/tools/slot.rs b/crates/runner-backend/src/mcp/tools/slot.rs similarity index 100% rename from crates/runner-app/src/mcp/tools/slot.rs rename to crates/runner-backend/src/mcp/tools/slot.rs diff --git a/crates/runner-app/src/model.rs b/crates/runner-backend/src/model.rs similarity index 100% rename from crates/runner-app/src/model.rs rename to crates/runner-backend/src/model.rs diff --git a/crates/runner-app/src/ops/crew.rs b/crates/runner-backend/src/ops/crew.rs similarity index 100% rename from crates/runner-app/src/ops/crew.rs rename to crates/runner-backend/src/ops/crew.rs diff --git a/crates/runner-app/src/ops/mcp.rs b/crates/runner-backend/src/ops/mcp.rs similarity index 100% rename from crates/runner-app/src/ops/mcp.rs rename to crates/runner-backend/src/ops/mcp.rs diff --git a/crates/runner-app/src/ops/mission.rs b/crates/runner-backend/src/ops/mission.rs similarity index 100% rename from crates/runner-app/src/ops/mission.rs rename to crates/runner-backend/src/ops/mission.rs diff --git a/crates/runner-app/src/ops/mod.rs b/crates/runner-backend/src/ops/mod.rs similarity index 100% rename from crates/runner-app/src/ops/mod.rs rename to crates/runner-backend/src/ops/mod.rs diff --git a/crates/runner-app/src/ops/node.rs b/crates/runner-backend/src/ops/node.rs similarity index 100% rename from crates/runner-app/src/ops/node.rs rename to crates/runner-backend/src/ops/node.rs diff --git a/crates/runner-app/src/ops/project.rs b/crates/runner-backend/src/ops/project.rs similarity index 100% rename from crates/runner-app/src/ops/project.rs rename to crates/runner-backend/src/ops/project.rs diff --git a/crates/runner-app/src/ops/runner.rs b/crates/runner-backend/src/ops/runner.rs similarity index 100% rename from crates/runner-app/src/ops/runner.rs rename to crates/runner-backend/src/ops/runner.rs diff --git a/crates/runner-app/src/ops/runtime.rs b/crates/runner-backend/src/ops/runtime.rs similarity index 100% rename from crates/runner-app/src/ops/runtime.rs rename to crates/runner-backend/src/ops/runtime.rs diff --git a/crates/runner-app/src/ops/session.rs b/crates/runner-backend/src/ops/session.rs similarity index 100% rename from crates/runner-app/src/ops/session.rs rename to crates/runner-backend/src/ops/session.rs diff --git a/crates/runner-app/src/ops/slot.rs b/crates/runner-backend/src/ops/slot.rs similarity index 100% rename from crates/runner-app/src/ops/slot.rs rename to crates/runner-backend/src/ops/slot.rs diff --git a/crates/runner-app/src/repo/crew.rs b/crates/runner-backend/src/repo/crew.rs similarity index 100% rename from crates/runner-app/src/repo/crew.rs rename to crates/runner-backend/src/repo/crew.rs diff --git a/crates/runner-app/src/repo/mission.rs b/crates/runner-backend/src/repo/mission.rs similarity index 100% rename from crates/runner-app/src/repo/mission.rs rename to crates/runner-backend/src/repo/mission.rs diff --git a/crates/runner-app/src/repo/mod.rs b/crates/runner-backend/src/repo/mod.rs similarity index 100% rename from crates/runner-app/src/repo/mod.rs rename to crates/runner-backend/src/repo/mod.rs diff --git a/crates/runner-app/src/repo/node.rs b/crates/runner-backend/src/repo/node.rs similarity index 100% rename from crates/runner-app/src/repo/node.rs rename to crates/runner-backend/src/repo/node.rs diff --git a/crates/runner-app/src/repo/project.rs b/crates/runner-backend/src/repo/project.rs similarity index 100% rename from crates/runner-app/src/repo/project.rs rename to crates/runner-backend/src/repo/project.rs diff --git a/crates/runner-app/src/repo/runner.rs b/crates/runner-backend/src/repo/runner.rs similarity index 100% rename from crates/runner-app/src/repo/runner.rs rename to crates/runner-backend/src/repo/runner.rs diff --git a/crates/runner-app/src/repo/serde.rs b/crates/runner-backend/src/repo/serde.rs similarity index 100% rename from crates/runner-app/src/repo/serde.rs rename to crates/runner-backend/src/repo/serde.rs diff --git a/crates/runner-app/src/repo/session.rs b/crates/runner-backend/src/repo/session.rs similarity index 100% rename from crates/runner-app/src/repo/session.rs rename to crates/runner-backend/src/repo/session.rs diff --git a/crates/runner-app/src/repo/slot.rs b/crates/runner-backend/src/repo/slot.rs similarity index 100% rename from crates/runner-app/src/repo/slot.rs rename to crates/runner-backend/src/repo/slot.rs diff --git a/crates/runner-app/src/router/handlers.rs b/crates/runner-backend/src/router/handlers.rs similarity index 100% rename from crates/runner-app/src/router/handlers.rs rename to crates/runner-backend/src/router/handlers.rs diff --git a/crates/runner-app/src/router/mod.rs b/crates/runner-backend/src/router/mod.rs similarity index 100% rename from crates/runner-app/src/router/mod.rs rename to crates/runner-backend/src/router/mod.rs diff --git a/crates/runner-app/src/router/prompt.rs b/crates/runner-backend/src/router/prompt.rs similarity index 100% rename from crates/runner-app/src/router/prompt.rs rename to crates/runner-backend/src/router/prompt.rs diff --git a/crates/runner-app/src/router/runtime.rs b/crates/runner-backend/src/router/runtime.rs similarity index 100% rename from crates/runner-app/src/router/runtime.rs rename to crates/runner-backend/src/router/runtime.rs diff --git a/crates/runner-app/src/router/tests.rs b/crates/runner-backend/src/router/tests.rs similarity index 100% rename from crates/runner-app/src/router/tests.rs rename to crates/runner-backend/src/router/tests.rs diff --git a/crates/runner-app/src/session/codex_capture.rs b/crates/runner-backend/src/session/codex_capture.rs similarity index 100% rename from crates/runner-app/src/session/codex_capture.rs rename to crates/runner-backend/src/session/codex_capture.rs diff --git a/crates/runner-app/src/session/launch.rs b/crates/runner-backend/src/session/launch.rs similarity index 100% rename from crates/runner-app/src/session/launch.rs rename to crates/runner-backend/src/session/launch.rs diff --git a/crates/runner-app/src/session/manager/lifecycle.rs b/crates/runner-backend/src/session/manager/lifecycle.rs similarity index 100% rename from crates/runner-app/src/session/manager/lifecycle.rs rename to crates/runner-backend/src/session/manager/lifecycle.rs diff --git a/crates/runner-app/src/session/manager/mod.rs b/crates/runner-backend/src/session/manager/mod.rs similarity index 100% rename from crates/runner-app/src/session/manager/mod.rs rename to crates/runner-backend/src/session/manager/mod.rs diff --git a/crates/runner-app/src/session/manager/output.rs b/crates/runner-backend/src/session/manager/output.rs similarity index 100% rename from crates/runner-app/src/session/manager/output.rs rename to crates/runner-backend/src/session/manager/output.rs diff --git a/crates/runner-app/src/session/manager/spawn.rs b/crates/runner-backend/src/session/manager/spawn.rs similarity index 100% rename from crates/runner-app/src/session/manager/spawn.rs rename to crates/runner-backend/src/session/manager/spawn.rs diff --git a/crates/runner-app/src/session/manager/tests.rs b/crates/runner-backend/src/session/manager/tests.rs similarity index 100% rename from crates/runner-app/src/session/manager/tests.rs rename to crates/runner-backend/src/session/manager/tests.rs diff --git a/crates/runner-app/src/session/mod.rs b/crates/runner-backend/src/session/mod.rs similarity index 100% rename from crates/runner-app/src/session/mod.rs rename to crates/runner-backend/src/session/mod.rs diff --git a/crates/runner-app/src/session/pty_runtime.rs b/crates/runner-backend/src/session/pty_runtime.rs similarity index 100% rename from crates/runner-app/src/session/pty_runtime.rs rename to crates/runner-backend/src/session/pty_runtime.rs diff --git a/crates/runner-app/src/session/runtime.rs b/crates/runner-backend/src/session/runtime.rs similarity index 100% rename from crates/runner-app/src/session/runtime.rs rename to crates/runner-backend/src/session/runtime.rs diff --git a/crates/runner-app/src/shell_path.rs b/crates/runner-backend/src/shell_path.rs similarity index 100% rename from crates/runner-app/src/shell_path.rs rename to crates/runner-backend/src/shell_path.rs diff --git a/crates/runner-app/src/windows.rs b/crates/runner-backend/src/windows.rs similarity index 100% rename from crates/runner-app/src/windows.rs rename to crates/runner-backend/src/windows.rs diff --git a/crates/runner-native/Cargo.toml b/crates/runner-native/Cargo.toml deleted file mode 100644 index 7ee37f0..0000000 --- a/crates/runner-native/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "runner-native" -version = "0.3.17" -edition.workspace = true -authors.workspace = true -publish = false -default-run = "runner-native" - -[[bin]] -name = "runner-native" -path = "src/main.rs" - -[dependencies] -alacritty_terminal = "0.26" -anyhow = "1" -base64 = "0.22" -futures = "0.3" -gpui = { package = "gpui-ce", version = "0.3" } -runner-app = { path = "../runner-app" } -runner-terminal = { path = "../runner-terminal" } -serde.workspace = true -serde_json.workspace = true -tokio = { version = "1", features = ["sync"] } -unicode-segmentation = "1" - -[dev-dependencies] -tempfile = "3" diff --git a/crates/runner-native/src/lib.rs b/crates/runner-native/src/lib.rs deleted file mode 100644 index 1ba78fe..0000000 --- a/crates/runner-native/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod bootstrap; -pub mod pane_layout; -pub mod terminal_ime; -pub mod terminal_resize; -pub mod text_util; diff --git a/crates/runner-terminal/Cargo.toml b/crates/runner-terminal/Cargo.toml index d96a303..e7cb8a7 100644 --- a/crates/runner-terminal/Cargo.toml +++ b/crates/runner-terminal/Cargo.toml @@ -9,7 +9,7 @@ publish = false alacritty_terminal = "0.26" anyhow = "1" base64 = "0.22" -runner-app = { path = "../runner-app" } +runner-backend = { path = "../runner-backend" } serde.workspace = true serde_json.workspace = true tokio = { version = "1", features = ["sync"] } diff --git a/crates/runner-terminal/src/lib.rs b/crates/runner-terminal/src/lib.rs index 1c8e428..cafa11f 100644 --- a/crates/runner-terminal/src/lib.rs +++ b/crates/runner-terminal/src/lib.rs @@ -1,10 +1,10 @@ //! UI-agnostic terminal model for Runner's native app — the `terminal` //! half of the Zed-style `terminal` / `terminal_view` split (impl 0046 //! Workstream C). Owns the `alacritty_terminal` state, parsing, input -//! encoding, and the fixture corpus; rendering lives in `runner-native`. +//! encoding, and the fixture corpus; rendering lives in `runner-app`. //! //! Deliberate deviation from Zed: this crate does not own the PTY. -//! `runner_app`'s `SessionManager` spawns and manages sessions; this +//! `runner_backend`'s `SessionManager` spawns and manages sessions; this //! crate consumes its output events and produces renderable grid state //! plus encoded input bytes. diff --git a/crates/runner-terminal/src/terminal.rs b/crates/runner-terminal/src/terminal.rs index 3db9a90..3810903 100644 --- a/crates/runner-terminal/src/terminal.rs +++ b/crates/runner-terminal/src/terminal.rs @@ -13,9 +13,9 @@ use alacritty_terminal::vte::ansi::Processor; use anyhow::{Context as _, Result}; use base64::engine::general_purpose::STANDARD as B64; use base64::Engine as _; -use runner_app::events::AppEvent; -use runner_app::session::manager::OutputEvent; -use runner_app::AppCore; +use runner_backend::events::AppEvent; +use runner_backend::session::manager::OutputEvent; +use runner_backend::AppCore; use tokio::sync::broadcast::error::RecvError; use crate::palette; @@ -198,12 +198,12 @@ impl TerminalSession { Ok(()) } - pub fn submit_text(&self, text: &str) -> runner_app::error::Result<()> { + pub fn submit_text(&self, text: &str) -> runner_backend::error::Result<()> { self.write_user_bytes(text.as_bytes())?; self.write_user_bytes(b"\r") } - pub fn write_user_bytes(&self, bytes: &[u8]) -> runner_app::error::Result<()> { + pub fn write_user_bytes(&self, bytes: &[u8]) -> runner_backend::error::Result<()> { self.core .sessions .inject_direct_stdin(&self.session_id, bytes, &self.core.session_events()) @@ -215,7 +215,7 @@ impl TerminalSession { ctrl: bool, alt: bool, key_char: Option<&str>, - ) -> runner_app::error::Result { + ) -> runner_backend::error::Result { let app_cursor = self .term .lock_unfair() @@ -230,7 +230,7 @@ impl TerminalSession { } } - pub fn paste(&self, text: &str) -> runner_app::error::Result<()> { + pub fn paste(&self, text: &str) -> runner_backend::error::Result<()> { let bracketed = self .term .lock_unfair() @@ -249,7 +249,7 @@ impl TerminalSession { } *size = (cols, rows); } - let _ = runner_app::ops::session::session_resize(&self.core, &self.session_id, cols, rows); + let _ = runner_backend::ops::session::session_resize(&self.core, &self.session_id, cols, rows); self.term .lock() .resize(TermSize::new(cols as usize, rows as usize)); @@ -317,7 +317,7 @@ impl TerminalBridge { .lock() .unwrap() .insert(session_id.clone(), Arc::downgrade(&session)); - let snapshot = runner_app::ops::session::session_output_snapshot(&self.core, &session_id) + let snapshot = runner_backend::ops::session::session_output_snapshot(&self.core, &session_id) .context("load terminal output snapshot")?; session.feed_snapshot_locked(&snapshot)?; Ok(()) @@ -370,7 +370,7 @@ impl TerminalBridge { for session in sessions { let _feed = session.feed_gate.lock().unwrap(); if let Ok(snapshot) = - runner_app::ops::session::session_output_snapshot(&self.core, session.session_id()) + runner_backend::ops::session::session_output_snapshot(&self.core, session.session_id()) { let _ = session.feed_snapshot_locked(&snapshot); } @@ -388,34 +388,34 @@ mod tests { use base64::engine::general_purpose::STANDARD as B64; use base64::Engine as _; - use runner_app::session::manager::OutputEvent; + use runner_backend::session::manager::OutputEvent; use super::TerminalSession; use crate::replay::visible_lines; - use runner_app::AppCore; + use runner_backend::AppCore; /// Minimal `AppCore` over a temp dir — the pieces `boot_core` wires - /// in runner-native, minus login-shell discovery and startup cleanup. + /// in runner-app, minus login-shell discovery and startup cleanup. fn test_core(root: &std::path::Path) -> AppCore { let app_data_dir = root.join("app-data"); std::fs::create_dir_all(&app_data_dir).unwrap(); - let pool = Arc::new(runner_app::db::open_pool(&app_data_dir.join("runner.db")).unwrap()); - let runtime: Arc = - Arc::new(runner_app::session::pty_runtime::PtyRuntime::new()); - let windows = Arc::new(runner_app::windows::WindowRegistry::new()); + let pool = Arc::new(runner_backend::db::open_pool(&app_data_dir.join("runner.db")).unwrap()); + let runtime: Arc = + Arc::new(runner_backend::session::pty_runtime::PtyRuntime::new()); + let windows = Arc::new(runner_backend::windows::WindowRegistry::new()); windows.register("main"); AppCore { db: pool, app_data_dir, - sessions: runner_app::session::SessionManager::new( - runner_app::shell_path::LoginShellEnv::default(), + sessions: runner_backend::session::SessionManager::new( + runner_backend::shell_path::LoginShellEnv::default(), runtime, ), - buses: runner_app::event_bus::BusRegistry::new(), - routers: runner_app::router::RouterRegistry::new(), - mcp: Arc::new(runner_app::mcp::McpHandle::new()), + buses: runner_backend::event_bus::BusRegistry::new(), + routers: runner_backend::router::RouterRegistry::new(), + mcp: Arc::new(runner_backend::mcp::McpHandle::new()), windows, - events: runner_app::events::EventChannel::new(), + events: runner_backend::events::EventChannel::new(), app_version: "0.0.0-test".into(), } } diff --git a/docs/impls/gpui-rewrite/README.md b/docs/impls/gpui-rewrite/README.md index 50a59b6..420f805 100644 --- a/docs/impls/gpui-rewrite/README.md +++ b/docs/impls/gpui-rewrite/README.md @@ -10,4 +10,4 @@ Standing decisions (dated in the plan): - `main` owns product design and the domain model; repo-and-below stays verbatim-identical, migration numbers are allocated on `main` only (plan §Decisions). - Framework: `gpui-ce`; terminal architecture mirrors Zed's `terminal`/`terminal_view` split on upstream `alacritty_terminal` (plan §Framework, §Workstream C). Zed's terminal crates are GPL — architectural reference only. - Pulse (`~/repos/yicheng47/pulse`) is the window-level UI reference and the updater reference — Sparkle via the pulse pattern (plan §UI reference, §Phase 5). -- Crate renames after the M3 session-hardening slice (timing revised 2026-08-18): `runner-app` → `runner-backend`, `runner-native` → `runner-app` (plan §Decisions). +- Crate renames after the M3 session-hardening slice (landed 2026-08-18): `runner-app` → `runner-backend`, `runner-native` → `runner-app` (plan §Decisions). diff --git a/docs/impls/gpui-rewrite/impl_log.md b/docs/impls/gpui-rewrite/impl_log.md index 9cd71dc..f60dbdd 100644 --- a/docs/impls/gpui-rewrite/impl_log.md +++ b/docs/impls/gpui-rewrite/impl_log.md @@ -6,7 +6,7 @@ Progress record for the whole gpui-rewrite program ([README](README.md)), from t - **Branch**: `gpui-nightly` (M0+M1 merged via PR #407, M2 via PR #408, both 2026-08-17; M3.1 via PR #409, M3.2 via PR #410, M3.3 via PR #411, M3.4 via the `feat/0046-m3-input-latch` PR, all 2026-08-18). Session-hardening slice complete. - **Done**: 0046 M0 (gpui-ce swap), M1 (repo-and-below at `main` parity, node tree adopted, protocol crates wholesale; human-verified 2026-08-17), M2 (terminal split + shell modularization), M3.1–M3.4 (session-hardening slice: reaping, resume seams + geometry, resize storms + owner gating, input latch + quit stamping), terminal-pane IME (M4 pull-forward; Pinyin human-verified 2026-08-18), direct-chat composer removal (parity restore, smoke-tested 2026-08-18). -- **Next**: crate renames as one mechanical commit (plan decision 7: `runner-app` → `runner-backend`, `runner-native` → `runner-app`); then M3.5 (runtimes slice: discovery, Qoder/TRAE, model catalog, model/effort overrides, codex trust preseed) → start-chat modal + pane controls (M4 pull-forward: `main`'s `StartChatModal` + pane close button replacing the sidebar-swap flow; needs M3.5's backend) → remaining M3 slices → M4 (UI parity) → nightly channel (plan decision 10) → M5 (sweep + watermark). Parity references are `main`'s React frontend (`src/`) and the `design/*.pen` files via the pencil MCP. +- **Next**: M3.5 (runtimes slice: discovery, Qoder/TRAE, model catalog, model/effort overrides, codex trust preseed) → start-chat modal + pane controls (M4 pull-forward: `main`'s `StartChatModal` + pane close button replacing the sidebar-swap flow; needs M3.5's backend) → remaining M3 slices → M4 (UI parity) → nightly channel (plan decision 10) → M5 (sweep + watermark). Parity references are `main`'s React frontend (`src/`) and the `design/*.pen` files via the pencil MCP. - **Parity watermark**: `origin/main` fully ported for repo-and-below as of `1b7ee92` (v0.5.2 line, 2026-08-17); feature-logic lag is M3 scope. ## 2026-07-18 — Phase 1 kickoff @@ -181,3 +181,9 @@ M3's slices (0046 §Sequencing) run as serial codex-peer missions, one task at a - Deferred the router and frontend halves precisely: this branch's router predates `828c9fc`'s reservation/outbox/listener contract, so the latch is tracked but routing does not consume it until the router/inbox slice; pulling that contract now would drag the whole group. `InboxBlockedPill` and `MissionWorkspace` have no native mission surface yet and remain M4 scope; the pill design is the `InboxBlockedPill` component in `design/runner-mission.pen`. - Ported `c5b1ce4` at the native seam: every real quit path atomically calls `mark_running_for_resume_on_launch` before stopping the returned live sessions, including the final event-loop fallback, and stop failures are aggregated after every marked id is attempted. This activates `cb32720`'s feasible quit-side claim handling: an in-flight launch claim is requeued before teardown. Claim consumption/finalization remains deferred because native has no `take_resume_on_launch` queue, `SessionManager::resume_on_launch` path, setting, or launch UI; manual resume must not finish a launch claim. Until a consumer lands, quit stamps accumulate as an undrained `PENDING` backlog, so the launch-queue slice must explicitly drain and discard the pre-existing backlog on first boot or impose a cutoff before enabling auto-resume. - Gates: `make verify` green (workspace check, 440 `runner-app` tests, all native/helper tests, clippy `-D warnings`, fmt-check); the 10-test terminal fixture corpus stays green. The sandboxed run hit the expected temporary Unix-socket `EPERM`; the identical permitted run passed. + +## 2026-08-18 — Crate renames (decision 7) + +- One mechanical commit right after M3.4, per the revised decision 7: `crates/runner-app` → `crates/runner-backend` (package `runner-backend`, lib `runner_backend`), `crates/runner-native` → `crates/runner-app` (package/bin `runner-app`, pulse's `-app` convention). Imports, workspace members, Makefile `run` target, and normative doc references updated; historical log/plan narrative keeps the old names. +- From here the port mapping is `src-tauri/src/*` ↔ `crates/runner-backend/src/*`; **`runner-app` now names the binary** — every future mission goal states this to defuse the name-reuse hazard. +- Gates: `make verify` green. diff --git a/docs/impls/gpui-rewrite/plan.md b/docs/impls/gpui-rewrite/plan.md index a7e8a52..388d8be 100644 --- a/docs/impls/gpui-rewrite/plan.md +++ b/docs/impls/gpui-rewrite/plan.md @@ -16,8 +16,8 @@ One native Rust binary. No webview, no JS, no Tauri. The UI is rendered by GPUI Keep (~33k LOC Rust, extracted into UI-agnostic crates): -- `crates/runner-app/src/session/` — PTY session manager (`portable-pty`). -- `crates/runner-app/src/router/`, `event_bus/`, `repo/`, `db.rs` (SQLite), `mcp/`, `model.rs`, `error.rs`. +- `crates/runner-backend/src/session/` — PTY session manager (`portable-pty`). +- `crates/runner-backend/src/router/`, `event_bus/`, `repo/`, `db.rs` (SQLite), `mcp/`, `model.rs`, `error.rs`. - `crates/runner-core/`, `cli/` — untouched. - The SQLite schema and NDJSON event-log format — the native app reads the same data; you can switch between old and new app against the same state. @@ -44,7 +44,7 @@ Revised 2026-07-19 by Jason's decision, superseding the original fold-into-main- Revised again 2026-07-19 (repo split): the pioneer line briefly moved to its own **runner-gpui** repository. Revised again 2026-08-17 (single repo, current state): the line moved back into the `runner` repo as the long-lived `gpui-nightly` branch, pushed at `774aa35` with the standalone repo's full history; the separate GitHub repo is retired, and locally the branch lives in the `runner-gpui` worktree. Rationale: most of the tree differs between the lines anyway, and the shared part is ported by hand either way — one repo keeps issues, CI, and history together without cross-repo remote juggling. -- **Cross-branch flow**: backend changes on `main` are ported onto `gpui-nightly` by conscious cherry-pick, hand-relocated from `src-tauri/src/*` into `crates/runner-app/*` paths. Nothing flows automatically in either direction. Schema- and protocol-affecting changes (`db.rs` migrations, the `runner-core` event log, `cli` message shapes) port promptly — both apps must keep reading the same `runner.db` and event logs; feature logic ports when a parity slice needs it. +- **Cross-branch flow**: backend changes on `main` are ported onto `gpui-nightly` by conscious cherry-pick, hand-relocated from `src-tauri/src/*` into `crates/runner-backend/*` paths. Nothing flows automatically in either direction. Schema- and protocol-affecting changes (`db.rs` migrations, the `runner-core` event log, `cli` message shapes) port promptly — both apps must keep reading the same `runner.db` and event logs; feature logic ports when a parity slice needs it. - **Milestone work** happens on task branches off `gpui-nightly`, merged back only after human verification. - **Cutover (Phase 6)** is a branch promotion: `gpui-nightly`'s tree replaces `main` in this repo. @@ -55,12 +55,12 @@ Build: the pioneer line has Rust-only `fmt`, `clippy`, `test`, and `run` Make ta 2026-08-17 (Jason): 1. **`main` owns the design.** The whole product design follows `main`, including the node-tree sidebar (Feature 44). The GPUI line does not fork product decisions. -2. **Repo-and-below is identical.** `crates/runner-core`, `cli/`, `db.rs` + migrations, and `repo/` must match `main` line-for-line (modulo file paths: `src-tauri/src/*` ↔ `crates/runner-app/src/*`). Anything GPUI-specific lives in the adapter layer (`ops/`) or above — never in repo/db. Migration numbers are allocated on `main` only; this branch never adds its own. +2. **Repo-and-below is identical.** `crates/runner-core`, `cli/`, `db.rs` + migrations, and `repo/` must match `main` line-for-line (modulo file paths: `src-tauri/src/*` ↔ `crates/runner-backend/src/*`). Anything GPUI-specific lives in the adapter layer (`ops/`) or above — never in repo/db. Migration numbers are allocated on `main` only; this branch never adds its own. 3. **Product UI never changes; only the tech stack changes.** The GPUI app renders the same surfaces, flows, and copy as `main`'s current React app. Divergence is a bug, not a design opportunity. 4. **Terminal architecture mimics Zed's `terminal` / `terminal_view` crate split** (studied from the local checkout at `~/repos/gui/zed`, tree of 2026-08-17), but on **upstream `alacritty_terminal`** from crates.io — not Zed's fork. 5. **Framework: `gpui-ce`** (see §Framework). 6. **Updater (Phase 5): Sparkle via the pulse pattern** (see §Roadmap, Phase 5). -7. **Crate renames after the M3 session-hardening slice** (timing revised 2026-08-18; originally at Phase 6 cutover): `runner-app` → `runner-backend` (the UI-agnostic core the two frontends shared), `runner-native` → `runner-app` (the binary; pulse convention: `-app` = the application). Lands as one mechanical commit at the slice boundary right after M3.4 (Cargo package names, imports, Makefile/CI, doc references), gated on `make verify` — so M4's UI code, the bulk of new code in this program, is written under the final crate layout. From then on the port mapping is `src-tauri/src/*` ↔ `crates/runner-backend/src/*`, and every mission goal states it explicitly with a warning that `runner-app` now names the binary — the name reuse is the wrong-crate hazard that kept this at cutover originally. +7. **Crate renames after the M3 session-hardening slice** (timing revised 2026-08-18; originally at Phase 6 cutover): `runner-app` → `runner-backend` (the UI-agnostic core the two frontends shared), `runner-native` → `runner-app` (the binary; pulse convention: `-app` = the application). Landed 2026-08-18 as one mechanical commit right after M3.4 (Cargo package names, imports, Makefile, doc references), gated on `make verify` — so M4's UI code, the bulk of new code in this program, is written under the final crate layout. From then on the port mapping is `src-tauri/src/*` ↔ `crates/runner-backend/src/*`, and every mission goal states it explicitly with a warning that `runner-app` now names the binary — the name reuse is the wrong-crate hazard that kept this at cutover originally. 2026-08-18 (Jason): @@ -83,7 +83,7 @@ Build: the pioneer line has Rust-only `fmt`, `clippy`, `test`, and `run` Make ta ### A2. DB layer: migrations 0014–0020 + backfills, verbatim -Copy `main`'s `src-tauri/src/db.rs` and `src-tauri/migrations/` over `crates/runner-app/`'s, adjusting only the module path prefix. New content: `0014_nodes.sql` (nodes table + 1:1 row copy from folders/tabs), `0015_retire_folders.sql` (drops legacy tables), `0016_session_last_size`, `0017_session_resume_on_launch`, `0018_slot_model_override`, `0019_session_agent_options`, `0020_slot_effort_override`, plus the Rust backfill steps (`backfill_0014_nodes`, `backfill_0015_retire_folders`) and the peer-coding-crew seed (`d04ce3f`). +Copy `main`'s `src-tauri/src/db.rs` and `src-tauri/migrations/` over `crates/runner-backend/`'s, adjusting only the module path prefix. New content: `0014_nodes.sql` (nodes table + 1:1 row copy from folders/tabs), `0015_retire_folders.sql` (drops legacy tables), `0016_session_last_size`, `0017_session_resume_on_launch`, `0018_slot_model_override`, `0019_session_agent_options`, `0020_slot_effort_override`, plus the Rust backfill steps (`backfill_0014_nodes`, `backfill_0015_retire_folders`) and the peer-coding-crew seed (`d04ce3f`). ### A3. Repo layer: adopt `main`'s files, retire ours @@ -117,9 +117,9 @@ Mirror Zed's two-crate shape (reference: `~/repos/gui/zed/crates/terminal` and ` | `terminal/src/alacritty.rs` + `alacritty/hyperlinks.rs` | isolation of `alacritty_terminal` API surface: grid iteration, selection types, event glue | `runner-terminal/src/alacritty.rs` (deferred until the view side needs it) | | `terminal/src/mappings/{keys,mouse,colors}.rs` | keystroke→escape encoding, mouse reports, color conversion | `runner-terminal/src/mappings.rs` | | `terminal/src/pty_info.rs` | foreground-process info | **not ported** — process identity is `SessionManager`'s domain | -| `terminal_view/src/terminal_element.rs` | GPUI `Element`: layout → batched text runs/rects/cursor, paint, IME `InputHandler` | stays in `runner-native/src/terminal_element.rs` (keeps the per-cell-origin alignment strategy) | -| `terminal_view/src/terminal_view.rs` | focus, key dispatch, blink, context menu | `runner-native` chat/pane views | -| `terminal_view/src/{terminal_panel,persistence,scrollbar,path_like_target}.rs` | workspace integration | `runner-native` pane layout / future slices | +| `terminal_view/src/terminal_element.rs` | GPUI `Element`: layout → batched text runs/rects/cursor, paint, IME `InputHandler` | stays in `runner-app/src/terminal_element.rs` (keeps the per-cell-origin alignment strategy) | +| `terminal_view/src/terminal_view.rs` | focus, key dispatch, blink, context menu | `runner-app` chat/pane views | +| `terminal_view/src/{terminal_panel,persistence,scrollbar,path_like_target}.rs` | workspace integration | `runner-app` pane layout / future slices | **Deliberate deviations from Zed:** @@ -147,8 +147,8 @@ Each milestone is a task branch off `gpui-nightly`, human-verified before merge. 2. **M1 — repo-and-below parity** ✓ (2026-08-17; A1–A3 + minimal B): protocol crates wholesale; db + migrations + repo verbatim; `ops/` adapter updated; sidebar rewired onto nodes far enough to compile and function. Gate: migration test on a *copy* of a production `runner.db`, then the A/B switch test — Tauri `main` build and GPUI build alternately opening the same copy. 3. **M2 — terminal split** ✓ (2026-08-17; C): extract `runner-terminal`, consolidate input encoding into `mappings`, move the fixture corpus. No rendering change; corpus green. 4. **M3 — feature-parity slices** (in progress; A4 + B function, dogfood order): session hardening → runtimes/model-effort pickers → node sidebar polish + pinned section → mission feed (read-mostly + channel composer) → pagination/update checks/misc. Run as serial codex-peer missions; task numbering (M3.1, M3.2, …) pinned in the [program log](impl_log.md)'s 2026-08-18 breakdown entry. Each slice daily-driven before the next. -5. **M4 — UI parity** (new 2026-08-18): bring the native app to full product-UI parity with `main` — this restores the surface breadth of the original Phase 4 list that M3's backend-first slices deliberately defer. Entry task is a **surface inventory**: walk `main`'s `src/` (7 pages, ~30 components + `settings/` + `ui/`) and the `design/*.pen` files, classify every surface as present / unstyled / missing in `runner-native`, and pin the resulting task list in the program log (continuing the serial-mission numbering). Known scope: chat surface + composer styling; tab bar and layout picker; sidebar visual polish beyond M3's functional parity; mission workspace UI; runners/crews CRUD pages; settings; modals and dialogs; command palette; themes as data (Tokyo Night + light); window chrome per §UI reference (hidden titlebar, traffic lights, drag areas — pulse pattern); multi-window; and **terminal-pane IME** (marked-text composition and candidate-window positioning in the focused terminal, committed UTF-8 forwarded through `SessionManager`, raw handling preserved for control/navigation/function keys — a cutover blocker carried from Phase 3). Exit gate: side-by-side parity review against the Tauri app on every surface — same surface, same flow, same copy — and the native app is the daily driver for all workflows, not just chats. -6. **M5 — sweep + watermark** (was 0046's M4): diff `crates/runner-app` against `main`'s `src-tauri/src` module-by-module; the diff should be adapter-shaped only. Record the synced `main` SHA in this doc as the watermark; subsequent `main` backend commits port promptly (schema/protocol) or per-slice (features). +5. **M4 — UI parity** (new 2026-08-18): bring the native app to full product-UI parity with `main` — this restores the surface breadth of the original Phase 4 list that M3's backend-first slices deliberately defer. Entry task is a **surface inventory**: walk `main`'s `src/` (7 pages, ~30 components + `settings/` + `ui/`) and the `design/*.pen` files, classify every surface as present / unstyled / missing in `runner-app` (the binary crate), and pin the resulting task list in the program log (continuing the serial-mission numbering). Known scope: chat surface + composer styling; tab bar and layout picker; sidebar visual polish beyond M3's functional parity; mission workspace UI; runners/crews CRUD pages; settings; modals and dialogs; command palette; themes as data (Tokyo Night + light); window chrome per §UI reference (hidden titlebar, traffic lights, drag areas — pulse pattern); multi-window; and **terminal-pane IME** (marked-text composition and candidate-window positioning in the focused terminal, committed UTF-8 forwarded through `SessionManager`, raw handling preserved for control/navigation/function keys — a cutover blocker carried from Phase 3). Exit gate: side-by-side parity review against the Tauri app on every surface — same surface, same flow, same copy — and the native app is the daily driver for all workflows, not just chats. +6. **M5 — sweep + watermark** (was 0046's M4): diff `crates/runner-backend` against `main`'s `src-tauri/src` module-by-module; the diff should be adapter-shaped only. Record the synced `main` SHA in this doc as the watermark; subsequent `main` backend commits port promptly (schema/protocol) or per-slice (features). ### Phase 5 — App-shell services (replace what Tauri gave for free)