Skip to content

feat: add onboarding CLI and Leptos chat UI#5

Merged
jbold merged 4 commits into
mainfrom
002-onboard-chat-ui
Feb 8, 2026
Merged

feat: add onboarding CLI and Leptos chat UI#5
jbold merged 4 commits into
mainfrom
002-onboard-chat-ui

Conversation

@jbold
Copy link
Copy Markdown
Owner

@jbold jbold commented Feb 8, 2026

Summary

  • Onboarding CLI (exoclaw onboard): Guided first-time setup with secure API key storage. Keys stored in ~/.exoclaw/credentials/{provider}.key (mode 0600), never in config. Resolution chain: env var → credential file → None.
  • Leptos Chat UI: Browser-based chat at http://localhost:7200 with WebSocket streaming, markdown rendering, auth prompt for non-loopback binds, and connection status indicator. Built as WASM via trunk, embedded in server binary via rust-embed.
  • Cargo workspace: Converted to workspace with root server crate + ui/ frontend crate sharing Cargo.lock and target/.
  • 14 new integration tests for credential security, config preservation, env var precedence, and edge cases (134 total tests passing).

Changes

  • 35 files changed, +3,885 lines
  • New ui/ crate: Leptos 0.7 CSR app (app, state, ws, markdown, 5 components)
  • New src/secrets.rs: Credential file read/write with filesystem permissions
  • New tests/onboard_test.rs: 14 integration tests
  • Modified src/gateway/server.rs: rust-embed UI serving + no-API-key warning
  • Modified src/main.rs, src/config.rs: Onboarding CLI command
  • Full spec artifacts in specs/002-onboard-chat-ui/

Test plan

  • cargo test passes (134 tests)
  • cargo clippy clean (0 warnings excluding dead_code)
  • cargo fmt --check clean
  • cargo check -p exoclaw-ui --target wasm32-unknown-unknown passes
  • trunk build produces WASM bundle in ui/dist/
  • Manual: cargo run -- onboard → enter provider + key → verify credential file created with 0600 permissions
  • Manual: cargo run -- gateway → open browser to localhost:7200 → verify chat UI loads
  • Manual: Type message in chat → verify WebSocket connection and streaming response

🤖 Generated with Claude Code

Implement two user stories for the 002-onboard-chat-ui feature:

US1 - Onboarding: `exoclaw onboard` guides first-time setup with secure
API key storage (~/.exoclaw/credentials/{provider}.key, mode 0600).
Config file never contains the key — resolved at runtime from env var
or credential file. Re-onboard preserves unrelated config sections.

US2 - Chat UI: Leptos 0.7 CSR web interface served at / via rust-embed.
WebSocket client connects to /ws with JSON-RPC protocol. Markdown
rendering (pulldown-cmark), streaming token display, auto-scroll,
auth prompt for non-loopback, connection status indicator. Dark theme.

Infrastructure:
- Convert to Cargo workspace (root server + ui/ Leptos crate)
- trunk builds WASM bundle, rust-embed serves it from the binary
- 14 new onboard tests (permissions, resolution, preservation)
- 134 total tests passing, 0 clippy warnings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jbold
Copy link
Copy Markdown
Owner Author

jbold commented Feb 8, 2026

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

- Prevent XSS by converting raw HTML events to escaped text in
  pulldown-cmark renderer (ui/src/markdown.rs)
- Remove unused tower-http "fs" feature from Cargo.toml
- Fix CLAUDE.md: correct Leptos version (0.7, not 0.8), remove
  nonexistent leptos_axum reference, add secrets.rs and update
  main.rs/AppState module descriptions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jbold
Copy link
Copy Markdown
Owner Author

jbold commented Feb 8, 2026

Code review

Found 1 issue:

  1. is_connected flag is misleading -- WebSocket connection is dropped immediately after probe. In app.rs, the initial connection attempt discards the WsConnection via Ok(_) and sets is_connected = true, but the socket closes when the value is dropped. The same pattern appears in the reconnect handler in status.rs. The UI shows "Connected" when no persistent socket exists. Each message send in input.rs creates its own fresh connection, so the status indicator does not reflect actual connectivity.

ui/src/app.rs (initial connection probe drops the socket):

exoclaw/ui/src/app.rs

Lines 13 to 25 in 8e5c11e

// Attempt initial WebSocket connection
wasm_bindgen_futures::spawn_local(async move {
match crate::ws::connect(None).await {
Ok(_) => {
state.is_connected.set(true);
}
Err(e) => {
if e.contains("auth") {
state.needs_auth.set(true);
}
}
}
});

ui/src/components/status.rs (reconnect handler same pattern):

let on_click = move |_| {
if !state.is_connected.get() {
// Trigger reconnect by spawning connection attempt
let token = state.auth_token.get();
wasm_bindgen_futures::spawn_local(async move {
match crate::ws::connect(token).await {
Ok(_) => {
state.is_connected.set(true);
}
Err(e) => {
if e.contains("auth") {
state.needs_auth.set(true);
}
state.add_error(format!("Reconnect failed: {}", e));
}
}
});
}

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

jbold and others added 2 commits February 8, 2026 07:27
Update is_connected signal in message handler: set true on successful
connect, set false on connect failure or stream error. The initial
probe in app.rs sets the starting state; message sends keep it current.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Extract set_secure_dir_permissions() and set_secure_file_permissions()
  into shared src/fs_util.rs module to eliminate duplication between
  config.rs and secrets.rs
- Add home_dir() helper that returns Result instead of silently falling
  back to "." when HOME is unset
- Update specs/002-onboard-chat-ui/research.md to reflect actual
  implementation: Leptos 0.7 CSR + trunk + rust-embed (not 0.8 SSR
  with leptos_axum)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jbold jbold merged commit f86e51a into main Feb 8, 2026
1 check 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