- DREAMSTACK.md: rewritten with accurate counts (48 examples, 14 components, 136 tests), full CLI reference, architecture diagrams, quick start guide, comparison table, and phased roadmap - IMPLEMENTATION_PLAN.md: rewritten with all 10 phases showing accurate completion status, current capabilities, and next steps - BITSTREAM_INTEGRATION.md: updated test count (82 → 136) - USE_CASES.md and STREAM_COMPOSITION.md: already current, unchanged
19 KiB
DreamStack: Reinventing the UI from First Principles
What if we threw away every assumption about web UI frameworks and started over — with the sole goal of creating ultra-reactive, dynamic interfaces?
Implementation Status ✅
DreamStack is real and running — 7 Rust crates, 136 tests, 48 compilable examples, 14 registry components, ~7KB runtime.
.ds source → ds-parser → ds-analyzer → ds-codegen → JavaScript
↓ ↓
ds-types ds-layout
(type checker) (Cassowary solver)
What Works Today
| Feature | Syntax | Status |
|---|---|---|
| Signals | let count = 0 |
✅ Fine-grained, auto-tracked |
| Derived | let doubled = count * 2 |
✅ DS.derived(), lazy evaluation |
| Interpolation | "Count: {count}" |
✅ Reactive DS.effect() |
| Conditional | when count > 5 -> text "hi" |
✅ Mount/unmount |
| If/else | if x then a else b |
✅ |
| Match | match state | Loading -> ... |
✅ |
| List rendering | for item in items -> text item |
✅ Reactive, keyed |
| Components | component Card(title) = ... |
✅ Reactive props (getter fns) |
| Component import | import { Badge } from "./badge" |
✅ Recursive resolution |
| Effects | effect fetch(id): Result / perform |
✅ Algebraic |
| Springs | spring(target: 0, stiffness: 300) |
✅ RK4 physics |
| Layout | layout { sidebar.width == 250 } |
✅ Cassowary solver |
| Types | type PositiveInt = Int where x > 0 |
✅ Refinement types |
| Router | route "/path" -> body / navigate |
✅ Hash-based |
| Two-way binding | input { bind: name } |
✅ Signal ↔ input |
| Async resources | DS.resource() / DS.fetchJSON() |
✅ Loading/Ok/Err |
| Physics scene | scene { gravity_y: g } [ circle {...} ] |
✅ Rapier2D WASM |
| Constraints | constrain el.width = expr |
✅ Reactive solver |
| Streaming (signal) | stream on "ws://..." { mode: signal } |
✅ Bidirectional, versioned diffs |
| Streaming (receiver) | stream from "ws://..." |
✅ Auto-reconnect, field select |
| Streaming (WebRTC) | transport: webrtc |
✅ P2P data channel + WS fallback |
| Stream output filter | output: paddleX, ballY, score |
✅ Skip internal signals |
| Dev server | dreamstack dev app.ds |
✅ File watcher + HMR polling |
| Playground | dreamstack playground |
✅ Monaco editor + live preview |
| CLI check | dreamstack check app.ds |
✅ Signal graph visualization |
| TSX converter | dreamstack convert component.tsx |
✅ React → DreamStack |
| Component registry | dreamstack add badge |
✅ 14 components |
| Timer merging | every 33 -> ... (multiple) |
✅ Single setInterval per interval |
| Sound | play_tone(440, 60) |
✅ Built-in oscillator |
CLI Commands
dreamstack build app.ds # Compile to HTML+JS
dreamstack dev app.ds # Dev server with hot reload (port 3000)
dreamstack check app.ds # Analyze signal graph, type check
dreamstack stream app.ds # Compile + serve with streaming
dreamstack playground # Monaco editor playground (port 4000)
dreamstack add badge # Add a registry component
dreamstack add --list # List all available components
dreamstack convert file.tsx # Convert React/TSX → DreamStack
dreamstack convert button --shadcn # Convert from shadcn/ui registry
dreamstack init my-app # Initialize new project
Registry Components (14)
| Component | File | Description |
|---|---|---|
| Alert | alert.ds |
Status messages with variants |
| Avatar | avatar.ds |
Profile images with sizes |
| Badge | badge.ds |
Status indicators (success/warning/error/info) |
| Button | button.ds |
Click actions with variants |
| Card | card.ds |
Content containers |
| Dialog | dialog.ds |
Modal overlays |
| Input | input.ds |
Text inputs with binding |
| Progress | progress.ds |
Progress bars |
| Select | select.ds |
Dropdown selects |
| Separator | separator.ds |
Visual dividers |
| Stat | stat.ds |
Statistic displays (value, label, trend) |
| Tabs | tabs.ds |
Tabbed navigation |
| Toast | toast.ds |
Notification toasts |
| Toggle | toggle.ds |
On/off switches |
Examples (48 compilable .ds files)
Games:
game-pong.ds— Multiplayer Pong with keyboard, sound, streaminggame-breakout.ds— Breakout with 5 rows, collision, score, streaminggame-snake.ds— Snake gamegame-reaction.ds— Reaction time test
Streaming:
streaming-counter.ds— Counter synced across tabsstreaming-clock.ds— Clock streamed to viewersstreaming-dashboard.ds— Real-time dashboard via relaystreaming-stats.ds— Signal stats viewerstreaming-mood.ds— Collaborative mood boardstreaming-physics.ds— Physics scene streamingstreaming-webrtc.ds— P2P WebRTC streamingstreaming-receiver.ds— Generic stream receiverpong-viewer.ds— Spectator view for Ponggame-viewer.ds— Generic game viewer
Audio:
step-sequencer.ds— 16-step drum machine with Web Audio APIbeats-viewer.ds— Spectator view for step sequencer
UI Patterns:
counter.ds— 3-line counterlist.ds— Dynamic list with add/removetodo.ds/todomvc.ds— TodoMVCform.ds— Form with validationrouter-demo.ds— Multi-page routingdashboard.ds— Dashboard layoutcomponent-gallery.ds— Component showcaseshowcase.ds— Full feature showcaseproject-manager.ds— Kanban-style project trackersprings.ds— Spring physics animations
Language Features:
language-features.ds— Comprehensive syntax demorefined-types.ds— Refinement type guardsimport-demo.ds— Component importsslot-demo.ds— Component children/slotseach-demo.ds—eachloop renderingwhen-else-demo.ds— Conditional renderingmulti-action.ds/timer-multi-action.ds— Multi-statement handlerscallback-demo.ds— Callback propsphysics.ds— Rapier2D physics scenebench-signals.ds— Signal performance benchmarks
DreamStack vs React
| DreamStack | React | |
|---|---|---|
| Reactivity | Fine-grained signals, surgical DOM | VDOM diff, re-render subtrees |
| State | count += 1 direct |
setState(c => c+1) immutable |
| Derived | let d = count * 2 auto |
useMemo(() => ..., [deps]) manual |
| Effects | Auto-tracked, algebraic | useEffect(..., [deps]) manual |
| Conditional | when x -> text "y" |
{x && <span>y</span>} |
| Lists | for item in items -> ... |
{items.map(i => ...)} |
| Router | route "/path" -> body |
react-router (external) |
| Forms | input { bind: name } |
useState + onChange (manual) |
| Animation | Built-in springs | framer-motion (external) |
| Physics | Built-in Rapier2D scene | matter.js (external) |
| Layout | Built-in Cassowary | CSS only |
| Streaming | Built-in bidirectional | WebSocket libraries (external) |
| Types | Native refinement types | TypeScript (external) |
| Bundle | ~7KB | ~175KB |
| Ecosystem | 48 examples, 14 components | Massive |
Benchmarks (signal propagation)
| Benchmark | Ops/sec | Signals |
|---|---|---|
| Wide Fan-Out (1→1000) | 46K | 1,001 |
| Deep Chain (100 deep) | 399K | 100 |
| Diamond Dependency | 189K | 4 |
| Batch Update (50) | 61K | 50 |
| Mixed Read/Write | 242K | 10 |
Quick Start
Counter (3 lines of logic)
let count = 0
view counter =
column [
text "Count: {count}"
button "+" { click: count += 1 }
]
Multiplayer Pong (40 lines)
let ballX = 300
let ballY = 200
let bvx = 4
let bvy = 3
let p1y = 180
on keydown(ev) -> p1y = if ev.key == "ArrowUp" then p1y - 30 else p1y
every 33 -> ballX = ballX + bvx
every 33 -> ballY = ballY + bvy
every 33 -> bvx = if ballX > 590 then -4 else bvx
every 33 -> bvy = if ballY < 5 then 3 else bvy
stream pong on "ws://localhost:9100/peer/pong" {
mode: signal,
output: ballX, ballY, p1y
}
view pong =
stack { style: "position:relative; width:600px; height:400px; background:#1a1a2e" } [
column { style: "...ball styles...", top: ballY, left: ballX } []
column { style: "...paddle...", top: p1y } []
]
Spectator View (5 lines)
let game = stream from "ws://localhost:9100/peer/pong"
select [ballX, ballY, p1y]
view viewer =
stack { style: "..." } [
column { style: "...ball...", top: game.ballY, left: game.ballX } []
column { style: "...paddle...", top: game.p1y } []
]
Run It
# Install
cargo install --path compiler/ds-cli
# Build
dreamstack build examples/counter.ds -o dist
# Dev server (with hot reload)
dreamstack dev examples/game-pong.ds
# With streaming
cargo run -p ds-stream # Tab 1: start relay
dreamstack dev examples/game-pong.ds # Tab 2: play
open dist/index.html # Tab 3: spectate (pong-viewer.ds)
Architecture
Compiler Pipeline
┌─────────────┐
.ds source ───────►│ ds-parser │──► AST (Program)
└──────┬──────┘
│
┌──────▼──────┐
│ ds-analyzer │──► SignalGraph (DAG)
└──────┬──────┘ + DomBindings
│ + SignalManifest
┌──────▼──────┐
│ ds-codegen │──► Single-file HTML+JS
└──────┬──────┘ (~7KB runtime)
│
┌──────▼──────┐
│ ds-cli │──► dev/build/stream/check
└─────────────┘
Signal Graph (Compile-Time)
Signal: count (Source)
├──► Derived: doubled (DS.derived)
│ └──► [DOM] TextNode "Doubled: {doubled}"
├──► [DOM] TextNode "Count: {count}"
└──► [Stream] _streamDiff("count", count.value)
- No virtual DOM. Compiler maps signals → specific DOM nodes at build time
- No re-rendering. Components execute once, set up reactive bindings
- No dependency arrays. Compiler infers all dependencies from code
- Timer merging. All
every 33statements share onesetInterval
Reactive Runtime
// Source signal
const count = DS.signal(0);
// Derived signal (auto-updates)
const doubled = DS.derived(() => count.value * 2);
// Effect (auto-tracked, re-runs on dependency change)
DS.effect(() => { el.textContent = `Count: ${count.value}`; });
// Component props: getter functions for live reactivity
DS_Badge({ label: () => `Score: ${score.value}` });
Stream Protocol (16-byte binary header)
┌────────┬─────────┬──────────┬────────────┬───────┬────────┬────────┐
│ type │ flags │ seq │ timestamp │ width │ height │ length │
│ u8 │ u8 │ u16 │ u32 │ u16 │ u16 │ u32 │
└────────┴─────────┴──────────┴────────────┴───────┴────────┴────────┘
| Mode | What's Sent | Bandwidth |
|---|---|---|
signal (default) |
JSON diffs of changed signals | ~2 KB/s |
delta |
XOR + RLE compressed pixel deltas | ~50 KB/s |
pixel |
Raw RGBA framebuffer | ~30 MB/s |
Features:
- Bidirectional sync with per-signal version counters
- Output filtering — only listed signals are broadcast
- Exponential reconnect — 2s → 4s → 8s, max 30s, reset on success
- WebRTC data channel with WebSocket fallback
- Deduplication — skip unchanged values
Vision
React was revolutionary in 2013. But it carries a decade of compromises: the virtual DOM, hooks with manual dependency arrays, re-rendering entire subtrees, CSS from 1996, animations bolted on as an afterthought. DreamStack asks: what would we build today if none of that existed?
The answer is a unified system where UI is data, reactivity is automatic, effects are composable values, layout is constraint-based, animation is physics-native, and the editor and runtime are one.
The Language
Not Clojure. Not TypeScript. A new language that steals the best ideas from everywhere.
Core Properties
| Property | Inspiration | Why |
|---|---|---|
| Homoiconic | Clojure, Lisp | UI = data. Code = data. Everything is transformable |
| Refinement types | Liquid Haskell | type Percent = Int where value >= 0 and value <= 100 |
| Algebraic effects | Eff, Koka, OCaml 5 | Side effects as first-class, composable, interceptable values |
| Reactive by default | Svelte, Solid, Excel | No useState, no subscriptions. Assignment is the API |
| Structural typing | TypeScript, Go | Flexible composition without class hierarchies |
| Compiled to JS | Svelte, Solid | Near-zero runtime overhead |
Syntax Reference
-- Signals (mutable state)
let count = 0
let name = "world"
let items = [1, 2, 3]
-- Derived signals (auto-updated)
let doubled = count * 2
let greeting = "Hello, {name}!"
-- Refinement types
type PositiveInt = Int where value > 0
let score: PositiveInt = 10
-- Event handlers
on keydown(ev) -> count = if ev.key == "ArrowUp" then count + 1 else count
-- Timers
every 33 -> ballX = ballX + velocity
-- Conditional rendering
when count > 10 -> text "High!"
-- Pattern matching
match state
"loading" -> spinner
"error" -> text "Failed"
_ -> text "Ready"
-- For loops
for item, i in items -> text "{i}: {item}"
-- Components
component Card(title, children) =
column { variant: "card" } [
text title { variant: "card-title" }
slot
]
-- Imports
import { Badge, Card } from "../registry/components/badge"
-- Routes
route "/" -> text "Home"
route "/about" -> text "About"
-- Springs
let x = spring(target: 0, stiffness: 300, damping: 30)
-- Streaming
stream app on "ws://localhost:9100/peer/app" {
mode: signal,
output: count, name
}
-- Receiving streams
let remote = stream from "ws://localhost:9100/peer/app"
select [count, name]
-- Views
view main =
column [
text "Hello {name}"
button "Click" { click: count += 1 }
]
Effect System: Algebraic Effects
effect fetchUser(id: UserId): Result<User, ApiError>
view profile(id: UserId) =
let user = perform fetchUser(id)
match user
Loading -> skeleton-loader
Ok(u) -> column [ avatar u.photo, text u.name ]
Err(e) -> error-card e | with retry: perform fetchUser(id)
Layout: Constraint-Based
layout dashboard {
sidebar.width == 250
main.x == sidebar.x + sidebar.width
main.width == parent.width - sidebar.width [strong]
}
Uses a Cassowary constraint solver. Constraints are reactive — when viewport changes, layout re-solves in a single pass.
Animation: Physics-Based Springs
let panel_x = spring(target: 0, stiffness: 300, damping: 30)
on toggle_sidebar ->
panel_x.target = if open then 250 else 0
view sidebar =
panel { x: panel_x } [ nav-items ]
Springs are interruptible, gesture-driven, and composable through the same signal system.
Physics: Rapier2D WASM
let gravity_y = 980
view main =
scene { width: 700, height: 450, gravity_y: gravity_y } [
circle { x: 200, y: 80, radius: 35, color: "#8b5cf6" }
rect { x: 500, y: 100, width: 80, height: 50, color: "#10b981" }
]
button "Anti-Gravity" { click: gravity_y = -500 }
Streaming: Built-In Multiplayer
Any DreamStack app becomes multiplayer with one declaration:
stream pong on "ws://localhost:9100/peer/pong" {
mode: signal,
output: ballX, ballY, paddleY, score
}
The compiler automatically:
- Connects to the relay via WebSocket
- Wraps signal mutations with
_streamDiff()calls - Sends versioned JSON diffs (conflict resolution via version counters)
- Filters to only broadcast
outputsignals (skips internals like velocity) - Reconnects with exponential backoff on disconnect
Viewers connect with stream from:
let game = stream from "ws://localhost:9100/peer/pong"
select [ballX, ballY, paddleY, score]
Comparison
| Capability | React | Svelte 5 | Solid.js | Flutter | DreamStack |
|---|---|---|---|---|---|
| Reactivity | Pull (VDOM diff) | Runes | Signals | Rebuild | Compile-time DAG |
| Side effects | useEffect + deps |
$effect |
createEffect |
Lifecycle | Algebraic effects |
| Layout | CSS (external) | CSS (scoped) | CSS (external) | Constraints | Cassowary (native) |
| Animation | 3rd party | 3rd party | 3rd party | Physics (native) | Springs (native) |
| Streaming | WebSocket libs | WebSocket libs | WebSocket libs | None | Built-in bidirectional |
| Runtime | ~175KB | ~2KB | ~7KB | ~2MB | ~7KB |
| Type safety | TypeScript | TypeScript | TypeScript | Dart | Refinement types |
| UI = Data | No (JSX) | No (templates) | No (JSX) | No (widgets) | Yes (homoiconic) |
Design Philosophy
- The compiler is the framework. Move work from runtime to compile time
- Reactivity is the default. Every value is live. You opt out, not in
- Effects are values. First-class, composable, interceptable
- Layout and animation are core. Not CSS bolt-ons or third-party libraries
- The editor and runtime are one. Bidirectional structural editing
- UI is data, all the way down. If you can't
mapover your UI, your abstraction is wrong - Any bitstream in → any bitstream out. The UI is just one codec
Next Steps
Phase 1: Polish (estimated ~2h)
- Better error messages — source context with line + caret in parser errors
- Route integration test — multi-page example exercising
route+navigate - WebRTC integration test — verify P2P data channel transport
- Layout constraint test — example using Cassowary solver
Phase 2: New Examples (estimated ~2h)
- Tetris — stress-test grid, timers, collision, streaming
- Todo with sync — CRUD + bidirectional streaming sync
- Snake polish — cleanup existing game, add streaming
Phase 3: Ecosystem (estimated ~1.5h)
- Component gallery — live showcase of all 14 components
- Crates.io publish —
cargo install dreamstack - Self-hosted playground — deploy playground as web app