facett-claude — embedding the Claude pane (host guide)
Class:
guide· What it's for: the how-to for a host (nornir · dwarves · holger) that wants to mount ONE Claude chat surface as a tab/panel. The crate is a pure leaf — you own the transport; the pane owns the conversation UI + state.
1. Add the dependency
[dependencies]
facett-claude = { path = "../facett-claude", version = "0.1" }
No reqwest, no nornir, no secrets are pulled in — this crate does no HTTP.
2. Mount the pane
ClaudePane is a facett_core::Facet/Panel, so it drops into any facett tab host:
use facett_claude::ClaudePane;
let mut pane = ClaudePane::new("Claude"); // or ClaudePane::default()
// each frame, in your tab body:
facett_core::Panel::ui(&mut pane, ui);
The pane renders the turn list, the prompt input, the model selector, and Send.
3. Wire a backend (the one seam you own)
On Send the pane does NOT call the network — it hands you a ClaudeEffect::Send (data). You run it through a ClaudeBackend (alias of ClaudeTransport) and post the reply back:
use facett_claude::{ClaudeBackend, ClaudePane, EchoBackend};
// The built-in mock/echo default — offline, no ANTHROPIC_API_KEY needed:
let backend = EchoBackend;
for effect in pane.update(msg) { // msg came from the UI or a driver
let reply = ClaudePane::deliver(&effect, &backend); // ClaudeMsg::ReceivedOk/Err
pane.update(reply);
}
Swap EchoBackend for your real transport by implementing the trait:
struct HttpTransport { /* reqwest client, key, … */ }
impl ClaudeBackend for HttpTransport {
fn send(&self, model: &str, messages: &[facett_claude::WireMsg])
-> Result<String, String> {
// POST api.anthropic.com/v1/messages; return assistant text, or an
// error string like "set ANTHROPIC_API_KEY". Never panic.
# todo!()
}
}
The pane surfaces a returned Err(_) as an inline error line and clears sending — an offline/unset-key host degrades cleanly, never panics.
4. Drive it headless (robot / tests)
The whole conversation runs over the update_json/state_json seam — no pixels, no AI at runtime:
use facett_core::Panel;
Panel::update_json(&mut pane, r#"{"prompt_changed":"hello"}"#);
Panel::update_json(&mut pane, "\"send\"");
Panel::update_json(&mut pane, r#"{"received_ok":"hi human"}"#); // host posts the reply
assert_eq!(Panel::state_json(&pane)["last_assistant"], "hi human");
Malformed/unknown JSON is ignored, not a panic.
5. Model selector
facett_claude::MODELS lists the offered ids (default DEFAULT_MODEL = claude-opus-4-8 first). Selecting one emits {"model_selected":"…"}; the chosen id rides along in the Effect::Send so your backend calls the model the user picked.
Flow
See assets/claude-pane.svg — pane emits Effect::Send, host runs the backend, reply returns as ReceivedOk/ReceivedErr.