Files
unshell/LEAF_MACRO_INTERFACE.md
T

187 lines
4.9 KiB
Markdown
Raw Normal View History

2026-05-31 12:21:33 -06:00
# Template Leaf Interface Design
2026-05-31 08:58:08 -06:00
2026-05-31 12:21:33 -06:00
**Status:** Implemented draft
**Last updated:** 2026-05-31
**Primary use case:** Small generated leaf wrappers without proc-macro machinery
2026-05-31 08:58:08 -06:00
## Summary
2026-05-31 12:21:33 -06:00
Leaf generation now uses a declarative `unshell_leaf!` template instead of the old
`#[unshell_leaf]` proc macro. The goal is to make generated code obvious, closer to
an HTML template than an AST transformation.
2026-05-31 08:58:08 -06:00
2026-05-31 12:21:33 -06:00
The macro only fills slots:
2026-05-31 08:58:08 -06:00
2026-05-31 12:21:33 -06:00
- wrapper name
- user state type
- leaf id
- interface metadata
- named session families
- named procedure families
2026-05-31 08:58:08 -06:00
2026-05-31 12:21:33 -06:00
All real behavior lives in normal Rust helpers under `src/protocol/runtime.rs`.
Those helpers are testable without macro parsing, `syn`, `quote`, or generated name
inference.
2026-05-31 08:58:08 -06:00
2026-05-31 12:21:33 -06:00
## User Shape
2026-05-31 08:58:08 -06:00
```rust
2026-05-31 12:21:33 -06:00
pub struct FakePtyState {
pub active_count: usize,
pub total_opened: u64,
2026-05-31 08:58:08 -06:00
}
2026-05-31 12:21:33 -06:00
unshell_leaf! {
pub leaf FakePtyLeaf for FakePtyState {
id: LEAF_FAKE_PTY,
meta: unshell::protocol::LeafMeta {
name: "Fake PTY Leaf",
identifier: "dev.unshell.v1.pty",
version: "v0",
authors: unshell::alloc::vec!["ASTATIN3"],
},
sessions {
2026-06-01 11:30:25 -06:00
pty: PtySessionState,
2026-05-31 12:21:33 -06:00
}
procedures {}
2026-05-31 08:58:08 -06:00
}
}
```
2026-05-31 12:21:33 -06:00
The field name before each session type is explicit. The macro does not invent a
field name from the Rust type.
2026-05-31 08:58:08 -06:00
2026-05-31 12:21:33 -06:00
## Generated Shape
2026-05-31 08:58:08 -06:00
2026-05-31 12:21:33 -06:00
The example above expands to the equivalent of:
2026-05-31 08:58:08 -06:00
```rust
2026-05-31 12:21:33 -06:00
pub struct FakePtyLeaf {
state: FakePtyState,
outbox: LeafOutbox,
2026-06-01 11:30:25 -06:00
pty: SessionFamily<PtySessionState>,
2026-05-31 08:58:08 -06:00
}
```
2026-06-01 11:30:25 -06:00
Session types are the per-hook state values themselves. There is no separate
zero-sized handler struct; a type like `PtySessionState` implements `Session` and is
stored directly in the generated `SessionFamily`.
2026-05-31 12:21:33 -06:00
The wrapper implements:
2026-05-31 08:58:08 -06:00
2026-05-31 12:21:33 -06:00
- `new(state)`
- `state()`
- `state_mut()`
- `active_session_count()`
- `pending_packet_count()`
- `Leaf::get_id()`
- `Leaf::update()`
- feature-gated `Leaf::update_interface()`
- feature-gated `Leaf::get_meta()`
- feature-gated `Leaf::render_ratatui()`
2026-05-31 08:58:08 -06:00
2026-05-31 12:21:33 -06:00
## Runtime Helpers
2026-05-31 08:58:08 -06:00
2026-05-31 12:21:33 -06:00
The macro delegates behavior to small helpers:
2026-05-31 08:58:08 -06:00
2026-05-31 12:21:33 -06:00
- `dispatch_session`
- `update_session_family`
- `dispatch_procedure`
- `flush_leaf_outbox`
2026-05-31 08:58:08 -06:00
2026-05-31 12:21:33 -06:00
This keeps the macro readable. The helper functions own the mechanics of session
2026-06-01 12:11:01 -06:00
lookup, initialization, procedure response flushing, and optional interface logging.
Sessions route their own output immediately through `Endpoint` helpers to avoid a
per-session output context and retry queue in small implant builds.
2026-05-31 08:58:08 -06:00
2026-05-31 12:21:33 -06:00
## Interface Store
2026-05-31 08:58:08 -06:00
2026-05-31 12:21:33 -06:00
`InterfaceStore` is caller-owned. It records packet flow and timing without putting
UI state inside `Endpoint` or the leaf wrapper.
2026-05-31 08:58:08 -06:00
```text
2026-05-31 12:21:33 -06:00
InterfaceStore
events: Vec<InterfaceEvent>
sessions: BTreeMap<SessionKey, SessionView>
procedures: BTreeMap<ProcedureKey, ProcedureView>
2026-05-31 08:58:08 -06:00
```
2026-05-31 12:21:33 -06:00
Generated leaves receive an optional mutable store during `update_interface`. The
helpers create and update the appropriate session/procedure views when packets are
2026-06-01 12:11:01 -06:00
dispatched, sessions update, and queued procedure outbound routes succeed or fail.
2026-05-31 08:58:08 -06:00
2026-06-01 09:54:37 -06:00
Internally, interface events are target-driven:
```text
generated runtime
knows packet owner
|
v
InterfaceTarget::Session(SessionKey)
InterfaceTarget::Procedure(ProcedureKey)
|
v
InterfaceStore::record(...)
append InterfaceEvent
link event index to exactly one view
update SessionViewStatus when applicable
```
This is deliberately not inferred from `Packet`. A PTY session packet and a one-shot
procedure packet both have `procedure_id` and `hook_id`, but they should not both
create session views. The runtime already knows which dispatch branch handled the
packet, so that answer is carried into the store.
2026-06-01 12:11:01 -06:00
Leaf-level retry queues carry the same owner metadata for procedure responses.
Session responses bypass this queue and use `Endpoint::send_hook_raw` or
`Endpoint::send_hook_frame` directly.
2026-06-01 09:54:37 -06:00
2026-05-31 12:21:33 -06:00
Time remains caller-supplied:
2026-05-31 08:58:08 -06:00
```rust
2026-05-31 12:21:33 -06:00
interface.set_now_ns(Some(now_ns));
leaf.update_interface(endpoint, &mut interface);
2026-05-31 08:58:08 -06:00
```
2026-05-31 12:21:33 -06:00
No clock is embedded in the no_std protocol layer.
2026-05-31 08:58:08 -06:00
2026-05-31 12:21:33 -06:00
## Ratatui Rendering
2026-05-31 08:58:08 -06:00
2026-05-31 12:21:33 -06:00
Ratatui rendering is a plain feature-gated pass:
2026-05-31 08:58:08 -06:00
```rust
2026-05-31 12:21:33 -06:00
leaf.render_ratatui(frame, area, &mut interface);
2026-05-31 08:58:08 -06:00
```
2026-06-01 11:30:25 -06:00
Session rendering is an associated function on the stored session state type:
2026-05-31 08:58:08 -06:00
```rust
2026-05-31 12:21:33 -06:00
fn render_ratatui(
leaf: &LeafState,
2026-06-01 11:30:25 -06:00
session: &Self,
2026-05-31 12:21:33 -06:00
view: &mut SessionView,
frame: &mut ratatui::Frame<'_>,
area: ratatui::layout::Rect,
) {
2026-05-31 08:58:08 -06:00
}
```
2026-05-31 12:21:33 -06:00
Procedure rendering is also associated and renders from leaf state plus the caller
owned procedure view.
2026-05-31 08:58:08 -06:00
2026-05-31 12:21:33 -06:00
## Why This Replaced The Proc Macro
2026-05-31 08:58:08 -06:00
2026-05-31 12:21:33 -06:00
The old proc macro had to parse attributes, infer names, generate many code paths,
and duplicate runtime logic inside codegen. That made the generator harder to reason
about than the leaf behavior it was trying to simplify.
2026-05-31 08:58:08 -06:00
2026-05-31 12:21:33 -06:00
The new design is intentionally boring:
2026-05-31 08:58:08 -06:00
```text
2026-05-31 12:21:33 -06:00
macro template -> named fields and loops
runtime helpers -> behavior
caller InterfaceStore -> UI/log state
2026-05-31 08:58:08 -06:00
```
2026-05-31 12:21:33 -06:00
That is the whole game.