C+
Packages · View as Markdown

inspector

A developer-only live inspector for a mounted facet tree: read and write any node's properties, add and remove nodes, in a running application.

It walks facet's own tree, not the platform's view hierarchy, so it sees pure-layout nodes, spans and menu items that have no native view at all — and one walker serves every backend.

This is a developer surface, distinct from the curated, permissioned UI verbs in agent_core. It sees unexposed nodes and writes properties that are not user affordances. facet_agent can publish these inspector verbs over the same MCP server, guarded by its edit_tree capability.

[dependencies]
inspector = "*"
facet     = "*"
stdlib    = "*"

Use cpc pm add . inspector to write the full platform-specific closure.

Embed the panel

import "inspector/widget" as panel;
import "facet/component" as component;
import "facet/facet" as core;
import "facet/elements" as ui;

struct App { panel: panel::Inspector, }

impl App: component::Component {
    fn build(ref this) -> core::Node {
        var row: core::Builder = core::Builder::new();
        row.add(my_ui());
        row.add(this.panel.build().width(620.0f64));
        return ui::row(row, key: "window").grow(1.0f64);
    }
}

impl App: component::Lifecycle {
    // A nested component's hooks are the host's to run.
    fn on_attach(ref this, why: component::Attach) {
        if why == component::Attach::Mount { panel::attach(#addr_of(this.panel)); }
    }
    fn on_detach(ref this, why: component::Detach) {
        if why == component::Detach::Unmount { panel::detach(#addr_of(this.panel)); }
    }
}

The platform half — the highlight overlay, the native rows, the UI-thread hop — is installed by whoever serves the agent surface (facet_agent), so an embedded panel has nothing to install.

Or attached to another process

connect takes inapp, a socket path, a loopback port, or http://host:port/ — the scheme picks the transport — and discover answers where a named app is listening.

import "stdlib/status" as status;
import "stdlib/text" as text;

async fn attach_remote(st: *panel::Inspector) -> status::Status {
    return await panel::connect(
        st, text::from_str("http://127.0.0.1:9123/"));
}

panel::discover("myapp") returns live addresses for a named app, newest first; each returned Text can be moved directly into connect.

The fourteen verbs are on every agent surface: an app that calls runtime::agent_mcp(id) is inspectable, with no second call. See docs/wire.md.

Modules

This package is the panel. The verbs, the walker and the platform halves live where their layer does, which is what lets agent_mcp serve the verbs without depending on a toolkit:

Module
inspector/widget the embeddable panel, and the host API — written against the vtable
inspector/remote the same vtable over a socket, a port, or HTTP
Elsewhere
agent_core/inspect the neutral surface — Handle, Value, Spec, Prop, Outcome, Backend, the property vocabulary. Names no toolkit, which is why the verbs could move
agent_mcp/inspect the fourteen verbs, published by agent_mcp itself when it starts serving
facet_agent/inspect_tree the facet-tree walker, the typed dispatch, the structural verbs
facet_agent/inspect_platform highlight overlay, native rows, the UI-thread hop — resolved for macOS, Linux, Windows, iOS, and Android

Docs

  • docs/tutorial.md — get a panel on screen, edit a property.
  • docs/guide.md — the three tiers, handles and staleness, the refusals, and the gotchas.
  • docs/ref.md — signatures, including the host API a containing application drives the panel with.
  • docs/wire.md — the JSON-RPC verbs on the wire.
  • docs/design.md — why it is shaped this way.

Tests

Unit, e2e and negative tests live in src/test_main.cplus; the facet tree is facet's own state, so the walker, the dispatch, the ledger, the structural verbs and every refusal are exercisable headlessly.

cd vendor/inspector && cpc test

examples/inspector_probe is the manual-test app for the things a test cannot assert — that an edit feels live.