Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added crates/pulse_layout/.gitkeep
Empty file.
Empty file added crates/pulse_macros/.gitkeep
Empty file.
Empty file added crates/pulse_platform/.gitkeep
Empty file.
Empty file added crates/pulse_reactive/.gitkeep
Empty file.
Empty file added crates/pulse_render/.gitkeep
Empty file.
Empty file added crates/pulse_text/.gitkeep
Empty file.
Empty file added crates/pulse_ui_core/.gitkeep
Empty file.
Empty file added crates/pulse_web/.gitkeep
Empty file.
392 changes: 392 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,392 @@
# Pulse UI API Specification (Phase 1 — DEFINE)

This document defines the intended public API shape for Phase 2 test authoring and Phase 3 implementation.

## 1. Declarative Authoring Choice

Pulse UI chooses **Option A (preferred)**: a Rust macro DSL (`rsx!`-like) in `pulse_macros`.

- MVP MUST support macro-based declarative tree authoring.
- The architecture MUST allow adding a markup/build-step path later without breaking core runtime contracts.

## 2. Crate Surface Overview

```rust
use pulse_reactive::*;
use pulse_ui_core::*;
use pulse_layout::*;
use pulse_render::*;
use pulse_text::*;
use pulse_platform::*;
use pulse_web::*;
```

## 3. `pulse_reactive` API

### 3.1 Core Primitives

```rust
pub struct Signal<T> { /* opaque */ }
pub struct ReadSignal<T> { /* opaque */ }
pub struct WriteSignal<T> { /* opaque */ }

pub fn signal<T: Clone + 'static>(initial: T) -> (ReadSignal<T>, WriteSignal<T>);

impl<T: Clone + 'static> ReadSignal<T> {
pub fn get(&self) -> T;
pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R;
}

impl<T: Clone + 'static> WriteSignal<T> {
pub fn set(&self, value: T);
pub fn update(&self, f: impl FnOnce(&mut T));
}
```

### 3.2 Derived State and Effects

```rust
pub struct Memo<T> { /* opaque */ }
pub struct EffectHandle { /* opaque */ }

pub fn memo<T: Clone + 'static>(f: impl Fn() -> T + 'static) -> Memo<T>;
impl<T: Clone + 'static> Memo<T> {
pub fn get(&self) -> T;
}

pub fn effect(f: impl FnMut() + 'static) -> EffectHandle;
```

### 3.3 Batching and Scheduler

```rust
pub fn batch(f: impl FnOnce());

pub trait Scheduler: Send + Sync {
fn schedule_microtask(&self, task: Box<dyn FnOnce() + Send>);
fn schedule_animation_frame(&self, task: Box<dyn FnOnce(f64) + Send>);
}

pub fn set_scheduler(scheduler: Box<dyn Scheduler>);
```

### 3.4 Debug Tracing

```rust
#[derive(Debug, Clone)]
pub enum ReactiveTraceEvent {
SignalWrite { id: u64 },
MemoRecompute { id: u64 },
EffectRun { id: u64 },
}

pub fn set_trace_enabled(enabled: bool);
pub fn take_trace_events() -> Vec<ReactiveTraceEvent>;
```

## 4. `pulse_ui_core` API

### 4.1 IDs and Tree

```rust
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct NodeId(pub u64);

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FocusId(pub u64);

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct EventHandlerId(pub u64);

pub struct UiTree { /* opaque */ }

impl UiTree {
pub fn new() -> Self;
pub fn root(&self) -> NodeId;
pub fn append_child(&mut self, parent: NodeId, spec: NodeSpec) -> NodeId;
pub fn remove_node(&mut self, node: NodeId);

pub fn set_prop(&mut self, node: NodeId, prop: UiProp);
pub fn invalidate_node(&mut self, node: NodeId, reason: InvalidationReason);

pub fn render_commands(&self) -> Vec<RenderCommand>;
}
```

### 4.2 Node and Prop Model

```rust
pub enum NodeSpec {
Container,
Text { content: String },
Image { image_id: ImageId },
Custom { type_name: &'static str },
}

pub enum UiProp {
Width(f32),
Height(f32),
MinWidth(f32),
MinHeight(f32),
MaxWidth(f32),
MaxHeight(f32),
Padding(f32),
Background(Color),
Text(String),
Focusable(bool),
ThemeToken(ThemeToken),
}

pub enum InvalidationReason {
Layout,
Paint,
Text,
HitTest,
Focus,
}
```

### 4.3 Event Model

```rust
pub enum EventPhase { Capture, Target, Bubble }

pub struct UiEvent {
pub event_type: UiEventType,
pub target: NodeId,
pub phase: EventPhase,
pub timestamp_ms: f64,
}

pub enum UiEventType {
PointerDown { x: f32, y: f32, button: u8 },
PointerMove { x: f32, y: f32 },
PointerUp { x: f32, y: f32, button: u8 },
TouchStart { id: u64, x: f32, y: f32 },
TouchMove { id: u64, x: f32, y: f32 },
TouchEnd { id: u64, x: f32, y: f32 },
KeyDown { key: KeyCode },
KeyUp { key: KeyCode },
TextInput { text: String },
}

pub enum EventDisposition {
Continue,
StopPropagation,
}

pub trait EventHandler {
fn handle_event(&mut self, event: &UiEvent) -> EventDisposition;
}
```

### 4.4 Focus and Scroll State

