fix: signal composition — stream derived signals, fix identity check, correct relay routing

- Register derived signals in _signalRegistry so _streamSync includes them
- Auto-sync all signals (source + derived) after flush() recomputes effects
- Fix Object.assign identity check: create new object so signal setter detects changes
- Change _connectStream receiver path from /signal/main to /stream/default
- Initialize stream state with {} instead of null to prevent crashes
- Emit StreamFrom bindings directly without double-wrapping in signal()

Verified: static build shows Count: 9, Doubled: 18 on composition page.
HMR interference with WebSocket connections is a separate issue.
This commit is contained in:
enzotar 2026-02-25 23:55:05 -08:00
parent e5ff612197
commit b0e7de3b2e
7 changed files with 699 additions and 67 deletions

View file

@ -1,4 +1,4 @@
# DreamStack — Use Cases & Vision
# DreamStack — Vision & Use Cases
> **Core insight**: DreamStack turns UI into a streamable binary protocol.
> Any `.ds` app becomes multiplayer with one line. No networking code. No database sync.
@ -22,7 +22,25 @@ composable, replayable, and physics-native.
---
## Fundamental UI Redesigns
## The Five Primitives
DreamStack's full vision combines five independent primitives into something
that doesn't exist yet:
| Primitive | Role |
|---|---|
| **DreamStack bitstream** | UI as a streamable signal protocol |
| **Iroh P2P** | Zero-infrastructure device mesh |
| **Local LLM** | On-device intelligence, fully private |
| **Solana NFT** | Ownership, identity, payments |
| **PgFlex** | Schemaless, real-time, zero-config database |
Each is powerful alone. Combined, they produce **a portable, tradeable,
self-sovereign software company — minted as a single NFT**.
---
## Part 1 — Fundamental UI Redesigns
### 1. Ambient UI — App Without a Device
@ -41,6 +59,23 @@ and renders at its own resolution and frame rate.
**Why it matters**: Apps stop being "installed things" and become ambient
services that appear on whatever screen is nearest.
#### Universal Receiver Spectrum
The simplest device that can accept a DreamStack stream needs only:
a socket client, 16-byte header parsing, and any output surface.
| Device | What it consumes | Cost |
|--------|-----------------|------|
| **ESP32 + LED** | Signal frames only (`0x30`/`0x31`) — ~20 lines MicroPython | ~$4 |
| **Raspberry Pi + e-ink** | Signal frames → text render | ~$15 |
| **Any browser tab** | All 6 frame types (zero-framework HTML receiver) | Free |
| **Phone / tablet** | All frames + touch/pointer input back | Free |
| **Smart display** | Full bidirectional + physics | ~$100 |
A signal-mode stream sending `{"brightness": 0.7}` is trivially parseable
even on a microcontroller. The source decides the *meaning*, the receiver
decides the *manifestation*.
### 2. Time-Travel Interfaces
Every signal diff is a timestamped binary frame. Record the stream →
@ -55,8 +90,6 @@ into a new interactive session.
- **Analytics**: Replay user sessions to understand behavior (not heatmaps —
actual interactive replays)
**Why it matters**: Every app gets "save game" for free.
### 3. UI Composition via Signal Mixing
```
@ -84,8 +117,6 @@ interactive UI appears. Close the tab, it's gone.
- Updates are instant — the source changes, all viewers see it immediately
- Works on any device with a bitstream receiver
**Why it matters**: Distribution friction drops to zero.
### 5. Physics-Native Interaction
With `ds-physics` baked into the language, UI elements aren't positioned —
@ -99,89 +130,497 @@ they're simulated.
The physics state streams like any other signal. Multiple users can
interact with the same physics world simultaneously.
**Why it matters**: Interfaces feel *physical* instead of mechanical.
---
## Part 2 — Iroh P2P: Zero-Infrastructure Streaming
Adding Iroh P2P to the bitstream protocol removes the relay server entirely
and opens use cases that centralized infrastructure cannot serve.
| | WebSocket Relay | WebRTC | **Iroh P2P** |
|---|---|---|---|
| Discovery | Needs known URL | Needs signaling server | **Content-addressed (hash)** |
| NAT traversal | N/A (server) | ICE/STUN/TURN | **Built-in holepunching** |
| Topology | Star (relay center) | Peer pairs | **Mesh / swarm** |
| Offline-first | ❌ | ❌ | **✅ (local-first sync)** |
| Identity | URL-based | Session-based | **Cryptographic (public key)** |
| Persistence | Relay must stay up | Connection must stay open | **Content persists in network** |
### Key Use Cases
**Zero-Infrastructure Streaming** — A teacher in a classroom with no internet
runs a live `.ds` lesson to 30 student tablets over LAN via mDNS discovery.
No server. No URL. No relay cost.
**Persistent Bitstream Archives** — A recorded bitstream session becomes a
content hash. Anyone with the hash fetches and replays it from the swarm.
Bug reports, tutorials, session replays — all just 32-byte hashes. No replay server.
**Mesh Multiplayer** — N peers form a gossip mesh instead of routing through a
star topology. If one person drops, the others keep going. Latency drops
(direct paths instead of relay bounce).
**Offline-First + Sync-on-Reconnect** — Device goes offline, accumulates signal
diffs locally, reconnects → Iroh syncs missing state automatically. Field workers,
IoT sensors, home displays — all work disconnected and reconcile on reconnect.
**Cryptographic Identity** — Every Iroh node has a public key. Zero-config auth:
only whitelisted keys can subscribe to your stream. No API keys, no OAuth.
---
## Part 3 — Local LLM: Intelligent Ambient UI
Combining DreamStack + Iroh + local LLM inference produces intelligent, private,
serverless computing that renders on any surface.
### The AI That Lives in the Room
A single device (NUC, Mac Mini, laptop) runs a local LLM and joins the Iroh mesh.
Every surface in the house receives its signal stream.
- Speak to **any screen** → voice input through the mesh → local LLM processes →
response streams as signal diffs to **every surface**
- Kitchen display shows the recipe. Phone shows the shopping list. TV shows the
tutorial video. **One inference, N renderers.**
- All private. Nothing leaves the network. Ever.
### Distributed Inference Swarm
Multiple devices in the mesh each run small models. The signal protocol
coordinates them:
- Phone runs a fast model for intent detection
- Desktop runs a large model for generation
- No orchestration server. Models discover each other via Iroh.
- **LLM inference becomes a mesh protocol.**
### Self-Organizing Smart Spaces
The LLM observes signal streams flowing through the mesh and adapts the UI:
- Sees calendar signals → "Meeting in 10 minutes" → pushes notification to watch
- Sees IoT sensor signals → generates natural-language summary → nearest display
- Learns patterns → proactively streams control signals
No IFTTT. No Home Assistant rules. The LLM **is** the automation engine,
DreamStack **is** the I/O bus.
### Private AI Tutor
Teacher runs a `.ds` physics simulation. Students interact via tablets (touch inputs
stream back through Iroh mesh). A local LLM watches each student's interaction
stream and generates personalized hints. The hints stream as signal diffs only to
that student's device.
**Adaptive tutoring. Zero cloud. Works offline in rural schools.**
### Conversational Database (with PgFlex)
The local LLM watches PgFlex's SSE change stream and builds understanding:
- "Top 3 products last week?" → LLM generates PostgREST query → PgFlex returns → DreamStack renders
- "Alert me when inventory < 50" LLM creates computed field + threshold SSE fires when triggered
- PgFlex's schemaless nature means the LLM can create new fields on the fly
- **The LLM becomes a database architect that responds to natural language**
| Without LLM | With Local LLM |
|---|---|
| Streams carry **data** | Streams carry **meaning** |
| Surfaces **render** signals | Surfaces **understand** signals |
| Users **configure** automations | The system **infers** intent |
---
## Part 4 — Content-Addressed UI Distribution
This is where all primitives converge into the most radical departure from
traditional software distribution.
### Two Layers of Hash
| Layer | What | Analogy |
|---|---|---|
| **App hash** | Compiled `.ds` code (static artifact) | Torrent of an executable |
| **Stream topic** | Live signal state (running instance) | Joining a live game server |
When you share `ds:bafk2bza...`, the receiver:
1. **Fetches the app code** from the swarm (content-addressed, cached, verified)
2. **Joins the live stream** from the source (signal diffs, real-time state)
You're not distributing a file. **You're distributing a running application.**
### Why It Matters
**Link rot / platform risk** — A content-addressed app cannot go offline unless
every peer disappears. No Vercel dependency. No AWS bill surprise. No domain expiration.
**Distribution without gatekeepers** — App Store takes 30%. A `.ds` app distributed
via hash has zero distribution cost and zero gatekeepers. The app doesn't need to be
"installed" — the receiver just decodes the bitstream.
**Instant, zero-trust sharing** — Today: URL → DNS → load JS → hydrate → render
(2-5 seconds). With hash: peer already has the bundle cached → stream connects →
UI appears (~100ms).
**Versioning is automatic** — v1.0 = hash `abc123`, v1.1 = hash `def456`. Both
exist simultaneously in the swarm. No deployment. No rollback scripts. Users can
pin a version. Developers publish updates without breaking existing users.
---
## Part 5 — Solana NFT: App Ownership On-Chain
A compiled `.ds` app is small — signal-mode apps are a reactive state graph plus
view declarations. Compiled, that's kilobytes. Small enough to store on-chain.
### The NFT Structure
```
┌─────────────────────────────────────────┐
│ Solana NFT (Metaplex Core) │
│ │
│ metadata: { name, creator, version } │
│ appData: <compiled .ds bytecode>
│ stream: <iroh topic hash>
│ dbConfig: <pgflex connection, encrypted
│ │
│ owner: 7xKXt... (your wallet) │
└─────────────────────────────────────────┘
```
The NFT contains:
1. **The compiled app**`.ds` bytecode in the AppData field
2. **The stream topic** — Iroh topic hash for the live instance
3. **The database config** — PgFlex connection (encrypted, owner-only)
4. **Ownership** — enforced by Solana consensus
### What Ownership Means
Today, owning an NFT means owning a JPEG pointer. Owning a DreamStack NFT means
owning a **running application**:
**The owner controls the source stream** — Only the wallet owner can publish signal
diffs to the stream topic. Transfer the NFT → the new owner becomes the source.
The app literally changes hands like a physical object.
**The NFT IS the deployment** — No Vercel. No domain. No hosting account.
`ds run sol:ADDRESS` → fetch bytecode from chain → boot the app → join the stream.
The app's existence is guaranteed by Solana's uptime, not yours.
**Versioning is on-chain** — Update = new transaction → AppData updates.
Full version history in the transaction log. Roll back = point to a previous
transaction's data.
### Apps as Tradeable Assets
A developer builds a `.ds` app. Mints it as an NFT. Sells it. The buyer owns and
operates the app. The developer gets royalties on secondary sales (Metaplex royalties).
Build once, earn forever.
### Access-Gated Streaming
The stream topic is public, but the source checks NFT ownership:
```
stream main on iroh {
gate: nft("NFT_ADDRESS") -- only NFT holders can connect
}
```
- Mint 100 "access pass" NFTs for a premium dashboard
- Each holder connects to the live stream
- Trade the access pass on the open market when done
- **Subscription replaced by asset ownership**
### Composable App Economy
Because `.ds` apps are signal graphs, they compose:
```
let data = stream from nft("NFT_A") -- a data feed
let viz = stream from nft("NFT_B") -- a visualization
-- Compose into a new app → mint as NFT_C
```
NFT_C depends on NFT_A and NFT_B. On-chain, this dependency is recorded.
When NFT_C earns revenue, royalties flow upstream automatically.
**An app supply chain with automatic revenue sharing, enforced by the blockchain.**
### The App Store Without Apple
| Traditional | DreamStack + Solana |
|---|---|
| Developer → App Store (30% cut) → User | Developer → Mint NFT (0.1 SOL) → User |
| Apple controls listing | Apps are permissionless |
| User pays subscription | User buys NFT (resellable) |
| App dies when company dies | App lives on-chain forever |
| No secondary market | Apps trade like assets |
---
## Part 6 — PgFlex: The Memory Layer
PgFlex solves the problem the other four primitives don't: **structured, queryable,
persistent state**. Bitstream signals are ephemeral. Iroh stores blobs but isn't a
database. The LLM reasons but doesn't remember. The NFT stores code, not data.
PgFlex is the **memory** of the system.
### Full-Stack Architecture
```
┌─────────────────────────────────────────────────────────┐
│ Solana NFT │
│ • bytecode • iroh topic • db config │
└──────────────────────┬──────────────────────────────────┘
┌──────────────────────▼──────────────────────────────────┐
│ Device (runs the app) │
│ │
│ DreamStack Runtime │
│ ├── Signal graph (reactive state) │
│ ├── Local LLM (inference) │
│ ├── Iroh P2P (stream to N surfaces) │
│ └── PgFlex client (persistent storage) │
│ │ │
│ PgFlex Stack │
│ ├── PostgreSQL 17 (data lives here) │
│ ├── PostgREST (zero-code REST API) │
│ ├── SSE Proxy (real-time change events) │
│ └── ElectricSQL (edge sync / offline) │
└─────────────────────────────────────────────────────────┘
```
### Self-Contained SaaS on an NFT
The NFT contains the entire stack config: app bytecode (DreamStack), stream topic
(Iroh), database schema (PgFlex — auto-discovered), LLM model reference.
Buy the NFT → `ds run sol:ADDRESS` → PgFlex boots with zero schema → the app writes
data → PgFlex auto-discovers fields → **fully functional SaaS with persistent data,
from a single NFT**.
You're not selling an app. You're selling a running business.
### Data Sovereignty by Default
PgFlex runs on YOUR device/server. The NFT says "this app stores data in PgFlex" but
doesn't dictate where. The buyer deploys their own PgFlex (`docker compose up`).
Their data never touches the developer's infrastructure.
- Buy a CRM-as-NFT → deploy PgFlex locally → all customer data stays on your server
- The app creator has zero access to your data
- **GDPR/HIPAA compliance by architecture, not by policy**
### PgFlex SSE → DreamStack Signal Bridge
PgFlex emits real-time change events via SSE (`pg_notify`). Bridge those directly
into DreamStack's signal graph:
```
let orders = pgflex.subscribe("orders")
view dashboard =
column [
text "New orders: {orders.length}"
each orders as order ->
text "{order.customer}: ${order.total}"
]
stream dashboard on iroh { topic: "dashboard" }
```
Database changes → PgFlex SSE → DreamStack signals → Iroh mesh → every screen
updates. **No API layer. No polling. No frontend state management.**
### Why Not Automerge / CRDTs?
CRDTs (like Automerge) solve concurrent uncoordinated writes with no central
authority. PgFlex doesn't need them because **no layer in this architecture
produces that problem**:
```
Layer Sync mechanism Conflicts?
────────────────────────────────────────────────────────
DreamStack signals Version map + diffs No — single source per stream
PgFlex writes PostgreSQL transactions No — single DB is authority
PgFlex reads ElectricSQL shapes No — read-only replication
Iroh P2P Content-addressed blobs No — immutable by hash
```
PgFlex's natural topology is **single-writer**: one PostgreSQL instance per
deployment (home, school, hospital). Within that deployment, PostgreSQL handles
write ordering via transactions. ElectricSQL handles read replication to edge
devices. SSE pushes change notifications. No write contention occurs.
For offline scenarios (two peers editing state while disconnected), the conflict
resolution happens **in the signal layer, not the database layer**:
```
Peer A ──signals──→ DreamStack merge ←──signals── Peer B
resolved state
PgFlex
(just persists)
```
DreamStack's signal protocol reconciles on reconnect (last-write-wins per signal,
using version maps). PgFlex materializes whatever the signal graph settles on.
Adding Automerge would be a redundant conflict resolution layer between two
systems that already don't produce conflicts.
> [!NOTE]
> If PgFlex-to-PgFlex replication across sites is ever needed, the answer is
> PostgreSQL logical replication + custom conflict handlers — not Automerge.
> Stay in the PostgreSQL ecosystem.
---
## Revenue-Generating Applications
### A. Relay-as-a-Service — "Liveblocks Competitor"
### Tier 1: High Conviction — Clear Buyer, Clear Budget
**Market**: $50M+ (Liveblocks $30M raised, PartyKit acquired by Cloudflare, Ably $50M raised)
#### A. Private AI Appliance for Regulated Industries
Sell the bitstream relay as hosted infrastructure:
- Developers add one `stream` line to their app
- Charge per concurrent connection / bandwidth
- Dashboard with analytics, channel management, auth
**Buyer**: Hospitals, law firms, financial advisors, government agencies
**Budget they already spend**: $50K-500K/year on HIPAA/SOC2-compliant cloud AI
**Edge over incumbents**:
- Binary protocol (10x smaller than JSON pub/sub)
- WebRTC auto-upgrade for low latency
- Signal-graph-aware (not generic pub/sub)
- Keyframe cache for instant late-join
**The product**: A box (Jetson/NUC) running local LLM + DreamStack + PgFlex. Plug into
the network. Every screen in the facility gets AI — medical record summaries on the
doctor's tablet, appointment prep on the wall display, billing codes on admin screen.
Zero data leaves the building.
**Ship fast**: The relay already works. Add auth, metering, a dashboard.
**Why they pay**: Compliance. They *can't* use ChatGPT for patient data. Local LLM
solves this. DreamStack solves multi-surface delivery. PgFlex stores patient data locally.
### B. Interactive Live Education — "Nearpod Killer"
**Revenue**: Hardware ($500-2000) + annual software license ($2K-10K/yr)
**Market**: Healthcare IT alone is $400B. Even 0.001% = $4M.
**Moat**: Multi-surface streaming. Competitors sell "local LLM in a box" but output
is one terminal. DreamStack makes it ambient.
**Market**: $100M+ (Nearpod sold for $650M, Pear Deck acquired by GoGuardian)
#### B. Live Education Platform
Teacher runs a `.ds` lesson — interactive physics simulation, math
visualizer, chemistry model. 30 students connect, see the same simulation,
can interact. Teacher sees who's clicking what.
**Buyer**: School districts, edtech resellers, corporate training departments
**Budget they already spend**: $5-15/student/year on Nearpod, Pear Deck, Kahoot
**Edge over incumbents**:
- Actual interactive simulations (WASM physics engine), not slides with polls
- Sub-frame latency via WebRTC
- Works offline — student can fork and explore independently
- *"Turn any simulation into a live classroom with one line of code"*
**The product**: Teacher runs interactive simulations on their laptop. Students connect
from any device — Chromebook, iPad, phone. Over LAN via Iroh when internet is down.
Local LLM provides per-student adaptive hints.
### C. Collaborative Design/Prototyping
**Why they pay**: Current tools are glorified PowerPoint with polls. DreamStack
delivers actual interactive simulations + works offline (huge for rural schools).
**Market**: $1B+ (Figma worth $20B — multiplayer-by-default was the differentiator)
**Revenue**: $8/student/year (undercut Nearpod at $15)
**Market**: 50M US K-12 students × $8 = $400M TAM
**Moat**: Physics-native simulations over bitstream. Offline-via-Iroh is a killer
feature for school procurement.
A `.ds` file IS a running prototype. Multiple stakeholders view and interact
simultaneously. Signal diffs mean only changed state transmits.
### Tier 2: Strong Signal — Needs Validation
**Edge over Figma/Framer**:
- DreamStack prototypes are actual running apps, not static mockups
- They have physics, reactivity, routing
- They compile to production JS
- Same file = design AND implementation
#### C. Full-Stack NFT Marketplace
### D. IoT / Operations Dashboards
**Buyer**: Solo developers, indie SaaS builders, agencies
**Budget**: Currently $0 (new market) or $20-100/mo on hosting
**Market**: $10B+ (Grafana, Datadog, industrial IoT monitoring)
**The product**: `ds publish --solana` mints your app as a tradeable NFT. Buyers get a
complete, deployable, intelligent app with persistent storage. Developers earn royalties
on secondary sales.
Stream sensor data / device state to multiple monitoring screens.
Signal diffs mean minimal bandwidth. Keyframe cache means new operators
see current state instantly.
| What you sell | What the buyer gets | Price range |
|---|---|---|
| CRM NFT | App + PgFlex schema + LLM customer insights | 10-50 SOL |
| Analytics Dashboard NFT | Real-time dashboard + data layer + anomaly detection | 5-20 SOL |
| Inventory Tracker NFT | Multi-surface inventory + auto-reorder via LLM | 20-100 SOL |
| Custom App (commissioned) | Bespoke `.ds` app minted for one client | 100+ SOL |
**Edge**: Binary protocol is 10-100x more efficient than JSON polling.
Physics engine enables animated, organic data visualization.
**Revenue**: Transaction fees (2.5% of NFT sales) + pinning service
**Moat**: The NFT isn't a JPEG — it's a running application. Utility value, not speculative.
### E. Live Commerce / Interactive Shopping
#### D. DreamStack Registry (ds.run)
**Market**: $500B+ globally (live shopping market)
**The product**: `ds publish` → app gets a content hash. Anyone with the hash
fetches and runs it from the swarm.
A host demonstrates products. Viewers see the same interactive page —
rotate 3D models, change colors, see inventory in real-time. The host
controls the flow, viewers interact with products.
| Tier | What | Price |
|---|---|---|
| **Free** | Publish to swarm (ephemeral, lives as long as peers have it) | $0 |
| **Pin** | Permanent peers keep your app alive 24/7 | $5/app/month |
| **Pro** | Pin + custom domain + analytics + `ds.run` web gateway | $20/month |
| **Enterprise** | Private registry, SSO, audit log, SLA | $200/month |
**Edge**: Not a video stream with overlay buttons — a full interactive
app where the product page IS the stream.
The web gateway `ds.run/hash` opens the app in a browser via the WASM receiver.
Lets you share apps with people who don't have DreamStack yet.
**Moat**: The hash isn't just code — it includes the live stream topic. Pinned
apps are running, interactive, multiplayer applications. IPFS = static files.
Vercel = static sites. DreamStack Registry = live interactive apps as hashes.
#### E. Relay-as-a-Service
**Buyer**: SaaS developers building collaborative features
**Budget**: $0.10-1.00/concurrent connection/month on Liveblocks
**The product**: `npm install dreamstack` → add one `stream` declaration → instant
multiplayer. Hosted relay + Iroh P2P fallback.
**Revenue**: Usage-based, $0.05/connection/month
**Moat**: Signal-graph-aware sync (not generic pub/sub), binary efficiency,
P2P fallback = lower infrastructure cost
#### F. IoT / Operations Dashboard
**Buyer**: Factory floor managers, DevOps teams, operations centers
**Budget**: $15-50/user/month on Grafana Cloud, Datadog
**The product**: Sensor data streams via Iroh mesh to any screen. Local LLM adds
anomaly detection + natural language alerts. No cloud egress fees.
**Revenue**: $20/source/month
**Moat**: Binary efficiency + P2P = massive cost savings at scale
---
## Go-to-Market Sequence
```
Phase 1 (NOW → 3 months): Open-source the protocol + relay.
Ship the "Collaborative Step Sequencer" demo.
→ Developer attention + GitHub stars.
Phase 2 (3-6 months): Relay-as-a-Service (E).
→ First revenue, proves developer demand.
→ Low cost to operate (already built).
Phase 3 (6-12 months): Education Platform (B) + ds.run Registry (D).
→ Pick one school district, pilot it.
→ Add local LLM hints as differentiator.
→ Registry grows alongside open-source adoption.
Phase 4 (12-18 months): Private AI Appliance (A) + NFT Marketplace (C).
→ Partner with healthcare IT reseller.
→ Hardware margin + recurring license.
→ NFT marketplace leverages existing Solana ecosystem.
```
**Through-line**: DreamStack's moat is **multi-surface + binary efficiency + P2P**.
Every product must lean into all three. If a product only needs one, someone else
will eat your lunch.
---
## Demo Prioritization
| Demo | Effort | Wow Factor | Revenue Signal |
|------|--------|------------|---------------|
|------|--------|------------|----------------|
| Collaborative Step Sequencer | 1 day | ⭐⭐⭐⭐⭐ | Relay-as-a-Service |
| Two-Tab Synced Counter | 1 hour | ⭐⭐⭐ | — |
| Physics Playground | 2 days | ⭐⭐⭐⭐ | Education |
| Collaborative Whiteboard | 2 days | ⭐⭐⭐⭐ | Design Tool |
| ESP32 Signal Receiver | 1 day | ⭐⭐⭐⭐ | IoT / Ambient UI |
| Session Replay Viewer | 3 days | ⭐⭐⭐ | Analytics |
| NFT-Minted App + ds.run | 3 days | ⭐⭐⭐⭐⭐ | NFT Marketplace |
| Multi-Surface Dashboard | 1 week | ⭐⭐⭐⭐⭐ | IoT/Operations |
**Recommended first demo**: Collaborative Step Sequencer.
@ -189,6 +628,11 @@ It's visual, temporal, interactive, and multiplayer — proving the entire
stack in the most visceral way possible. Two browser tabs, one beat grid,
instant sync, zero networking code.
**Killer demo for NFT vision**: Publish a step sequencer as a content hash.
Share it in a tweet. Anyone who clicks the `ds.run/hash` link instantly joins
the live session — no account, no install, no server. 50 people making music
together from a 32-byte hash.
---
## Technical Foundation (Complete)
@ -203,4 +647,15 @@ All infrastructure is built and tested:
- ✅ **Compiler integration**: `stream` keyword, `stream from` expression,
`transport: webrtc` option
- ✅ **Receiver protocol**: All 6 frame types, RLE decode, exponential backoff
- ✅ **105 tests, 0 failures**
- ✅ **PgFlex**: Schemaless PostgreSQL, PostgREST, SSE, ElectricSQL — 110 tests
- ✅ **110+ tests, 0 failures**
### Primitives Still Needed
- ⬜ **Iroh P2P transport**: Replace WebSocket relay with Iroh mesh
- ⬜ **`ds publish`**: Mint `.ds` app as Solana NFT
- ⬜ **`ds run sol:ADDRESS`**: Fetch + boot from on-chain bytecode
- ⬜ **ds.run gateway**: Web gateway for content-addressed apps
- ⬜ **NFT-gated stream auth**: Check wallet ownership before allowing connection
- ⬜ **PgFlex signal bridge**: SSE → DreamStack signal graph
- ⬜ **Local LLM integration**: Model loading + signal-based I/O

View file

@ -97,6 +97,12 @@ impl JsEmitter {
self.emit_line(&format!("const {} = {};", node.name, js_expr));
continue;
}
// Check if it's a stream from — _connectStream returns a signal proxy
if matches!(expr, Expr::StreamFrom { .. }) {
let js_expr = self.emit_expr(expr);
self.emit_line(&format!("const {} = {};", node.name, js_expr));
continue;
}
self.emit_expr(expr)
} else {
"null".to_string()
@ -116,6 +122,8 @@ impl JsEmitter {
"const {} = DS.derived(() => {});",
node.name, js_expr
));
// Register derived signal so it's included in stream sync
self.emit_line(&format!("DS._registerSignal(\"{}\", {});", node.name, node.name));
}
}
SignalKind::Handler { .. } => {} // Handled later
@ -1475,6 +1483,10 @@ const DS = (() => {
for (const eff of effects) {
eff._run();
}
// After effects recompute derived signals, sync all values to stream
if (_streamWs && _streamWs.readyState === 1 && _streamMode === 'signal' && !_applyingRemoteDiff) {
_streamSync(_signalRegistry);
}
}
// ── Event System ──
@ -1961,7 +1973,19 @@ const DS = (() => {
}
function _connectStream(url) {
var state = signal(null);
// Auto-detect bare relay URL and append default receiver path
// e.g. "ws://localhost:9100" → "ws://localhost:9100/stream/default"
// Source connects at /peer/default, receiver subscribes at /stream/default
var _csUrl = url;
try {
var u = new URL(url);
if (u.pathname === '/' || u.pathname === '') {
_csUrl = url.replace(/\/$/, '') + '/stream/default';
}
} catch(e) {
// Not a valid URL — use as-is
}
var state = signal({});
var _csWs = null;
var _csReconnectDelay = 1000;
var _csStats = { frames: 0, bytes: 0, reconnects: 0 };
@ -1984,7 +2008,7 @@ const DS = (() => {
}
function _csConnect() {
_csWs = new WebSocket(url);
_csWs = new WebSocket(_csUrl);
_csWs.binaryType = 'arraybuffer';
_csWs.onopen = function() {
console.log('[ds-stream] Receiver connected:', url);
@ -2014,9 +2038,11 @@ const DS = (() => {
try {
var newState = JSON.parse(new TextDecoder().decode(pl));
if (type === 0x30) {
state.value = newState; // full replace
state.value = newState; // full replace — new object
} else {
state.value = Object.assign(state._value || {}, newState);
// Create a NEW object so the signal setter detects the change
// (Object.assign to existing object returns same ref, trips identity check)
state.value = Object.assign({}, state._value || {}, newState);
}
} catch(ex) {}
break;

View file

@ -796,18 +796,35 @@ impl Parser {
}
}
// Stream from
// Stream from — accept string URL or dotted identifier
TokenKind::Stream => {
self.advance();
self.expect(&TokenKind::From)?;
let source = self.expect_ident()?;
// Allow dotted source: `button.click`
let mut full_source = source;
while self.check(&TokenKind::Dot) {
self.advance();
let next = self.expect_ident()?;
full_source = format!("{full_source}.{next}");
}
// Accept string URL: `stream from "ws://localhost:9100"`
// or dotted ident: `stream from button.click`
let full_source = if matches!(self.peek(), TokenKind::StringFragment(_) | TokenKind::StringEnd | TokenKind::StringInterp) {
// Parse string literal and extract the raw text
let expr = self.parse_string_lit()?;
match &expr {
Expr::StringLit(s) if s.segments.len() == 1 => {
if let StringSegment::Literal(text) = &s.segments[0] {
text.clone()
} else {
return Err(self.error("stream from requires a plain string URL".into()));
}
}
_ => return Err(self.error("stream from requires a plain string URL".into())),
}
} else {
let source = self.expect_ident()?;
let mut full = source;
while self.check(&TokenKind::Dot) {
self.advance();
let next = self.expect_ident()?;
full = format!("{full}.{next}");
}
full
};
Ok(Expr::StreamFrom { source: full_source, mode: None })
}
@ -1321,4 +1338,52 @@ view counter =
other => panic!("expected Stream, got {other:?}"),
}
}
#[test]
fn test_stream_from_string_url() {
let prog = parse(r#"let remote = stream from "ws://localhost:9100""#);
match &prog.declarations[0] {
Declaration::Let(decl) => {
assert_eq!(decl.name, "remote");
match &decl.value {
Expr::StreamFrom { source, .. } => {
assert_eq!(source, "ws://localhost:9100");
}
other => panic!("expected StreamFrom, got {other:?}"),
}
}
other => panic!("expected Let, got {other:?}"),
}
}
#[test]
fn test_stream_from_dotted_ident() {
let prog = parse("let remote = stream from button.click");
match &prog.declarations[0] {
Declaration::Let(decl) => {
assert_eq!(decl.name, "remote");
match &decl.value {
Expr::StreamFrom { source, .. } => {
assert_eq!(source, "button.click");
}
other => panic!("expected StreamFrom, got {other:?}"),
}
}
other => panic!("expected Let, got {other:?}"),
}
}
#[test]
fn test_multiple_stream_from() {
let prog = parse(r#"let a = stream from "ws://localhost:9100"
let b = stream from "ws://localhost:9101""#);
assert_eq!(prog.declarations.len(), 2);
for decl in &prog.declarations {
match decl {
Declaration::Let(d) => {
assert!(matches!(d.value, Expr::StreamFrom { .. }));
}
other => panic!("expected Let, got {other:?}"),
}
}
}
}

View file

@ -0,0 +1,23 @@
-- Compose Search + Map widgets into one view
--
-- Run with:
-- Tab 1: dreamstack stream examples/widget-search.ds
-- Tab 2: dreamstack stream examples/widget-map.ds --port 9101
-- Tab 3: dreamstack dev examples/compose-search-map.ds --port 3001
let search = stream from "ws://localhost:9100"
let map = stream from "ws://localhost:9101"
view main =
row [
column [
text "🔍 Search"
text "Query: {search.query}"
text "Results: {search.filtered}"
]
column [
text "📍 Map"
text "{map.label}"
text "{map.lat}, {map.lng}"
]
]

View file

@ -0,0 +1,28 @@
-- DreamStack Signal Composition Demo
-- Compose two independent widget streams into one dashboard.
--
-- Run with:
-- Tab 1: dreamstack stream examples/streaming-counter.ds
-- Tab 2: dreamstack stream examples/streaming-physics.ds --port 9101
-- Tab 3: dreamstack dev examples/compose-widgets.ds --port 3001
--
-- Open http://localhost:3001 to see both streams composed.
let counter = stream from "ws://localhost:9100"
let physics = stream from "ws://localhost:9101"
view main =
column [
text "📡 Composed Dashboard"
row [
column [
text "── Counter Widget ──"
text "Count: {counter.count}"
text "Doubled: {counter.doubled}"
]
column [
text "── Physics Widget ──"
text "Bodies: {physics.body_count}"
]
]
]

17
examples/widget-map.ds Normal file
View file

@ -0,0 +1,17 @@
-- Map Widget — renders a location marker, streams its signals
--
-- Run with:
-- dreamstack stream examples/widget-map.ds --port 9101
let lat = 37.7749
let lng = -122.4194
let label = "San Francisco"
stream map on "ws://localhost:9101" { mode: signal }
view map =
column [
text "📍 {label}"
text "Lat: {lat}"
text "Lng: {lng}"
]

18
examples/widget-search.ds Normal file
View file

@ -0,0 +1,18 @@
-- Search Widget — standalone, streams its signals
--
-- Run with:
-- dreamstack stream examples/widget-search.ds
let query = ""
let results = ["San Francisco", "San Jose", "San Diego", "Santa Cruz", "Sacramento"]
let filtered = filter(results, (r -> contains(lower(r), lower(query))))
stream search on "ws://localhost:9100" { mode: signal }
view search =
column [
text "🔍 Search"
input "" { bind: query, placeholder: "Type to filter..." }
for item in filtered ->
text item
]