dreamstack/DREAMSTACK.md

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 — 8 Rust crates, 205 tests, 51 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, streaming
  • game-breakout.ds — Breakout with 5 rows, collision, score, streaming
  • game-snake.ds — Snake game
  • game-reaction.ds — Reaction time test

Streaming:

  • streaming-counter.ds — Counter synced across tabs
  • streaming-clock.ds — Clock streamed to viewers
  • streaming-dashboard.ds — Real-time dashboard via relay
  • streaming-stats.ds — Signal stats viewer
  • streaming-mood.ds — Collaborative mood board
  • streaming-physics.ds — Physics scene streaming
  • streaming-webrtc.ds — P2P WebRTC streaming
  • streaming-receiver.ds — Generic stream receiver
  • pong-viewer.ds — Spectator view for Pong
  • game-viewer.ds — Generic game viewer

Audio:

  • step-sequencer.ds — 16-step drum machine with Web Audio API
  • beats-viewer.ds — Spectator view for step sequencer

UI Patterns:

  • counter.ds — 3-line counter
  • list.ds — Dynamic list with add/remove
  • todo.ds / todomvc.ds — TodoMVC
  • form.ds — Form with validation
  • router-demo.ds — Multi-page routing
  • dashboard.ds — Dashboard layout
  • component-gallery.ds — Component showcase
  • showcase.ds — Full feature showcase
  • project-manager.ds — Kanban-style project tracker
  • springs.ds — Spring physics animations

Language Features:

  • language-features.ds — Comprehensive syntax demo
  • refined-types.ds — Refinement type guards
  • import-demo.ds — Component imports
  • slot-demo.ds — Component children/slots
  • each-demo.dseach loop rendering
  • when-else-demo.ds — Conditional rendering
  • multi-action.ds / timer-multi-action.ds — Multi-statement handlers
  • callback-demo.ds — Callback props
  • physics.ds — Rapier2D physics scene
  • bench-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 33 statements share one setInterval

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:

  1. Connects to the relay via WebSocket
  2. Wraps signal mutations with _streamDiff() calls
  3. Sends versioned JSON diffs (conflict resolution via version counters)
  4. Filters to only broadcast output signals (skips internals like velocity)
  5. 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

  1. The compiler is the framework. Move work from runtime to compile time
  2. Reactivity is the default. Every value is live. You opt out, not in
  3. Effects are values. First-class, composable, interceptable
  4. Layout and animation are core. Not CSS bolt-ons or third-party libraries
  5. The editor and runtime are one. Bidirectional structural editing
  6. UI is data, all the way down. If you can't map over your UI, your abstraction is wrong
  7. 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 publishcargo install dreamstack
  • Self-hosted playground — deploy playground as web app