```rust
pub struct FocusState {
pub focused: Option<FocusId>,
}

pub struct ScrollState {
pub x: f32,
pub y: f32,
}

impl UiTree {
pub fn set_focus(&mut self, focus: Option<FocusId>);
pub fn focus_next(&mut self);
pub fn focus_prev(&mut self);
pub fn focus_state(&self) -> &FocusState;
}
```

### 4.5 Theming

```rust
pub enum ThemeToken {
ColorPrimary,
ColorBackground,
SpacingSm,
SpacingMd,
FontBody,
FontHeading,
}

pub struct Theme {
pub colors: ThemeColors,
pub spacing: ThemeSpacing,
pub typography: ThemeTypography,
}
```

## 5. `pulse_layout` API

```rust
pub struct LayoutEngine { /* opaque */ }

pub struct LayoutConstraints {
pub min_width: f32,
pub max_width: f32,
pub min_height: f32,
pub max_height: f32,
}

pub struct LayoutResult {
pub rects: Vec<(NodeId, Rect)>,
}

impl LayoutEngine {
pub fn new() -> Self;
pub fn compute(&mut self, tree: &UiTree, constraints: LayoutConstraints) -> LayoutResult;
}

#[cfg(feature = "taffy")]
pub struct TaffyAdapter;
```

## 6. `pulse_render` API

```rust
pub struct Renderer { /* opaque */ }

pub struct RenderTargetDescriptor {
pub width: u32,
pub height: u32,
pub scale_factor: f32,
}

#[derive(Debug, Clone)]
pub enum RenderCommand {
DrawRect { rect: Rect, color: Color },
DrawRoundedRect { rect: Rect, radius: f32, color: Color },
DrawText { rect: Rect, text: String, style: TextStyle },
DrawImage { rect: Rect, image_id: ImageId },
PushClip { rect: Rect },
PopClip,
PushTransform { transform: Transform2D },
PopTransform,
}

impl Renderer {
pub async fn new_wgpu(target: RenderTargetDescriptor) -> Result<Self, RenderError>;
pub fn render(&mut self, commands: &[RenderCommand]) -> Result<(), RenderError>;
}

pub struct HeadlessRenderer { /* opaque */ }

impl HeadlessRenderer {
pub fn new(width: u32, height: u32) -> Self;
pub fn render_to_rgba(&mut self, commands: &[RenderCommand]) -> Vec<u8>;
pub fn render_hash(&mut self, commands: &[RenderCommand]) -> [u8; 32];
}
```

## 7. `pulse_text` API

```rust
pub struct TextSystem { /* opaque */ }

pub struct TextLayoutRequest<'a> {
pub text: &'a str,
pub max_width: f32,
pub style: TextStyle,
}

pub struct GlyphRun { /* opaque */ }

impl TextSystem {
pub fn new() -> Self;
pub fn shape(&mut self, req: TextLayoutRequest<'_>) -> Vec<GlyphRun>;
}
```

## 8. `pulse_platform` API

```rust
pub struct AppConfig {
pub title: String,
pub width: u32,
pub height: u32,
}

pub trait AppDelegate {
fn init(&mut self, cx: &mut AppContext);
fn event(&mut self, cx: &mut AppContext, event: PlatformEvent);
fn frame(&mut self, cx: &mut AppContext, frame_time_ms: f64);
}

pub fn run_native(config: AppConfig, app: impl AppDelegate + 'static) -> Result<(), PlatformError>;
```

## 9. `pulse_web` API

```rust
pub struct WebKernelConfig {
pub canvas_id: String,
pub feature_base_url: String,
}

pub async fn start_kernel(config: WebKernelConfig) -> Result<(), WebError>;

pub trait FeatureModule {
fn init_feature(request_bytes: &[u8]) -> Vec<u8>;
fn handle_event(request_bytes: &[u8]) -> Vec<u8>;
}
```

### 9.1 Versioned Message Envelopes

```rust
#[derive(Serialize, Deserialize)]
pub enum KernelFeatureMessage {
MsgV1(ModuleEnvelopeV1),
MsgV2(ModuleEnvelopeV2),
}

#[derive(Serialize, Deserialize)]
pub struct ModuleEnvelopeV1 {
pub message_type: String,
pub payload: Vec<u8>,
}
```

## 10. `pulse_macros` API

```rust
let view = rsx! {
column(width: 320.0, height: 200.0) {
text("Hello")
button(on_click = move |_| set_count.update(|n| *n += 1)) {
text(format!("count: {}", count.get()))
}
}
};
```

Macro contract:

- MUST emit stable node identity bindings.
- MUST wire reactive property dependencies for fine-grained invalidation.
- SHOULD support component composition and theming ergonomics.

## 11. Resumability API (`feature = "resumable"`)

```rust
#[cfg(feature = "resumable")]
pub trait SnapshotStore {
fn snapshot(&self, options: SnapshotOptions) -> Vec<u8>;
fn restore(&mut self, bytes: &[u8]) -> Result<(), SnapshotError>;
}

#[cfg(feature = "resumable")]
pub struct SnapshotOptions {
pub include_focus: bool,
pub include_scroll: bool,
pub include_reactive_values: bool,
}
```

## 12. API Stability Notes

- This API document is DEFINE-phase and SHOULD be considered provisional.
- Phase 2 tests MUST pin behavior contracts before Phase 3 implementation finalizes signatures.
Loading