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 {
|
|
|
|
|
pty: PtySession,
|
|
|
|
|
}
|
|
|
|
|
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,
|
|
|
|
|
pty: SessionFamily<<PtySession as Session<FakePtyState>>::State>,
|
2026-05-31 08:58:08 -06:00
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
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`
|
|
|
|
|
- `flush_session_family`
|
|
|
|
|
- `flush_packet_queue_with_interface`
|
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
|
|
|
|
|
lookup, initialization, retry-safe flushing, and optional interface logging.
|
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
|
|
|
|
|
dispatched, sessions update, and outbound routes succeed or fail.
|
2026-05-31 08:58:08 -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-05-31 12:21:33 -06:00
|
|
|
Session rendering is an associated function because session families are type-level
|
|
|
|
|
contracts, not stored objects:
|
2026-05-31 08:58:08 -06:00
|
|
|
|
|
|
|
|
```rust
|
2026-05-31 12:21:33 -06:00
|
|
|
fn render_ratatui(
|
|
|
|
|
leaf: &LeafState,
|
|
|
|
|
session: &Self::State,
|
|
|
|
|
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.
|