Skip to content

M4 (1/3): app shell, onboarding, crews and bots - #5

Merged
httpsphl merged 5 commits into
mainfrom
feat/m4-app-shell
Sep 28, 2026
Merged

httpsphl merged 5 commits into
mainfrom
feat/m4-app-shell

Conversation

@httpsphl

Copy link
Copy Markdown
Owner

First of three slices of M4 (spec 17): the app connects to the daemon and manages crews and bots. The terminal (xterm.js) and the message timeline come next, in their own PRs.

What changes

  • Tauri shell (spec 15.2):
    • daemon_status checks /health on the port read from <BOTLOFT_HOME>\config.toml, the same folder the daemon uses. A dev daemon is found without extra setup.
    • daemon_start starts the botloftd.exe next to the app. It runs detached, without a console and outside the app's job, so it outlives the window. M5 replaces this with the scheduled task.
    • read_owner_token reads the owner token.
    • open_path opens folders only (Explorer would run a file).
  • Client (lib/rpc.ts, client.ts):
    • JSON-RPC over WebSocket, with session.hello first.
    • Requests are queued until the hello is accepted.
    • Errors come back as typed RpcError codes.
    • Lost connections reconnect with backoff (0.5 s, doubling to 5 s).
    • A request that was already sent when the socket drops fails; it is not replayed, because it may have run.
    • A refused hello stops for good.
  • BotloftApi: the contract components use. There are two implementations: the real client, and FakeBotloft, an in-memory daemon typed from the generated RpcMethods. Host does the same for the Tauri commands and the window.
  • Store: crews, bots and system.status load on every (re)connection, then follow the notifications. system.status has no notification, so it is re-read every 15 s.
  • Screens:
    • Onboarding: start the daemon, and see a specific error when the port is taken, the protocol differs or the hello is refused.
    • First-run checklist: daemon and Claude Code status.
    • Crews and bots in the sidebar, a crew overview with bot cards, and a bot view with pause, restart (fresh or not), edit, open folder and archive.
    • Daemon validation errors show inside the form.
    • A banner when bots cannot start (runtimeError).
    • A title-bar indicator when the connection drops.
  • Look (spec 15.3):
    • Custom title bar with Windows controls.
    • Light and dark themes that follow Windows.
    • IBM Plex type and avatars drawn from the mascot.
    • Bot state shows as color, icon and label.
  • Core: the avatar palette and field limits are exported to protocol.gen.ts, so the forms never restate them.
  • pnpm dev preview: opened in a plain browser, it runs the UI against FakeBotloft, for UI work without Tauri or the daemon. Release builds do not include it.

Spec changes

  • 7.4.1 (folder trust). Per the docs, trusting a parent folder does not cover a project's permissions.allow rules. The generated settings allow mcp__botloft, so every new bot shows the trust prompt once, and "trust the workspaces root in onboarding" would not help.
    • The app explains the prompt instead, and the owner answers it in the bot's terminal (next PR).
    • Botloft does not write ~/.claude.json, because Claude Code owns and rewrites that file.
    • This is recorded in section 19.
  • 15.1 / 15.2: Host, daemon_start, and how the app finds the port.
  • CLAUDE.md: the dev config.toml now lives inside the dev BOTLOFT_HOME, so the daemon and the app share it.

Tests

  • rpc.test.ts (fake socket):
    • hello first, and queued requests;
    • typed errors and notifications;
    • refused hello;
    • reconnection timing and states;
    • timeouts, and close.
  • store/app.test.ts:
    • loading and default selection;
    • bot.state, and archiving a bot or a whole crew;
    • reload after a reconnection that missed notifications.
  • link.test.ts: running, stopped then started, did not come up, protocol mismatch, missing token, taken port, refused hello.
  • App.test.tsx (FakeHost + FakeBotloft):
    • first run;
    • creating a crew and a bot;
    • a live state change;
    • a validation error kept in the form;
    • starting the daemon;
    • archiving with confirmation;
    • pausing a crew;
    • the missing Claude Code banner;
    • the reconnecting indicator.
  • Rust (daemon.rs): port from the config, a closed port, and a port held by something else.
  • Totals: 25 app tests and 140 Rust tests. pnpm check, pnpm build, clippy and fmt pass. Each commit typechecks and passes its tests on its own.

