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..756bc80 --- /dev/null +++ b/docs/API.md @@ -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 { /* 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)); +} +``` + +### 3.2 Derived State 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; +``` + +### 3.3 Batching and Scheduler + +```rust +pub fn batch(f: impl FnOnce()); + +pub trait Scheduler: Send + Sync { + fn schedule_microtask(&self, task: Box); + fn schedule_animation_frame(&self, task: Box); +} + +pub fn set_scheduler(scheduler: Box); +``` + +### 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; +``` + +## 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; +} +``` + +### 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, +} + +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 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; + 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]; +} +``` + +## 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; +} +``` + +## 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; + fn handle_event(request_bytes: &[u8]) -> Vec; +} +``` + +### 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, +} +``` + +## 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; + 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. diff --git a/docs/Architecture.md b/docs/Architecture.md new file mode 100644 index 0000000..38fe241 --- /dev/null +++ b/docs/Architecture.md @@ -0,0 +1,313 @@ +# Pulse UI Architecture (Phase 1 — DEFINE) + +## 1. Scope and Mission + +Pulse UI is a Rust-first, single-engine UI framework that MUST produce deterministic framebuffer UI output across: + +- iOS 26+ (native) +- Android 8+ (native) +- WebAssembly in Safari iOS 26+ and other modern browsers + +This document defines architecture only (DEFINE phase). It does not implement runtime behavior yet. + +## 2. Normative Terms + +This spec uses RFC keywords: + +- MUST / MUST NOT: absolute requirements +- SHOULD / SHOULD NOT: recommended defaults with rationale for exceptions +- MAY: optional + +## 3. Goals and Non-Goals + +### 3.1 Goals + +- 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"`. + +### 3.2 Non-Goals (MVP) + +- 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. + +## 4. Workspace Architecture + +Pulse UI uses a consistent `pulse_*` naming scheme: + +- `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) + +Examples: + +- `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: + +- `Signal` mutable source nodes +- `Memo` derived nodes with dependency capture during evaluation +- `Effect` side-effect nodes rerun after upstream invalidation + +Dependency edges MUST be captured dynamically when reads occur in active reactive contexts. + +### 6.2 Invalidation and Scheduling + +- 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. + +### 6.3 Debug Traceability + +A debug mode MUST emit structured tracing events for: + +- signal writes +- memo recomputes +- effect runs +- UI node/property invalidation edges + +Trace output SHOULD be consumable by tests and optional tooling. + +## 7. Retained UI Tree and Component Model (`pulse_ui_core`) + +### 7.1 Retained Tree + +The retained tree MUST own: + +- 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) + +### 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` + +Renderer submission MUST preserve command ordering semantics. + +### 9.2 Backend Policy + +- 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. + +### 9.3 Headless Golden Path + +A headless render path MUST produce RGBA buffers for deterministic verification (hash and/or image golden comparisons). + +## 10. Text System Plan (`pulse_text`) + +- 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. + +## 11. Input, Events, Hit Testing, Focus + +### 11.1 Input Coverage + +The platform model MUST cover: + +- pointer/touch +- keyboard +- focus traversal +- basic text input + +### 11.2 Routing + +Routing MUST define deterministic phase semantics (capture/target/bubble or equivalent). This architecture chooses capture/target/bubble. + +### 11.3 Hit Testing + +Hit testing MUST be derived from layout geometry and deterministic z-order policy. + +### 11.4 Focus + +Focus IDs MUST be stable and serializable for resumability. + +## 12. Web Lazy Wasm Architecture (`pulse_web`) + +### 12.1 Module Roles + +**Kernel wasm (always loaded at startup) MUST include:** + +- render loop +- input normalization +- router +- reactive store + retained tree owner +- feature loader/runtime bridge + +**Feature wasm modules (lazy) MUST include:** + +- route/feature-specific behavior +- exported initialization and event handling entrypoints + +### 12.2 Real Lazy Loading + +Feature wasm binaries MUST NOT be downloaded or instantiated at startup. +They MUST load only on demand (route change or first interaction). + +### 12.3 Message Contract + +Kernel-feature communication MUST use versioned serialized envelopes (`MsgV1`, `MsgV2`, ...). + +- Binary payloads SHOULD be preferred (CBOR/bytes) for larger messages. +- Small control messages MAY use compact JSON. + +### 12.4 Loader Lifecycle + +The web loader MUST support: + +- dynamic `import()` and/or fetch-based discovery +- `WebAssembly.instantiateStreaming` when supported +- explicit fallback path when streaming is unavailable + +### 12.5 wasm-bindgen Linked Module Splitting + +Build docs MUST describe `wasm-bindgen --split-linked-modules` usage for linked JS/snippet/module splitting and CSP-compatible asset serving via trunk. + +Note: linked-module splitting MUST NOT be misrepresented as splitting Rust wasm code into multiple codegen shards. + +## 13. Platform Policy Constraints + +- 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. + +## 14. Resumability Direction (`feature = "resumable"`) + +### 14.1 Snapshot Scope + +The resumable snapshot MUST include, at minimum: + +- reactive signal values +- selected UI state (focus, scroll) +- stable node/event IDs needed for restore continuity + +### 14.2 Snapshot Format + +A versioned binary format (CBOR or MessagePack) SHOULD be used. + +### 14.3 Restore Semantics + +Restore SHOULD avoid full recomputation where possible and MUST preserve correctness. + +MVP acceptance target: + +- restored state equals pre-snapshot state for covered fields, +- post-restore headless render output equals pre-snapshot output. + +## 15. Performance Budgets (Initial Targets) + +These are initial targets and MAY be revised in MVP milestones with measurement data: + +- 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. + +## 16. DTI Execution Rules + +- 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. + +## 17. Global Acceptance Criteria (for final completion, not Phase 1) + +Before claiming full project completion, the workspace MUST satisfy: + +- `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 + +## 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. + diff --git a/docs/MVP.md b/docs/MVP.md new file mode 100644 index 0000000..6e1b3da --- /dev/null +++ b/docs/MVP.md @@ -0,0 +1,95 @@ +# Pulse UI MVP Plan (Phase 1 — DEFINE) + +## 1. DTI Plan + +Pulse UI follows DEFINE → TEST → IMPLEMENT. + +- **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. + +## 2. Milestones + +## MVP0 — Reactive Core + Retained Tree + Render Commands + +### Scope + +- 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. + +### Acceptance checks + +- 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 + Input/Focus Foundations + +### Scope + +- 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`. + +### Acceptance checks + +- 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 + +- 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. + +### Acceptance checks + +- 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). + +## MVP3 — Resumable Snapshot/Restore (Feature-Flagged) + +### Scope + +- Add `feature = "resumable"` snapshot/restore APIs. +- Persist and restore signal values + selected UI state (focus/scroll). +- Validate post-restore render parity in headless mode. + +### Acceptance checks + +- Snapshot/restore state parity tests pass (R12). +- Post-restore render hash parity tests pass (R12, R8). + +## 3. Global Quality Gates + +Before project completion claim: + +- `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). + +## 4. Initial Performance Budgets (to be validated later) + +- 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. + +## 5. Risk Register (DEFINE) + +- 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. 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