From 5e5aa914089cbd8da95d03d379c865adabaca460 Mon Sep 17 00:00:00 2001 From: Michael Grilo <4789416+michaelgrilo@users.noreply.github.com> Date: Mon, 9 Feb 2026 22:27:45 -0500 Subject: [PATCH 1/3] docs: define phase-1 architecture and API for pulse UI workspace --- crates/pulse_layout/.gitkeep | 0 crates/pulse_macros/.gitkeep | 0 crates/pulse_platform/.gitkeep | 0 crates/pulse_reactive/.gitkeep | 0 crates/pulse_render/.gitkeep | 0 crates/pulse_text/.gitkeep | 0 crates/pulse_ui_core/.gitkeep | 0 crates/pulse_web/.gitkeep | 0 docs/API.md | 351 +++++++++++++++++++++++++++++ docs/Architecture.md | 270 ++++++++++++++++++++++ docs/MVP.md | 130 +++++++++++ examples/counter/.gitkeep | 0 examples/gallery/.gitkeep | 0 examples/todo/.gitkeep | 0 examples/web_feature_demo/.gitkeep | 0 examples/web_kernel/.gitkeep | 0 16 files changed, 751 insertions(+) create mode 100644 crates/pulse_layout/.gitkeep create mode 100644 crates/pulse_macros/.gitkeep create mode 100644 crates/pulse_platform/.gitkeep create mode 100644 crates/pulse_reactive/.gitkeep create mode 100644 crates/pulse_render/.gitkeep create mode 100644 crates/pulse_text/.gitkeep create mode 100644 crates/pulse_ui_core/.gitkeep create mode 100644 crates/pulse_web/.gitkeep create mode 100644 docs/API.md create mode 100644 docs/Architecture.md create mode 100644 docs/MVP.md create mode 100644 examples/counter/.gitkeep create mode 100644 examples/gallery/.gitkeep create mode 100644 examples/todo/.gitkeep create mode 100644 examples/web_feature_demo/.gitkeep create mode 100644 examples/web_kernel/.gitkeep diff --git a/crates/pulse_layout/.gitkeep b/crates/pulse_layout/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/crates/pulse_macros/.gitkeep b/crates/pulse_macros/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/crates/pulse_platform/.gitkeep b/crates/pulse_platform/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/crates/pulse_reactive/.gitkeep b/crates/pulse_reactive/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/crates/pulse_render/.gitkeep b/crates/pulse_render/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/crates/pulse_text/.gitkeep b/crates/pulse_text/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/crates/pulse_ui_core/.gitkeep b/crates/pulse_ui_core/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/crates/pulse_web/.gitkeep b/crates/pulse_web/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 0000000..18b3fa7 --- /dev/null +++ b/docs/API.md @@ -0,0 +1,351 @@ +# Pulse UI Public API Draft (Phase 1 — DEFINE) + +This document defines the intended public API surface for MVP planning. Signatures may evolve minimally in implementation phases. + +## 1) Top-level crate usage + +```rust +use pulse_reactive::*; +use pulse_ui_core::*; +use pulse_layout::*; +use pulse_render::*; +``` + +## 2) `pulse_reactive` API + +### Core primitives + +```rust +pub struct Signal { /* opaque */ } +pub struct ReadSignal { /* opaque */ } +pub struct WriteSignal { /* opaque */ } + +pub fn signal(initial: T) -> (ReadSignal, WriteSignal); + +impl ReadSignal { + pub fn get(&self) -> T; + pub fn with(&self, f: impl FnOnce(&T) -> R) -> R; +} + +impl WriteSignal { + pub fn set(&self, value: T); + pub fn update(&self, f: impl FnOnce(&mut T)); +} +``` + +### Memos and effects + +```rust +pub struct Memo { /* opaque */ } +pub struct EffectHandle { /* opaque */ } + +pub fn memo(f: impl Fn() -> T + 'static) -> Memo; +impl Memo { + pub fn get(&self) -> T; +} + +pub fn effect(f: impl FnMut() + 'static) -> EffectHandle; +``` + +### Batching and scheduler + +```rust +pub fn batch(f: impl FnOnce()); + +pub trait Scheduler { + fn schedule_microtask(&self, task: Box); + fn schedule_animation_frame(&self, task: Box); +} + +pub fn set_scheduler(scheduler: Box); +``` + +### 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; +``` + +## 3) `pulse_ui_core` API + +### Node and tree model + +```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); + +pub struct UiTree { /* opaque */ } + +impl UiTree { + pub fn new() -> Self; + pub fn root(&self) -> NodeId; + pub fn append_child(&mut self, parent: NodeId, child: NodeSpec) -> 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; +} +``` + +### Node spec, properties, and invalidation + +```rust +pub enum NodeSpec { + Container, + Text { content: String }, + Custom { type_name: &'static str }, +} + +pub enum UiProp { + Width(f32), + Height(f32), + Padding(f32), + Background(Color), + Text(String), + Focusable(bool), +} + +pub enum InvalidationReason { + Layout, + Paint, + Text, + HitTest, + Focus, +} +``` + +### Events and routing + +```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 }, + KeyDown { key: KeyCode }, + KeyUp { key: KeyCode }, + TextInput { text: String }, +} + +pub trait EventHandler { + fn handle_event(&mut self, event: &UiEvent) -> EventDisposition; +} + +pub enum EventDisposition { + Continue, + StopPropagation, +} +``` + +### Focus and state + +```rust +pub struct FocusState { + pub focused: Option, +} + +impl UiTree { + pub fn focus_next(&mut self); + pub fn focus_prev(&mut self); + pub fn set_focus(&mut self, focus: Option); + pub fn focus_state(&self) -> &FocusState; +} +``` + +## 4) `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; +} +``` + +Feature-gated adapter: + +```rust +#[cfg(feature = "taffy")] +pub struct TaffyAdapter; +``` + +## 5) `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; + 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; + pub fn render_hash(&mut self, commands: &[RenderCommand]) -> [u8; 32]; +} +``` + +## 6) `pulse_text` API (staged) + +```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; +} +``` + +## 7) `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>; +``` + +## 8) `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; + fn handle_event(request_bytes: &[u8]) -> Vec; +} +``` + +Message envelope: + +```rust +#[derive(Serialize, Deserialize)] +pub struct ModuleEnvelope { + pub version: u16, + pub message_type: String, + pub payload: Vec, +} +``` + +## 9) `pulse_macros` API (preferred declarative layer) + +```rust +// Example usage target +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 output contract: + +- Generates stable node keys and component constructors. +- Emits bindings to reactive sources with property-level invalidation. + +## 10) Resumability API (feature = "resumable") + +```rust +#[cfg(feature = "resumable")] +pub trait SnapshotStore { + fn snapshot(&self) -> Vec; + fn restore(&mut self, bytes: &[u8]) -> Result<(), SnapshotError>; +} + +#[cfg(feature = "resumable")] +impl SnapshotStore for UiTree { /* planned */ } + +#[cfg(feature = "resumable")] +pub struct SnapshotOptions { + pub include_focus: bool, + pub include_scroll: bool, + pub include_reactive_values: bool, +} +``` + +## 11) Testing Contracts to Implement + +1. Reactive dependency tracking correctness. +2. Memo laziness and recompute minimality. +3. Effect ordering with batching. +4. UI invalidation minimality by property. +5. Deterministic layout outputs. +6. Deterministic render hash under identical state. +7. Kernel-only startup and on-demand feature wasm loading. +8. Snapshot/restore parity under `resumable`. diff --git a/docs/Architecture.md b/docs/Architecture.md new file mode 100644 index 0000000..8329dcd --- /dev/null +++ b/docs/Architecture.md @@ -0,0 +1,270 @@ +# Pulse UI Architecture (Phase 1 — DEFINE) + +## 1) Vision + +Pulse UI is a **single-engine** Rust UI framework designed for deterministic, pixel-identical framebuffer rendering across: + +- iOS native (iOS 26+) +- Android native (Android 8+) +- WebAssembly in Safari iOS 26+ and modern browsers + +The architecture enforces one retained UI model, one reactive runtime, one layout strategy, and one renderer pipeline so visuals and interaction semantics remain aligned across targets. + +## 2) Goals and Non-Goals + +### Goals + +1. Pixel-identical rendering pipeline across all targets. +2. Fine-grained reactivity (signals/memos/effects), incremental recomputation. +3. Deterministic retained UI tree and invalidation graph. +4. Deterministic layout engine and hit testing semantics. +5. GPU-first renderer via `wgpu` (WebGL2 backend for web target). +6. Web lazy wasm module loading (kernel + feature wasm bundles). +7. Optional resumability direction behind feature flag (`resumable`). + +### Non-Goals (MVP) + +1. Wrapping native widget toolkits (SwiftUI/UIKit widgets, Compose widgets). +2. DOM-based UI rendering on web. +3. Full IME parity in MVP (planned, staged). +4. Downloading executable code post-review on iOS native app delivery. + +## 3) Workspace and Crate Responsibilities + +```text +crates/ + pulse_reactive/ # signals, memos, effects, batching, scheduler, debug tracing + pulse_ui_core/ # retained UI tree, component runtime, invalidation graph + pulse_layout/ # deterministic layout + optional feature-gated taffy adapter + pulse_render/ # deterministic RenderCommand -> wgpu backend + headless renderer + pulse_text/ # text shaping/rasterization/atlas strategy and APIs + pulse_platform/ # native shell + event loop integration + pulse_web/ # wasm bindings, canvas surface, input normalization, wasm loader + pulse_macros/ # rsx-like DSL macros (preferred authoring layer) +examples/ + counter/ + todo/ + gallery/ + web_kernel/ + web_feature_demo/ +docs/ + Architecture.md + API.md + MVP.md +``` + +## 4) Rendering Model (Mandatory) + +### Data flow + +`Component/Signal State` → `Retained UiTree` → `Deterministic RenderCommand stream` → `pulse_render (wgpu/headless)`. + +### RenderCommand model + +The `pulse_ui_core` crate outputs an ordered render list: + +- `DrawRect` +- `DrawRoundedRect` +- `DrawText` +- `DrawImage` +- `PushClip` / `PopClip` +- `PushTransform` / `PopTransform` + +Determinism constraints: + +- Stable node IDs and stable child ordering. +- Stable traversal strategy. +- Stable floating-point policy (quantization/rounding policy documented). +- No target-specific painter shortcuts that alter ordering. + +### Headless rendering + +`pulse_render` includes a headless backend that renders to an RGBA image buffer for golden/hash verification tests. + +## 5) Reactive Runtime Model + +Pulse uses a fine-grained dependency graph with lazy dependency capture during execution: + +- `Signal`: mutable source. +- `Memo`: derived, cached value with dependency tracking. +- `Effect`: executes side effects and re-runs when dependencies become dirty. + +Execution principles: + +1. Read tracking happens during signal/memo reads in active reactive contexts. +2. Updates mark downstream nodes dirty. +3. Scheduler batches updates into transactions. +4. Recompute happens only when observed/required (lazy memos). +5. Effects run after state stabilization in scheduler phase. + +Debug mode: + +- Optional trace stream reports: + - Which signals changed + - Which memos recomputed + - Which effects ran + - Which UI nodes/properties invalidated + +## 6) UI Core + Invalidation + +`pulse_ui_core` stores: + +- Stable `NodeId` +- Node kind (container/text/custom component) +- Style/layout properties +- Event handlers (stable event binding IDs) +- Dirty flags per concern (layout, paint, text, hit map) + +Invalidation strategy: + +- Property-level invalidation edges map reactive values to specific node fields. +- On updates, only impacted node properties are recomputed. +- Layout and paint phases operate on dirty subgraphs only. + +## 7) Layout Strategy + +MVP layout engine is deterministic flexbox-like layout with explicit constraints: + +- Deterministic traversal and rounding. +- Constraint propagation is explicit and platform-independent. +- Feature-gated `taffy` adapter may be added to compare behavior and offer compatibility. + +Rationale: + +- Flexbox-like model provides broad UI expressiveness. +- Deterministic implementation remains tractable for cross-platform pixel parity. + +## 8) Text Strategy (Plan + Staging) + +### Shaping + raster plan + +- Shape text into glyph runs with stable shaping configuration. +- Rasterize glyphs into atlas pages. +- Render via atlas-backed draw commands. + +MVP staging: + +1. Basic Latin text shaping and atlas upload. +2. Deterministic line breaking + clipping. +3. Fallback fonts and international scripts. +4. IME composition integration (planned in platform layers). + +IME status: + +- Planned interfaces in API, staged implementation after MVP rendering/reactivity baseline. + +## 9) Event Routing, Hit Testing, Focus + +### Hit testing + +- Deterministic rect-based hit testing over layout output. +- Stable z-order and reverse paint-order hit resolution. + +### Routing model + +MVP uses explicit capture/target/bubble phases: + +1. Capture: root to target ancestors +2. Target: target node +3. Bubble: target ancestors to root + +### Focus model + +- `FocusId` stable across frames/snapshots. +- Keyboard navigation via focus graph computed from tree order + explicit overrides. +- Focus state stored in UI core for resumability. + +### Text input + +- Basic text input events normalized from native/web source. +- IME preedit/commit planned in dedicated pipeline. + +## 10) Web Architecture: Kernel + Lazy Features + +### Kernel module (`kernel.wasm`) + +Responsibilities: + +- Boot renderer and surface +- Input normalization +- Router +- Reactive store + retained tree +- Feature module loader + +### Feature modules (`feature_*.wasm`) + +Loaded on demand per route or interaction: + +- Export `init_feature(request_bytes) -> response_bytes` +- Export `handle_event(request_bytes) -> response_bytes` + +Communication: + +- Versioned binary envelopes (MessagePack preferred) for robust evolution. +- `Uint8Array`/`ArrayBuffer` for payloads. +- Small control messages may use JS object wrappers. + +Web loading: + +- dynamic `import()` for JS glue +- `WebAssembly.instantiateStreaming` where available, fallback to array-buffer instantiate + +Tooling: + +- `wasm-bindgen --split-linked-modules` +- Trunk initializer hook for serving split linked modules/assets + +## 11) Native Policy Constraints + +- iOS: no architecture requiring post-review executable code download. + - “Lazy loading” on iOS maps to lazy initialization and on-demand assets only. +- Android: architecture boundaries align with potential Play Feature Delivery integration. + +## 12) Resumability Plan (`feature = "resumable"`) + +### Snapshot scope + +- Reactive values (signals/memo cache metadata where valid) +- Retained UI tree state (stable IDs, key props) +- Focus state +- Scroll offsets +- Route/module state handles + +### Format + +- MessagePack bytes (compact, versioned schema) + +### Restore semantics + +- Restore graph + UI state without full rebuild where possible. +- Revalidate any stale memo dependencies lazily. +- First post-restore frame should match pre-snapshot output (headless parity test). + +## 13) Determinism Rules + +1. Stable IDs for nodes and handlers. +2. Stable iteration order for maps/collections (ordered map usage). +3. Stable rounding policy for layout and raster placement. +4. Platform-normalized input timestamps/event ordering. +5. Identical command ordering before renderer submission. + +## 14) DTI Methodology Integration + +This project follows DEFINE → TEST → IMPLEMENT: + +1. **DEFINE**: architecture/APIs/checklists/milestones (this phase). +2. **TEST**: author behavior and determinism tests first where practical. +3. **IMPLEMENT**: crate-by-crate, smallest verifiable increments. + +## 15) Requirements Checklist (to be validated in later phases) + +- [ ] Single retained tree and command renderer used on all targets. +- [ ] Fine-grained reactivity proves incremental recomputation. +- [ ] No full-tree diff as primary update mechanism. +- [ ] Deterministic layout tests pass. +- [ ] Headless render parity tests pass (pixel hash/golden). +- [ ] Web demo starts with kernel only; feature wasm loads only on demand. +- [ ] No DOM widget UI usage for web rendering. +- [ ] Native platforms use non-widget framebuffer rendering. +- [ ] `resumable` snapshot/restore tests pass for state + selected UI state. +- [ ] Debug tracing mode logs reactive/invalidation activity. diff --git a/docs/MVP.md b/docs/MVP.md new file mode 100644 index 0000000..4b63571 --- /dev/null +++ b/docs/MVP.md @@ -0,0 +1,130 @@ +# Pulse UI MVP Plan (Phase 1 — DEFINE) + +## Milestone Overview (DTI) + +- MVP0: Reactive runtime + retained tree + render commands + headless proof +- MVP1: Deterministic layout + input/hit/focus foundations +- MVP2: Web kernel + lazy feature wasm loading demo +- MVP3: `resumable` snapshot/restore (feature flag) + +## MVP0 — Reactive + Rendering Primitives + +### Scope + +- Implement `pulse_reactive` core primitives: + - signal/read/write + - memo + - effect + - batch/transaction + - scheduler hooks + - debug trace mode +- Implement `pulse_ui_core` base retained tree and invalidation tags. +- Implement `pulse_render` command model and headless renderer hash path. + +### Tests (required) + +- Reactive: + - dependency tracking + - memo recompute only when dirty + observed + - effect ordering and batching coalescing +- UI core: + - property-level invalidation only for affected nodes +- Render: + - deterministic command stream ordering + - deterministic headless hash for same input + +### Exit criteria + +- Cargo tests pass for `pulse_reactive`, `pulse_ui_core`, `pulse_render`. +- Debug tracing demonstrates fine-grained updates. + +## MVP1 — Deterministic Layout + Interaction Foundations + +### Scope + +- Implement `pulse_layout` deterministic flex-like algorithm. +- Integrate layout with ui_core invalidation flow. +- Implement deterministic hit testing, event phases, and focus navigation. +- Add text subsystem scaffolding APIs in `pulse_text`. + +### Tests (required) + +- Layout determinism for fixed trees/constraints. +- Hit testing determinism with overlapping nodes. +- Focus traversal stability. + +### Exit criteria + +- Layout + event/focus tests pass. +- `counter` and `todo` examples exercise state + interaction. + +## MVP2 — Web Kernel + Real Lazy Feature Wasm + +### Scope + +- `pulse_web` starts kernel-only module. +- Implement module loader for feature wasm bundles. +- Versioned byte message protocol between kernel and feature modules. +- trunk + wasm-bindgen split linked modules integration docs. + +### Tests (required) + +- Startup path does not load feature wasm. +- Route/interaction triggers feature module load. +- Feature request/response payload roundtrip contract test. + +### Exit criteria + +- `examples/web_kernel` runs with canvas-only rendering. +- `examples/web_feature_demo` is loaded lazily after interaction. + +## MVP3 — Resumable Snapshot/Restore (`feature = "resumable"`) + +### Scope + +- Snapshot state format (MessagePack + versioning). +- Persist/restore reactive values and selected UI state (focus, scroll). +- Restore to equivalent render output. + +### Tests (required) + +- Snapshot + restore reactive parity. +- Snapshot + restore focus/scroll parity. +- Pre/post snapshot headless render hash parity. + +### Exit criteria + +- `cargo test --features resumable` passes for scoped resumability tests. +- Document known gaps (e.g., partial memo cache invalidation policy). + +## Global Acceptance Criteria Tracking + +- [ ] `cargo test` passes workspace-wide. +- [ ] `cargo fmt --all -- --check` passes. +- [ ] `cargo clippy --workspace --all-targets -- -D warnings` passes. +- [ ] At least one native example runs. +- [ ] Web trunk example builds/runs canvas-only. +- [ ] Lazy-loaded wasm feature proven in demo. +- [ ] Resumable tests pass behind feature flag. + +## Run Instructions (for later implementation phases) + +### Native demo + +```bash +cargo run -p counter +``` + +### Web demo + +```bash +cd examples/web_kernel +trunk serve +``` + +### Lazy feature demo expectation + +1. Launch `web_kernel`. +2. Verify only kernel wasm requested at startup. +3. Click lazy feature entry route/button. +4. Verify `web_feature_demo` wasm fetched/instantiated then. diff --git a/examples/counter/.gitkeep b/examples/counter/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/examples/gallery/.gitkeep b/examples/gallery/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/examples/todo/.gitkeep b/examples/todo/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/examples/web_feature_demo/.gitkeep b/examples/web_feature_demo/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/examples/web_kernel/.gitkeep b/examples/web_kernel/.gitkeep new file mode 100644 index 0000000..e69de29 From 921b4c37765e2f89f8df26b92a7e550bd6769884 Mon Sep 17 00:00:00 2001 From: Michael Grilo <4789416+michaelgrilo@users.noreply.github.com> Date: Mon, 9 Feb 2026 23:41:21 -0500 Subject: [PATCH 2/3] docs: refine phase-1 define docs with requirements traceability --- docs/API.md | 145 ++++++++++------ docs/Architecture.md | 401 +++++++++++++++++++++++-------------------- docs/MVP.md | 153 +++++++---------- docs/Requirements.md | 120 +++++++++++++ 4 files changed, 488 insertions(+), 331 deletions(-) create mode 100644 docs/Requirements.md diff --git a/docs/API.md b/docs/API.md index 18b3fa7..756bc80 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1,19 +1,29 @@ -# Pulse UI Public API Draft (Phase 1 — DEFINE) +# Pulse UI API Specification (Phase 1 — DEFINE) -This document defines the intended public API surface for MVP planning. Signatures may evolve minimally in implementation phases. +This document defines the intended public API shape for Phase 2 test authoring and Phase 3 implementation. -## 1) Top-level crate usage +## 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::*; ``` -## 2) `pulse_reactive` API +## 3. `pulse_reactive` API -### Core primitives +### 3.1 Core Primitives ```rust pub struct Signal { /* opaque */ } @@ -33,7 +43,7 @@ impl WriteSignal { } ``` -### Memos and effects +### 3.2 Derived State and Effects ```rust pub struct Memo { /* opaque */ } @@ -47,20 +57,20 @@ impl Memo { pub fn effect(f: impl FnMut() + 'static) -> EffectHandle; ``` -### Batching and scheduler +### 3.3 Batching and Scheduler ```rust pub fn batch(f: impl FnOnce()); -pub trait Scheduler { +pub trait Scheduler: Send + Sync { fn schedule_microtask(&self, task: Box); fn schedule_animation_frame(&self, task: Box); } -pub fn set_scheduler(scheduler: Box); +pub fn set_scheduler(scheduler: Box); ``` -### Debug tracing +### 3.4 Debug Tracing ```rust #[derive(Debug, Clone)] @@ -74,9 +84,9 @@ pub fn set_trace_enabled(enabled: bool); pub fn take_trace_events() -> Vec; ``` -## 3) `pulse_ui_core` API +## 4. `pulse_ui_core` API -### Node and tree model +### 4.1 IDs and Tree ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -85,34 +95,46 @@ 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, child: NodeSpec) -> 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; } ``` -### Node spec, properties, and invalidation +### 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 { @@ -124,7 +146,7 @@ pub enum InvalidationReason { } ``` -### Events and routing +### 4.3 Event Model ```rust pub enum EventPhase { Capture, Target, Bubble } @@ -140,37 +162,64 @@ 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 trait EventHandler { - fn handle_event(&mut self, event: &UiEvent) -> EventDisposition; -} - pub enum EventDisposition { Continue, StopPropagation, } + +pub trait EventHandler { + fn handle_event(&mut self, event: &UiEvent) -> EventDisposition; +} ``` -### Focus and state +### 4.4 Focus and Scroll State ```rust pub struct FocusState { pub focused: Option, } +pub struct ScrollState { + pub x: f32, + pub y: f32, +} + impl UiTree { + pub fn set_focus(&mut self, focus: Option); pub fn focus_next(&mut self); pub fn focus_prev(&mut self); - pub fn set_focus(&mut self, focus: Option); pub fn focus_state(&self) -> &FocusState; } ``` -## 4) `pulse_layout` API +### 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 */ } @@ -190,16 +239,12 @@ impl LayoutEngine { pub fn new() -> Self; pub fn compute(&mut self, tree: &UiTree, constraints: LayoutConstraints) -> LayoutResult; } -``` - -Feature-gated adapter: -```rust #[cfg(feature = "taffy")] pub struct TaffyAdapter; ``` -## 5) `pulse_render` API +## 6. `pulse_render` API ```rust pub struct Renderer { /* opaque */ } @@ -236,7 +281,7 @@ impl HeadlessRenderer { } ``` -## 6) `pulse_text` API (staged) +## 7. `pulse_text` API ```rust pub struct TextSystem { /* opaque */ } @@ -255,7 +300,7 @@ impl TextSystem { } ``` -## 7) `pulse_platform` API +## 8. `pulse_platform` API ```rust pub struct AppConfig { @@ -273,7 +318,7 @@ pub trait AppDelegate { pub fn run_native(config: AppConfig, app: impl AppDelegate + 'static) -> Result<(), PlatformError>; ``` -## 8) `pulse_web` API +## 9. `pulse_web` API ```rust pub struct WebKernelConfig { @@ -289,21 +334,25 @@ pub trait FeatureModule { } ``` -Message envelope: +### 9.1 Versioned Message Envelopes ```rust #[derive(Serialize, Deserialize)] -pub struct ModuleEnvelope { - pub version: u16, +pub enum KernelFeatureMessage { + MsgV1(ModuleEnvelopeV1), + MsgV2(ModuleEnvelopeV2), +} + +#[derive(Serialize, Deserialize)] +pub struct ModuleEnvelopeV1 { pub message_type: String, pub payload: Vec, } ``` -## 9) `pulse_macros` API (preferred declarative layer) +## 10. `pulse_macros` API ```rust -// Example usage target let view = rsx! { column(width: 320.0, height: 200.0) { text("Hello") @@ -314,23 +363,21 @@ let view = rsx! { }; ``` -Macro output contract: +Macro contract: -- Generates stable node keys and component constructors. -- Emits bindings to reactive sources with property-level invalidation. +- MUST emit stable node identity bindings. +- MUST wire reactive property dependencies for fine-grained invalidation. +- SHOULD support component composition and theming ergonomics. -## 10) Resumability API (feature = "resumable") +## 11. Resumability API (`feature = "resumable"`) ```rust #[cfg(feature = "resumable")] pub trait SnapshotStore { - fn snapshot(&self) -> Vec; + fn snapshot(&self, options: SnapshotOptions) -> Vec; fn restore(&mut self, bytes: &[u8]) -> Result<(), SnapshotError>; } -#[cfg(feature = "resumable")] -impl SnapshotStore for UiTree { /* planned */ } - #[cfg(feature = "resumable")] pub struct SnapshotOptions { pub include_focus: bool, @@ -339,13 +386,7 @@ pub struct SnapshotOptions { } ``` -## 11) Testing Contracts to Implement +## 12. API Stability Notes -1. Reactive dependency tracking correctness. -2. Memo laziness and recompute minimality. -3. Effect ordering with batching. -4. UI invalidation minimality by property. -5. Deterministic layout outputs. -6. Deterministic render hash under identical state. -7. Kernel-only startup and on-demand feature wasm loading. -8. Snapshot/restore parity under `resumable`. +- This API document is DEFINE-phase and SHOULD be considered provisional. +- Phase 2 tests MUST pin behavior contracts before Phase 3 implementation finalizes signatures. diff --git a/docs/Architecture.md b/docs/Architecture.md index 8329dcd..572aefa 100644 --- a/docs/Architecture.md +++ b/docs/Architecture.md @@ -1,270 +1,301 @@ # Pulse UI Architecture (Phase 1 — DEFINE) -## 1) Vision +## 1. Scope and Mission -Pulse UI is a **single-engine** Rust UI framework designed for deterministic, pixel-identical framebuffer rendering across: +Pulse UI is a Rust-first, single-engine UI framework that MUST produce deterministic framebuffer UI output across: -- iOS native (iOS 26+) -- Android native (Android 8+) -- WebAssembly in Safari iOS 26+ and modern browsers +- iOS 26+ (native) +- Android 8+ (native) +- WebAssembly in Safari iOS 26+ and other modern browsers -The architecture enforces one retained UI model, one reactive runtime, one layout strategy, and one renderer pipeline so visuals and interaction semantics remain aligned across targets. +This document defines architecture only (DEFINE phase). It does not implement runtime behavior yet. -## 2) Goals and Non-Goals +## 2. Normative Terms -### Goals +This spec uses RFC keywords: -1. Pixel-identical rendering pipeline across all targets. -2. Fine-grained reactivity (signals/memos/effects), incremental recomputation. -3. Deterministic retained UI tree and invalidation graph. -4. Deterministic layout engine and hit testing semantics. -5. GPU-first renderer via `wgpu` (WebGL2 backend for web target). -6. Web lazy wasm module loading (kernel + feature wasm bundles). -7. Optional resumability direction behind feature flag (`resumable`). +- MUST / MUST NOT: absolute requirements +- SHOULD / SHOULD NOT: recommended defaults with rationale for exceptions +- MAY: optional -### Non-Goals (MVP) +## 3. Goals and Non-Goals -1. Wrapping native widget toolkits (SwiftUI/UIKit widgets, Compose widgets). -2. DOM-based UI rendering on web. -3. Full IME parity in MVP (planned, staged). -4. Downloading executable code post-review on iOS native app delivery. +### 3.1 Goals -## 3) Workspace and Crate Responsibilities +- The framework MUST use one retained UI tree, one reactive runtime, one deterministic layout engine, and one renderer command model across all targets. +- The framework MUST target pixel-identical output per D1: bitwise-identical RGBA for same state/input/viewport/assets in headless tests. +- The framework MUST provide incremental updates driven by a fine-grained dependency graph. +- The framework MUST provide deterministic event routing, hit testing, and focus semantics. +- The framework MUST support web kernel/feature wasm architecture with real on-demand feature loading. +- The framework MUST provide resumability hooks behind `feature = "resumable"`. -```text -crates/ - pulse_reactive/ # signals, memos, effects, batching, scheduler, debug tracing - pulse_ui_core/ # retained UI tree, component runtime, invalidation graph - pulse_layout/ # deterministic layout + optional feature-gated taffy adapter - pulse_render/ # deterministic RenderCommand -> wgpu backend + headless renderer - pulse_text/ # text shaping/rasterization/atlas strategy and APIs - pulse_platform/ # native shell + event loop integration - pulse_web/ # wasm bindings, canvas surface, input normalization, wasm loader - pulse_macros/ # rsx-like DSL macros (preferred authoring layer) -examples/ - counter/ - todo/ - gallery/ - web_kernel/ - web_feature_demo/ -docs/ - Architecture.md - API.md - MVP.md -``` +### 3.2 Non-Goals (MVP) -## 4) Rendering Model (Mandatory) +- The framework MUST NOT wrap native widgets (e.g., SwiftUI/Compose widgets). +- The framework MUST NOT use DOM for UI layout/widgets; DOM MAY only create the rendering surface and collect input events. +- Full IME parity is NOT required in MVP; architecture MUST preserve compatibility for IME completion later. +- iOS native distribution MUST NOT rely on downloading new executable code post-review. -### Data flow +## 4. Workspace Architecture -`Component/Signal State` → `Retained UiTree` → `Deterministic RenderCommand stream` → `pulse_render (wgpu/headless)`. +Pulse UI uses a consistent `pulse_*` naming scheme: -### RenderCommand model +- `crates/pulse_reactive`: signals/memos/effects, dependency tracking, transactions, scheduler, debug tracing +- `crates/pulse_ui_core`: retained node tree, component instances, props/state bindings, invalidation graph +- `crates/pulse_layout`: deterministic layout engine (flex-like), optional adapter features +- `crates/pulse_render`: render command execution, `wgpu` backend, headless RGBA rendering +- `crates/pulse_text`: shaping/raster plan, atlas policy, font lifecycle +- `crates/pulse_platform`: native window/surface and event-loop integration +- `crates/pulse_web`: web glue (surface, event normalization, kernel/feature loader) +- `crates/pulse_macros`: declarative authoring DSL (`rsx!`-like) -The `pulse_ui_core` crate outputs an ordered render list: +Examples: -- `DrawRect` -- `DrawRoundedRect` -- `DrawText` -- `DrawImage` -- `PushClip` / `PopClip` -- `PushTransform` / `PopTransform` +- `examples/counter`, `examples/todo`, `examples/gallery` +- `examples/web_kernel` (startup kernel) +- `examples/web_feature_demo` (lazy feature wasm) + +## 5. Determinism and Pixel Identity + +### 5.1 Determinism Contract + +Given identical: + +1. App state +2. Input event stream (including ordering/timestamps after normalization) +3. Viewport and scale factor +4. Fonts/assets + +the engine MUST produce: + +- identical retained tree state, +- identical ordered render command stream, +- identical headless RGBA output. + +### 5.2 Determinism Rules + +- Node IDs and event handler IDs MUST be stable. +- Child traversal order MUST be stable. +- Floating-point policy for layout and raster coordinates MUST be explicitly defined and uniformly applied. +- Unordered collections in logic-critical paths MUST NOT affect output ordering. +- Event normalization MUST define deterministic ordering semantics. + +## 6. Reactive Runtime Model (`pulse_reactive`) + +### 6.1 Core Graph + +The runtime MUST support: -Determinism constraints: +- `Signal` mutable source nodes +- `Memo` derived nodes with dependency capture during evaluation +- `Effect` side-effect nodes rerun after upstream invalidation -- Stable node IDs and stable child ordering. -- Stable traversal strategy. -- Stable floating-point policy (quantization/rounding policy documented). -- No target-specific painter shortcuts that alter ordering. +Dependency edges MUST be captured dynamically when reads occur in active reactive contexts. -### Headless rendering +### 6.2 Invalidation and Scheduling -`pulse_render` includes a headless backend that renders to an RGBA image buffer for golden/hash verification tests. +- Signal writes MUST mark downstream memo/effect dependencies dirty. +- Recompute MUST be lazy where possible (memo reevaluates only when observed after invalidation). +- Effects MUST execute in scheduler phases after batched state stabilization. +- Transactions/batching MUST coalesce multiple writes into minimal recompute waves. +- The architecture MUST NOT rely on full-tree diffing as its primary update strategy. -## 5) Reactive Runtime Model +### 6.3 Debug Traceability -Pulse uses a fine-grained dependency graph with lazy dependency capture during execution: +A debug mode MUST emit structured tracing events for: -- `Signal`: mutable source. -- `Memo`: derived, cached value with dependency tracking. -- `Effect`: executes side effects and re-runs when dependencies become dirty. +- signal writes +- memo recomputes +- effect runs +- UI node/property invalidation edges -Execution principles: +Trace output SHOULD be consumable by tests and optional tooling. -1. Read tracking happens during signal/memo reads in active reactive contexts. -2. Updates mark downstream nodes dirty. -3. Scheduler batches updates into transactions. -4. Recompute happens only when observed/required (lazy memos). -5. Effects run after state stabilization in scheduler phase. +## 7. Retained UI Tree and Component Model (`pulse_ui_core`) -Debug mode: +### 7.1 Retained Tree -- Optional trace stream reports: - - Which signals changed - - Which memos recomputed - - Which effects ran - - Which UI nodes/properties invalidated +The retained tree MUST own: -## 6) UI Core + Invalidation +- stable `NodeId` +- node type and component instance metadata +- style/layout/paint props +- event handler bindings with stable IDs +- dirty flags by subsystem (layout, paint, text, hit, focus) -`pulse_ui_core` stores: +### 7.2 Components and Theming + +- Components MUST be reusable units parameterized by props. +- Theme tokens (color, spacing, typography) MUST be first-class. +- Responsive primitives MUST be expressible through constraints and layout adapters. + +### 7.3 Invalidation Graph + +Property-level dependency edges MUST map reactive values to impacted node fields. + +On updates: + +- only affected properties SHOULD recompute, +- only affected nodes SHOULD relayout/repaint, +- unaffected subtrees SHOULD remain untouched. + +## 8. Layout Strategy (`pulse_layout`) + +- Layout MUST be deterministic given tree + constraints. +- MVP layout SHOULD use a flex-like algorithm for expressiveness and implementation tractability. +- Rounding/quantization policy MUST be centralized and deterministic. +- A feature-gated adapter integration (e.g., `taffy`) MAY be provided, but must not compromise determinism. + +## 9. Rendering Model (`pulse_render`) + +### 9.1 Command Pipeline + +`pulse_ui_core` MUST output an ordered command list containing at least: + +- `DrawRect` +- `DrawRoundedRect` +- `DrawText` +- `DrawImage` +- `PushClip` / `PopClip` +- `PushTransform` / `PopTransform` -- Stable `NodeId` -- Node kind (container/text/custom component) -- Style/layout properties -- Event handlers (stable event binding IDs) -- Dirty flags per concern (layout, paint, text, hit map) +Renderer submission MUST preserve command ordering semantics. -Invalidation strategy: +### 9.2 Backend Policy -- Property-level invalidation edges map reactive values to specific node fields. -- On updates, only impacted node properties are recomputed. -- Layout and paint phases operate on dirty subgraphs only. +- Rendering MUST be `wgpu`-first. +- Web SHOULD use `wgpu` web targets (WebGPU where available, with fallback path documented). +- Native targets SHOULD use equivalent `wgpu` paths for parity. -## 7) Layout Strategy +### 9.3 Headless Golden Path -MVP layout engine is deterministic flexbox-like layout with explicit constraints: +A headless render path MUST produce RGBA buffers for deterministic verification (hash and/or image golden comparisons). -- Deterministic traversal and rounding. -- Constraint propagation is explicit and platform-independent. -- Feature-gated `taffy` adapter may be added to compare behavior and offer compatibility. +## 10. Text System Plan (`pulse_text`) -Rationale: +- Text architecture MUST define shaping + glyph atlas + draw integration. +- MVP MUST include deterministic baseline text path sufficient for tests and examples. +- IME support MAY be staged, but API hooks for preedit/commit MUST be planned now. +- Font fallback strategy SHOULD be deterministic and versioned in test assets. -- Flexbox-like model provides broad UI expressiveness. -- Deterministic implementation remains tractable for cross-platform pixel parity. +## 11. Input, Events, Hit Testing, Focus -## 8) Text Strategy (Plan + Staging) +### 11.1 Input Coverage -### Shaping + raster plan +The platform model MUST cover: -- Shape text into glyph runs with stable shaping configuration. -- Rasterize glyphs into atlas pages. -- Render via atlas-backed draw commands. +- pointer/touch +- keyboard +- focus traversal +- basic text input -MVP staging: +### 11.2 Routing -1. Basic Latin text shaping and atlas upload. -2. Deterministic line breaking + clipping. -3. Fallback fonts and international scripts. -4. IME composition integration (planned in platform layers). +Routing MUST define deterministic phase semantics (capture/target/bubble or equivalent). This architecture chooses capture/target/bubble. -IME status: +### 11.3 Hit Testing -- Planned interfaces in API, staged implementation after MVP rendering/reactivity baseline. +Hit testing MUST be derived from layout geometry and deterministic z-order policy. -## 9) Event Routing, Hit Testing, Focus +### 11.4 Focus -### Hit testing +Focus IDs MUST be stable and serializable for resumability. -- Deterministic rect-based hit testing over layout output. -- Stable z-order and reverse paint-order hit resolution. +## 12. Web Lazy Wasm Architecture (`pulse_web`) -### Routing model +### 12.1 Module Roles -MVP uses explicit capture/target/bubble phases: +**Kernel wasm (always loaded at startup) MUST include:** -1. Capture: root to target ancestors -2. Target: target node -3. Bubble: target ancestors to root +- render loop +- input normalization +- router +- reactive store + retained tree owner +- feature loader/runtime bridge -### Focus model +**Feature wasm modules (lazy) MUST include:** -- `FocusId` stable across frames/snapshots. -- Keyboard navigation via focus graph computed from tree order + explicit overrides. -- Focus state stored in UI core for resumability. +- route/feature-specific behavior +- exported initialization and event handling entrypoints -### Text input +### 12.2 Real Lazy Loading -- Basic text input events normalized from native/web source. -- IME preedit/commit planned in dedicated pipeline. +Feature wasm binaries MUST NOT be downloaded or instantiated at startup. +They MUST load only on demand (route change or first interaction). -## 10) Web Architecture: Kernel + Lazy Features +### 12.3 Message Contract -### Kernel module (`kernel.wasm`) +Kernel-feature communication MUST use versioned serialized envelopes (`MsgV1`, `MsgV2`, ...). -Responsibilities: +- Binary payloads SHOULD be preferred (CBOR/bytes) for larger messages. +- Small control messages MAY use compact JSON. -- Boot renderer and surface -- Input normalization -- Router -- Reactive store + retained tree -- Feature module loader +### 12.4 Loader Lifecycle -### Feature modules (`feature_*.wasm`) +The web loader MUST support: -Loaded on demand per route or interaction: +- dynamic `import()` and/or fetch-based discovery +- `WebAssembly.instantiateStreaming` when supported +- explicit fallback path when streaming is unavailable -- Export `init_feature(request_bytes) -> response_bytes` -- Export `handle_event(request_bytes) -> response_bytes` +### 12.5 wasm-bindgen Linked Module Splitting -Communication: +Build docs MUST describe `wasm-bindgen --split-linked-modules` usage for linked JS/snippet/module splitting and CSP-compatible asset serving via trunk. -- Versioned binary envelopes (MessagePack preferred) for robust evolution. -- `Uint8Array`/`ArrayBuffer` for payloads. -- Small control messages may use JS object wrappers. +Note: linked-module splitting MUST NOT be misrepresented as splitting Rust wasm code into multiple codegen shards. -Web loading: +## 13. Platform Policy Constraints -- dynamic `import()` for JS glue -- `WebAssembly.instantiateStreaming` where available, fallback to array-buffer instantiate +- iOS native MUST NOT download/execute new code after App Store review. +- Therefore, “lazy” on iOS native MUST mean lazy initialization + on-demand assets only. +- Android architecture SHOULD map cleanly to future Play Feature Delivery integration boundaries. -Tooling: +## 14. Resumability Direction (`feature = "resumable"`) -- `wasm-bindgen --split-linked-modules` -- Trunk initializer hook for serving split linked modules/assets +### 14.1 Snapshot Scope -## 11) Native Policy Constraints +The resumable snapshot MUST include, at minimum: -- iOS: no architecture requiring post-review executable code download. - - “Lazy loading” on iOS maps to lazy initialization and on-demand assets only. -- Android: architecture boundaries align with potential Play Feature Delivery integration. +- reactive signal values +- selected UI state (focus, scroll) +- stable node/event IDs needed for restore continuity -## 12) Resumability Plan (`feature = "resumable"`) +### 14.2 Snapshot Format -### Snapshot scope +A versioned binary format (CBOR or MessagePack) SHOULD be used. -- Reactive values (signals/memo cache metadata where valid) -- Retained UI tree state (stable IDs, key props) -- Focus state -- Scroll offsets -- Route/module state handles +### 14.3 Restore Semantics -### Format +Restore SHOULD avoid full recomputation where possible and MUST preserve correctness. -- MessagePack bytes (compact, versioned schema) +MVP acceptance target: -### Restore semantics +- restored state equals pre-snapshot state for covered fields, +- post-restore headless render output equals pre-snapshot output. -- Restore graph + UI state without full rebuild where possible. -- Revalidate any stale memo dependencies lazily. -- First post-restore frame should match pre-snapshot output (headless parity test). +## 15. Performance Budgets (Initial Targets) -## 13) Determinism Rules +These are initial targets and MAY be revised in MVP milestones with measurement data: -1. Stable IDs for nodes and handlers. -2. Stable iteration order for maps/collections (ordered map usage). -3. Stable rounding policy for layout and raster placement. -4. Platform-normalized input timestamps/event ordering. -5. Identical command ordering before renderer submission. +- Web kernel time-to-first-frame on iPhone-class hardware: SHOULD target `< 120 ms` for simple demo scene. +- Hot render path allocations: SHOULD be zero or explicitly bounded/measured. +- Incremental updates: SHOULD demonstrate that unaffected nodes do not recompute. -## 14) DTI Methodology Integration +## 16. DTI Execution Rules -This project follows DEFINE → TEST → IMPLEMENT: +- Phase 1 (DEFINE): architecture/API/requirements/milestones docs only. +- Phase 2 (TEST): author tests first where practical; each test MUST trace to requirement IDs. +- Phase 3 (IMPLEMENT): crate-by-crate incremental implementation with verification gates. -1. **DEFINE**: architecture/APIs/checklists/milestones (this phase). -2. **TEST**: author behavior and determinism tests first where practical. -3. **IMPLEMENT**: crate-by-crate, smallest verifiable increments. +## 17. Global Acceptance Criteria (for final completion, not Phase 1) -## 15) Requirements Checklist (to be validated in later phases) +Before claiming full project completion, the workspace MUST satisfy: -- [ ] Single retained tree and command renderer used on all targets. -- [ ] Fine-grained reactivity proves incremental recomputation. -- [ ] No full-tree diff as primary update mechanism. -- [ ] Deterministic layout tests pass. -- [ ] Headless render parity tests pass (pixel hash/golden). -- [ ] Web demo starts with kernel only; feature wasm loads only on demand. -- [ ] No DOM widget UI usage for web rendering. -- [ ] Native platforms use non-widget framebuffer rendering. -- [ ] `resumable` snapshot/restore tests pass for state + selected UI state. -- [ ] Debug tracing mode logs reactive/invalidation activity. +- `cargo test` passes across workspace +- `cargo fmt --all -- --check` passes +- `cargo clippy --workspace --all-targets -- -D warnings` passes +- at least one native example runs +- web demo builds/runs with trunk +- fine-grained recompute behavior is demonstrated via tests + debug logs +- headless pixel-stability tests pass +- web demonstrates real lazy feature wasm loading diff --git a/docs/MVP.md b/docs/MVP.md index 4b63571..6e1b3da 100644 --- a/docs/MVP.md +++ b/docs/MVP.md @@ -1,130 +1,95 @@ # Pulse UI MVP Plan (Phase 1 — DEFINE) -## Milestone Overview (DTI) +## 1. DTI Plan -- MVP0: Reactive runtime + retained tree + render commands + headless proof -- MVP1: Deterministic layout + input/hit/focus foundations -- MVP2: Web kernel + lazy feature wasm loading demo -- MVP3: `resumable` snapshot/restore (feature flag) +Pulse UI follows DEFINE → TEST → IMPLEMENT. -## MVP0 — Reactive + Rendering Primitives +- **Phase 1 (DEFINE)**: architecture/API/requirements docs only. +- **Phase 2 (TEST)**: tests first where practical; each test MUST map to requirement IDs. +- **Phase 3 (IMPLEMENT)**: crate-by-crate implementation with verification gates. -### Scope +## 2. Milestones -- Implement `pulse_reactive` core primitives: - - signal/read/write - - memo - - effect - - batch/transaction - - scheduler hooks - - debug trace mode -- Implement `pulse_ui_core` base retained tree and invalidation tags. -- Implement `pulse_render` command model and headless renderer hash path. +## MVP0 — Reactive Core + Retained Tree + Render Commands -### Tests (required) +### Scope -- Reactive: - - dependency tracking - - memo recompute only when dirty + observed - - effect ordering and batching coalescing -- UI core: - - property-level invalidation only for affected nodes -- Render: - - deterministic command stream ordering - - deterministic headless hash for same input +- Define and implement `pulse_reactive` primitives: Signal/Memo/Effect, dependency tracking, batching, scheduler hooks. +- Define and implement `pulse_ui_core` retained tree and invalidation primitives. +- Define and implement `pulse_render` render command model + headless render output path. -### Exit criteria +### Acceptance checks -- Cargo tests pass for `pulse_reactive`, `pulse_ui_core`, `pulse_render`. -- Debug tracing demonstrates fine-grained updates. +- Reactive dependency tracking tests pass (R5). +- Transaction coalescing and effect scheduling tests pass (R5, R6). +- UI invalidation minimality tests pass (R6). +- Render command determinism tests pass (R8). +- Headless RGBA hash tests pass for stable input (R1, R8). -## MVP1 — Deterministic Layout + Interaction Foundations +## MVP1 — Deterministic Layout + Input/Focus Foundations ### Scope -- Implement `pulse_layout` deterministic flex-like algorithm. -- Integrate layout with ui_core invalidation flow. -- Implement deterministic hit testing, event phases, and focus navigation. -- Add text subsystem scaffolding APIs in `pulse_text`. - -### Tests (required) +- Implement deterministic layout engine in `pulse_layout`. +- Integrate layout + hit testing + event routing + focus state in `pulse_ui_core`. +- Integrate text baseline path in `pulse_text`. -- Layout determinism for fixed trees/constraints. -- Hit testing determinism with overlapping nodes. -- Focus traversal stability. +### Acceptance checks -### Exit criteria - -- Layout + event/focus tests pass. -- `counter` and `todo` examples exercise state + interaction. +- Layout determinism tests pass with fixed constraints (R7). +- Event phase and hit-testing determinism tests pass (R11). +- Focus traversal stability tests pass (R11). ## MVP2 — Web Kernel + Real Lazy Feature Wasm ### Scope -- `pulse_web` starts kernel-only module. -- Implement module loader for feature wasm bundles. -- Versioned byte message protocol between kernel and feature modules. -- trunk + wasm-bindgen split linked modules integration docs. - -### Tests (required) +- Implement `pulse_web` kernel startup path. +- Implement on-demand feature module loader. +- Implement versioned kernel/feature serialized message contract. +- Document trunk + wasm-bindgen linked module splitting behavior. -- Startup path does not load feature wasm. -- Route/interaction triggers feature module load. -- Feature request/response payload roundtrip contract test. +### Acceptance checks -### Exit criteria +- Web kernel boots without downloading feature wasm (R10). +- Feature wasm download/instantiate happens only after trigger (R10). +- Web renders to single surface (no DOM UI widgets) (R3). +- trunk + wasm-bindgen build flow validated for web demo (R9). -- `examples/web_kernel` runs with canvas-only rendering. -- `examples/web_feature_demo` is loaded lazily after interaction. - -## MVP3 — Resumable Snapshot/Restore (`feature = "resumable"`) +## MVP3 — Resumable Snapshot/Restore (Feature-Flagged) ### Scope -- Snapshot state format (MessagePack + versioning). -- Persist/restore reactive values and selected UI state (focus, scroll). -- Restore to equivalent render output. - -### Tests (required) - -- Snapshot + restore reactive parity. -- Snapshot + restore focus/scroll parity. -- Pre/post snapshot headless render hash parity. - -### Exit criteria - -- `cargo test --features resumable` passes for scoped resumability tests. -- Document known gaps (e.g., partial memo cache invalidation policy). +- Add `feature = "resumable"` snapshot/restore APIs. +- Persist and restore signal values + selected UI state (focus/scroll). +- Validate post-restore render parity in headless mode. -## Global Acceptance Criteria Tracking +### Acceptance checks -- [ ] `cargo test` passes workspace-wide. -- [ ] `cargo fmt --all -- --check` passes. -- [ ] `cargo clippy --workspace --all-targets -- -D warnings` passes. -- [ ] At least one native example runs. -- [ ] Web trunk example builds/runs canvas-only. -- [ ] Lazy-loaded wasm feature proven in demo. -- [ ] Resumable tests pass behind feature flag. +- Snapshot/restore state parity tests pass (R12). +- Post-restore render hash parity tests pass (R12, R8). -## Run Instructions (for later implementation phases) +## 3. Global Quality Gates -### Native demo +Before project completion claim: -```bash -cargo run -p counter -``` +- `cargo test` MUST pass workspace-wide. +- `cargo fmt --all -- --check` MUST pass. +- `cargo clippy --workspace --all-targets -- -D warnings` MUST pass. +- At least one native example MUST run (R2, R11). +- Web trunk example MUST run and use a single render surface (R3, R9). +- Pixel-stable headless tests MUST exist and pass (R1, R8). +- Real lazy web feature wasm loading MUST be demonstrated (R10). -### Web demo +## 4. Initial Performance Budgets (to be validated later) -```bash -cd examples/web_kernel -trunk serve -``` +- Web kernel time-to-first-frame SHOULD target `< 120 ms` on iPhone-class hardware for demo scene. +- Hot render path allocations SHOULD be zero or explicitly measured/bounded. +- Incremental update tests SHOULD prove unaffected nodes are not recomputed. -### Lazy feature demo expectation +## 5. Risk Register (DEFINE) -1. Launch `web_kernel`. -2. Verify only kernel wasm requested at startup. -3. Click lazy feature entry route/button. -4. Verify `web_feature_demo` wasm fetched/instantiated then. +- Browser backend differences MAY require fallback policies; deterministic contract remains mandatory. +- Font rasterization differences are a determinism risk and MUST be controlled via test assets/policies. +- wasm module boundary design complexity MAY delay MVP2; requirement traceability MUST remain intact. +- Resumability cache correctness is a risk; MVP3 SHOULD begin with conservative invalidation semantics. diff --git a/docs/Requirements.md b/docs/Requirements.md new file mode 100644 index 0000000..0ee9625 --- /dev/null +++ b/docs/Requirements.md @@ -0,0 +1,120 @@ +# Pulse UI Requirements and Traceability Matrix (Phase 1 — DEFINE) + +This document defines measurable requirements and maps each requirement to planned tests, examples, and documentation sections. + +## 1. Requirement Definitions + +## R1 — Pixel-identical output + +The engine MUST produce bitwise-identical headless RGBA output across platforms for identical state/input/viewport/assets. + +**Acceptance criterion:** +Headless golden/hash tests pass with equal output for equivalent platform runs. + +## R2 — No native widget wrapping + +The framework MUST NOT rely on SwiftUI/Compose widgets for UI rendering. + +**Acceptance criterion:** +Platform integrations render via shared engine surface and command pipeline only. + +## R3 — No DOM UI + +Web UI MUST use one render surface; DOM MAY only create surface and collect events. + +**Acceptance criterion:** +Web examples render widgets/layout via engine commands, not DOM elements. + +## R4 — wgpu-first rendering + +Renderer MUST be wgpu-first; web SHOULD use wgpu web targets with documented fallback. + +**Acceptance criterion:** +Renderer crate exposes wgpu backend path used by native and web targets. + +## R5 — Fine-grained reactive graph + +Runtime MUST implement Signal/Memo/Effect with dependency tracking. + +**Acceptance criterion:** +Unit tests verify dependency capture, memo laziness, and effect triggering semantics. + +## R6 — Incremental invalidation updates + +Updates MUST be invalidation-based and MUST NOT primarily use full-tree diffing. + +**Acceptance criterion:** +Tests demonstrate updates touch only affected node properties/subtrees. + +## R7 — Deterministic layout + +Layout MUST be deterministic for fixed tree and constraints. + +**Acceptance criterion:** +Repeat layout runs produce identical rect outputs. + +## R8 — Deterministic rendering pipeline + +Same inputs MUST yield same render commands and same headless pixels. + +**Acceptance criterion:** +Render command ordering tests and pixel hash tests pass. + +## R9 — Web build tooling constraint + +Web examples MUST build via trunk + wasm-bindgen without Node dependency for core functionality. + +**Acceptance criterion:** +Documented trunk build/run flow succeeds for web example(s). + +## R10 — Real lazy-loaded wasm features + +Web MUST load feature wasm modules only on-demand, not at startup. + +**Acceptance criterion:** +Tests/log assertions confirm no feature wasm fetch at boot and on-demand fetch after trigger. + +## R11 — Input and focus foundations + +Platforms MUST support pointer/touch, keyboard, focus navigation, and basic text input. + +**Acceptance criterion:** +Cross-platform event normalization and focus tests pass. + +## R12 — Resumability feature flag + +Under `feature = "resumable"`, snapshot/restore MUST preserve signal values + selected UI state (focus/scroll) and render parity. + +**Acceptance criterion:** +Snapshot/restore tests validate state equality and post-restore render hash equality. + +## R13 — iOS policy constraint + +iOS native MUST NOT download/execute new code post-review. + +**Acceptance criterion:** +Architecture documentation explicitly constrains iOS lazy behavior to init/assets only. + +## 2. Traceability Matrix (RTM) + +| Requirement | Planned tests (Phase 2) | Example(s) | Docs mapping | +|---|---|---|---| +| R1 | `render_headless_pixel_identity` | `gallery`, `counter` | `Architecture.md` §§5, 9 | +| R2 | `native_surface_only_no_widget_wrapping` (integration/design check) | `counter` (native), `todo` (native) | `Architecture.md` §§3.2, 4 | +| R3 | `web_single_surface_no_dom_widgets` | `web_kernel`, `web_feature_demo` | `Architecture.md` §§3.2, 12 | +| R4 | `wgpu_backend_selected_for_targets` | `counter`, `web_kernel` | `Architecture.md` §9 | +| R5 | `reactive_dependency_tracking`, `memo_lazy_recompute`, `effect_rerun_semantics` | `counter` | `Architecture.md` §6, `API.md` §3 | +| R6 | `ui_property_level_invalidation` | `todo`, `gallery` | `Architecture.md` §§6, 7 | +| R7 | `layout_determinism_fixed_constraints` | `gallery` | `Architecture.md` §8, `API.md` §5 | +| R8 | `render_command_order_stability`, `render_hash_stability` | `gallery`, `web_kernel` | `Architecture.md` §§5, 9 | +| R9 | `web_trunk_build_smoke` | `web_kernel` | `Architecture.md` §12, `MVP.md` MVP2 | +| R10 | `kernel_boot_without_feature_fetch`, `feature_fetch_on_trigger` | `web_kernel` + `web_feature_demo` | `Architecture.md` §12 | +| R11 | `event_phase_ordering`, `hit_test_determinism`, `focus_navigation_stability` | `counter`, `todo`, `gallery` | `Architecture.md` §11, `API.md` §4.3 | +| R12 | `resumable_snapshot_restore_state`, `resumable_post_restore_render_parity` | `gallery` (headless), `todo` | `Architecture.md` §14, `API.md` §11 | +| R13 | `ios_policy_design_assertion` (docs/architecture gate) | N/A | `Architecture.md` §13 | + +## 3. Test Naming and Evidence Rules + +- Each test case MUST include a requirement reference in code comments or test name metadata (e.g., `[R5]`). +- CI output SHOULD summarize pass/fail by requirement ID. +- Example validation notes SHOULD include which R# each demo validates. From fbf6bc524e02bac58f392037fd69cd8a97152bd8 Mon Sep 17 00:00:00 2001 From: Michael Grilo <4789416+michaelgrilo@users.noreply.github.com> Date: Tue, 10 Feb 2026 13:46:15 -0500 Subject: [PATCH 3/3] docs: add explicit LLM/app provenance disclosure --- docs/Architecture.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/Architecture.md b/docs/Architecture.md index 572aefa..38fe241 100644 --- a/docs/Architecture.md +++ b/docs/Architecture.md @@ -299,3 +299,15 @@ Before claiming full project completion, the workspace MUST satisfy: - fine-grained recompute behavior is demonstrated via tests + debug logs - headless pixel-stability tests pass - web demonstrates real lazy feature wasm loading + +## 18. Provenance and Authorship Disclosure + +This crate/workspace specification was generated by **GPT-5.2-Codex** via the OpenAI Codex app/agent workflow, based on user-provided requirements. + +Authorship roles for this repository SHOULD be interpreted as: + +- **Prompter/User**: product requirements, constraints, acceptance criteria, review direction +- **LLM Agent (GPT-5.2-Codex)**: generated documentation and implementation artifacts unless otherwise attributed + +If a human maintainer manually edits files later, those commits become additional human-authored contributions. +