crabidy/architecture/cbd-bundle.md

4.4 KiB

cbd: bundled server + client binary

Context and problem statement

crabidy-server and cbd-tui are separate binaries: the normal setup runs a long-lived server and attaches TUIs to it. The user wants a single binary cbd for the one-machine case: starting it starts the server and connects the TUI to it. Everything else works exactly the same — same config format, same gRPC wire, same features. (cbd reads its own cbd.toml, not cbd-tui.toml; see the resolved note under Risks.)

Assumptions (confirmed)

  • cbd-tui's config (cbd-tui.toml) already carries the server address; the server listens on a constant (0.0.0.0:50051).
  • Both mains are thin shells over module code: the server's main.rs holds the command/message enums and the startup sequence; the TUI's holds tracing setup and two loops (orchestrate, run_ui).
  • The gRPC boundary stays: the bundled TUI talks to the in-process server over localhost exactly like a remote one ("works the same completely"). No in-process transport special-casing.

Decisions

D1 — Both binaries become libraries with thin mains

  • crabidy-server: playback, provider, rpc move from bin modules to lib modules; the command/message enums and the startup sequence move into the lib (serve(addr) builds orchestrator, queue store, playback, rpc service and serves tonic on addr). main.rs keeps only stderr tracing setup + serve(LISTEN_ADDR).
  • cbd-tui: gains src/lib.rs exposing run(config) (the two loops and their channels); main.rs keeps file-based tracing setup + config init + run.
  • Behavior-preserving: no logic changes, only module moves and crabidy_server::crate:: path rewrites. All existing tests move along unchanged.

Not chosen: cbd spawning crabidy-server as a subprocess — that needs the second binary installed, which is exactly what "a single binary" is for.

D2 — cbd = start (or adopt) the server, then run the TUI

New tiny binary crate cbd:

  1. Tracing goes to the TUI's log file for both halves — the terminal belongs to the TUI, so the server's stderr logging would corrupt it.
  2. Spawn crabidy_server::serve(LISTEN_ADDR) on a background task. If the port is already taken (AddrInUse — a standalone server is running), log and carry on: the TUI simply connects to the existing server. Any other server error before readiness is fatal.
  3. Wait for readiness by polling a TCP connect against the configured server address (bounded retries with delay, then a clear error).
  4. Run the TUI exactly as cbd-tui would, with the same cbd-tui.toml.

Quitting the TUI ends the process — and with it the in-process server. That is inherent to bundling and fine: the current queue is persisted continuously, so the next start restores it (the ≤200 ms persist debounce window is the same loss window as killing the standalone server).

D3 — Out of scope (explicitly)

  • An in-process (channel) transport instead of localhost gRPC.
  • Daemonizing: cbd never outlives its TUI. Users who want a persistent server keep running crabidy-server.
  • CLI subcommands (cbd server, cbd attach, …) — later if wanted.

Structure

direction: right

cbd: "cbd (one binary)" {
  boot: "main: file tracing,\nspawn server, wait, run TUI"
  srv: "crabidy-server lib\nserve(addr)"
  tui: "cbd-tui lib\nrun(config)"
  boot -> srv: "tokio::spawn\n(AddrInUse → adopt)"
  boot -> tui: "after TCP readiness"
  tui -> srv: "localhost gRPC\n(unchanged wire)"
}

standalone: "crabidy-server bin\n(unchanged)" 
remote: "cbd-tui bin\n(unchanged)"
remote -> standalone: "gRPC (remote setup\nkeeps working)"

Risks and open questions

  • Separate client configs (resolved 2026-07-21): originally cbd and cbd-tui both read cbd-tui.toml, so pointing that file at a remote server (the standalone cbd-tui's job) also dragged cbd's own TUI to the remote while it started an unused local server. cbd now reads its own cbd.toml (same option set, defaulting to localhost — matching its in-process server), so the self-contained cbd and a remote-pointed cbd-tui coexist on one machine. See architecture/client-configs.md.
  • Two log producers, one file: server and TUI layers share the bundled tracing subscriber; targets distinguish them.
  • Open (future): a --no-server flag; graceful server shutdown (flush the persister) on TUI exit.