Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

facett-claude/.nornir/guide.md

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.