Checked against the real daemon

  • The app's real client.ts ran against a fresh dev daemon:
    • hello, a request queued before the hello, crews.create/list/archive, and a validation error as RpcError −32004;
    • both crew.changed notifications arrived;
    • a wrong token ended in failed with no retries.
  • A WebSocket from the Vite origin (http://localhost:1420, the dev webview) passed the Origin check and got the hello answer.
  • Every screen was looked at in the browser preview, in light and dark themes.

Manual test (needs the Tauri window)

$env:BOTLOFT_HOME = "$PWD\.dev\home"
cargo build -p botloftd
cd app; pnpm tauri dev
  1. With no daemon running, the window shows "Start the daemon".
  2. Clicking it starts target\debug\botloftd.exe and shows the first-run checklist.
  3. Create a crew and a bot. The bot shows "Starting" until its trust prompt is answered, which needs the terminal (next PR).
  4. Close the app window. The daemon and the bot keep running.
  5. The title bar drags the window, double-click maximizes, and the minimize, maximize and close buttons work.

The app's forms need the daemon's length limits and color palette; the
generated file now carries them so the UI never restates them.
Commands for what the page cannot do (spec 15.2): daemon_status checks
/health on the port from <home>\config.toml, daemon_start launches the
botloftd.exe next to the app detached so it outlives the window,
read_owner_token reads the owner token, and open_path opens folders only.

The window has no decorations (the app draws its own title bar), and the
CSP allows ws://127.0.0.1 on any port so a dev daemon with its own port
is reachable.
rpc.ts speaks spec 11 over WebSocket: session.hello first, requests queued
until it is accepted, answers matched by id, typed RpcError codes, and
reconnection with backoff (0.5 s doubling to 5 s). Requests already sent
when the socket drops fail instead of being replayed, since they may have
run. A refused hello (token or protocol) stops for good.

BotloftApi is the contract components use; client.ts implements it over
rpc.ts and FakeBotloft in memory, typed from the generated RpcMethods so
every method is covered. Host is the same idea for the Tauri commands
and the window.
- Onboarding: finds the daemon, offers to start it, reads the owner token
  and connects; a taken port, another protocol version or a refused hello
  each get their own message. The first run lists what is ready (daemon,
  Claude Code) and explains the one-time folder trust prompt per bot.
- Store: crews, bots and system.status load on every (re)connection, then
  follow crew.changed, bot.changed and bot.state; archived records leave.
- Screens: crews and their bots in the sidebar, a crew overview with bot
  cards, and a bot view with pause, restart (fresh or not), edit, open
  folder and archive. State reads as color, icon and label; paused bots
  read as paused. Daemon validation errors stay in the form.
- Own title bar with Windows controls, light and dark themes following
  Windows, IBM Plex type, bot avatars drawn from the mascot.
- `pnpm dev` in a plain browser runs the UI against FakeBotloft, for UI
  work without Tauri or the daemon. The preview is not in release builds.
The docs say a parent folder's trust does not cover a project's
permissions.allow rules, and the generated settings allow mcp__botloft,
so every new bot asks once; trusting the workspaces root would not help.
The app explains the prompt instead, and Botloft leaves ~/.claude.json
alone. Spec 15 gains daemon_start (M4, replaced by the service in M5) and
how the app finds the daemon's port. The dev config now lives inside the
dev BOTLOFT_HOME so the daemon and the app share it.
@httpsphl
httpsphl merged commit 644d167 into main Sep 28, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant