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:
parent
e5ff612197
commit
b0e7de3b2e
7 changed files with 699 additions and 67 deletions
563
USE_CASES.md
563
USE_CASES.md
|
|
@ -1,4 +1,4 @@
|
||||||
# DreamStack — Use Cases & Vision
|
# DreamStack — Vision & Use Cases
|
||||||
|
|
||||||
> **Core insight**: DreamStack turns UI into a streamable binary protocol.
|
> **Core insight**: DreamStack turns UI into a streamable binary protocol.
|
||||||
> Any `.ds` app becomes multiplayer with one line. No networking code. No database sync.
|
> 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
|
### 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
|
**Why it matters**: Apps stop being "installed things" and become ambient
|
||||||
services that appear on whatever screen is nearest.
|
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
|
### 2. Time-Travel Interfaces
|
||||||
|
|
||||||
Every signal diff is a timestamped binary frame. Record the stream →
|
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 —
|
- **Analytics**: Replay user sessions to understand behavior (not heatmaps —
|
||||||
actual interactive replays)
|
actual interactive replays)
|
||||||
|
|
||||||
**Why it matters**: Every app gets "save game" for free.
|
|
||||||
|
|
||||||
### 3. UI Composition via Signal Mixing
|
### 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
|
- Updates are instant — the source changes, all viewers see it immediately
|
||||||
- Works on any device with a bitstream receiver
|
- Works on any device with a bitstream receiver
|
||||||
|
|
||||||
**Why it matters**: Distribution friction drops to zero.
|
|
||||||
|
|
||||||
### 5. Physics-Native Interaction
|
### 5. Physics-Native Interaction
|
||||||
|
|
||||||
With `ds-physics` baked into the language, UI elements aren't positioned —
|
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
|
The physics state streams like any other signal. Multiple users can
|
||||||
interact with the same physics world simultaneously.
|
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
|
## 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:
|
**Buyer**: Hospitals, law firms, financial advisors, government agencies
|
||||||
- Developers add one `stream` line to their app
|
**Budget they already spend**: $50K-500K/year on HIPAA/SOC2-compliant cloud AI
|
||||||
- Charge per concurrent connection / bandwidth
|
|
||||||
- Dashboard with analytics, channel management, auth
|
|
||||||
|
|
||||||
**Edge over incumbents**:
|
**The product**: A box (Jetson/NUC) running local LLM + DreamStack + PgFlex. Plug into
|
||||||
- Binary protocol (10x smaller than JSON pub/sub)
|
the network. Every screen in the facility gets AI — medical record summaries on the
|
||||||
- WebRTC auto-upgrade for low latency
|
doctor's tablet, appointment prep on the wall display, billing codes on admin screen.
|
||||||
- Signal-graph-aware (not generic pub/sub)
|
Zero data leaves the building.
|
||||||
- Keyframe cache for instant late-join
|
|
||||||
|
|
||||||
**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
|
**Buyer**: School districts, edtech resellers, corporate training departments
|
||||||
visualizer, chemistry model. 30 students connect, see the same simulation,
|
**Budget they already spend**: $5-15/student/year on Nearpod, Pear Deck, Kahoot
|
||||||
can interact. Teacher sees who's clicking what.
|
|
||||||
|
|
||||||
**Edge over incumbents**:
|
**The product**: Teacher runs interactive simulations on their laptop. Students connect
|
||||||
- Actual interactive simulations (WASM physics engine), not slides with polls
|
from any device — Chromebook, iPad, phone. Over LAN via Iroh when internet is down.
|
||||||
- Sub-frame latency via WebRTC
|
Local LLM provides per-student adaptive hints.
|
||||||
- Works offline — student can fork and explore independently
|
|
||||||
- *"Turn any simulation into a live classroom with one line of code"*
|
|
||||||
|
|
||||||
### 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
|
### Tier 2: Strong Signal — Needs Validation
|
||||||
simultaneously. Signal diffs mean only changed state transmits.
|
|
||||||
|
|
||||||
**Edge over Figma/Framer**:
|
#### C. Full-Stack NFT Marketplace
|
||||||
- DreamStack prototypes are actual running apps, not static mockups
|
|
||||||
- They have physics, reactivity, routing
|
|
||||||
- They compile to production JS
|
|
||||||
- Same file = design AND implementation
|
|
||||||
|
|
||||||
### 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.
|
| What you sell | What the buyer gets | Price range |
|
||||||
Signal diffs mean minimal bandwidth. Keyframe cache means new operators
|
|---|---|---|
|
||||||
see current state instantly.
|
| 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.
|
**Revenue**: Transaction fees (2.5% of NFT sales) + pinning service
|
||||||
Physics engine enables animated, organic data visualization.
|
**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 —
|
| Tier | What | Price |
|
||||||
rotate 3D models, change colors, see inventory in real-time. The host
|
|---|---|---|
|
||||||
controls the flow, viewers interact with products.
|
| **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
|
The web gateway `ds.run/hash` opens the app in a browser via the WASM receiver.
|
||||||
app where the product page IS the stream.
|
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 Prioritization
|
||||||
|
|
||||||
| Demo | Effort | Wow Factor | Revenue Signal |
|
| Demo | Effort | Wow Factor | Revenue Signal |
|
||||||
|------|--------|------------|---------------|
|
|------|--------|------------|----------------|
|
||||||
| Collaborative Step Sequencer | 1 day | ⭐⭐⭐⭐⭐ | Relay-as-a-Service |
|
| Collaborative Step Sequencer | 1 day | ⭐⭐⭐⭐⭐ | Relay-as-a-Service |
|
||||||
| Two-Tab Synced Counter | 1 hour | ⭐⭐⭐ | — |
|
| Two-Tab Synced Counter | 1 hour | ⭐⭐⭐ | — |
|
||||||
| Physics Playground | 2 days | ⭐⭐⭐⭐ | Education |
|
| Physics Playground | 2 days | ⭐⭐⭐⭐ | Education |
|
||||||
| Collaborative Whiteboard | 2 days | ⭐⭐⭐⭐ | Design Tool |
|
| Collaborative Whiteboard | 2 days | ⭐⭐⭐⭐ | Design Tool |
|
||||||
|
| ESP32 Signal Receiver | 1 day | ⭐⭐⭐⭐ | IoT / Ambient UI |
|
||||||
| Session Replay Viewer | 3 days | ⭐⭐⭐ | Analytics |
|
| Session Replay Viewer | 3 days | ⭐⭐⭐ | Analytics |
|
||||||
|
| NFT-Minted App + ds.run | 3 days | ⭐⭐⭐⭐⭐ | NFT Marketplace |
|
||||||
| Multi-Surface Dashboard | 1 week | ⭐⭐⭐⭐⭐ | IoT/Operations |
|
| Multi-Surface Dashboard | 1 week | ⭐⭐⭐⭐⭐ | IoT/Operations |
|
||||||
|
|
||||||
**Recommended first demo**: Collaborative Step Sequencer.
|
**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,
|
stack in the most visceral way possible. Two browser tabs, one beat grid,
|
||||||
instant sync, zero networking code.
|
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)
|
## Technical Foundation (Complete)
|
||||||
|
|
@ -203,4 +647,15 @@ All infrastructure is built and tested:
|
||||||
- ✅ **Compiler integration**: `stream` keyword, `stream from` expression,
|
- ✅ **Compiler integration**: `stream` keyword, `stream from` expression,
|
||||||
`transport: webrtc` option
|
`transport: webrtc` option
|
||||||
- ✅ **Receiver protocol**: All 6 frame types, RLE decode, exponential backoff
|
- ✅ **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
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,12 @@ impl JsEmitter {
|
||||||
self.emit_line(&format!("const {} = {};", node.name, js_expr));
|
self.emit_line(&format!("const {} = {};", node.name, js_expr));
|
||||||
continue;
|
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)
|
self.emit_expr(expr)
|
||||||
} else {
|
} else {
|
||||||
"null".to_string()
|
"null".to_string()
|
||||||
|
|
@ -116,6 +122,8 @@ impl JsEmitter {
|
||||||
"const {} = DS.derived(() => {});",
|
"const {} = DS.derived(() => {});",
|
||||||
node.name, js_expr
|
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
|
SignalKind::Handler { .. } => {} // Handled later
|
||||||
|
|
@ -1475,6 +1483,10 @@ const DS = (() => {
|
||||||
for (const eff of effects) {
|
for (const eff of effects) {
|
||||||
eff._run();
|
eff._run();
|
||||||
}
|
}
|
||||||
|
// After effects recompute derived signals, sync all values to stream
|
||||||
|
if (_streamWs && _streamWs.readyState === 1 && _streamMode === 'signal' && !_applyingRemoteDiff) {
|
||||||
|
_streamSync(_signalRegistry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Event System ──
|
// ── Event System ──
|
||||||
|
|
@ -1961,7 +1973,19 @@ const DS = (() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function _connectStream(url) {
|
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 _csWs = null;
|
||||||
var _csReconnectDelay = 1000;
|
var _csReconnectDelay = 1000;
|
||||||
var _csStats = { frames: 0, bytes: 0, reconnects: 0 };
|
var _csStats = { frames: 0, bytes: 0, reconnects: 0 };
|
||||||
|
|
@ -1984,7 +2008,7 @@ const DS = (() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function _csConnect() {
|
function _csConnect() {
|
||||||
_csWs = new WebSocket(url);
|
_csWs = new WebSocket(_csUrl);
|
||||||
_csWs.binaryType = 'arraybuffer';
|
_csWs.binaryType = 'arraybuffer';
|
||||||
_csWs.onopen = function() {
|
_csWs.onopen = function() {
|
||||||
console.log('[ds-stream] Receiver connected:', url);
|
console.log('[ds-stream] Receiver connected:', url);
|
||||||
|
|
@ -2014,9 +2038,11 @@ const DS = (() => {
|
||||||
try {
|
try {
|
||||||
var newState = JSON.parse(new TextDecoder().decode(pl));
|
var newState = JSON.parse(new TextDecoder().decode(pl));
|
||||||
if (type === 0x30) {
|
if (type === 0x30) {
|
||||||
state.value = newState; // full replace
|
state.value = newState; // full replace — new object
|
||||||
} else {
|
} 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) {}
|
} catch(ex) {}
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -796,18 +796,35 @@ impl Parser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stream from
|
// Stream from — accept string URL or dotted identifier
|
||||||
TokenKind::Stream => {
|
TokenKind::Stream => {
|
||||||
self.advance();
|
self.advance();
|
||||||
self.expect(&TokenKind::From)?;
|
self.expect(&TokenKind::From)?;
|
||||||
|
// 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 source = self.expect_ident()?;
|
||||||
// Allow dotted source: `button.click`
|
let mut full = source;
|
||||||
let mut full_source = source;
|
|
||||||
while self.check(&TokenKind::Dot) {
|
while self.check(&TokenKind::Dot) {
|
||||||
self.advance();
|
self.advance();
|
||||||
let next = self.expect_ident()?;
|
let next = self.expect_ident()?;
|
||||||
full_source = format!("{full_source}.{next}");
|
full = format!("{full}.{next}");
|
||||||
}
|
}
|
||||||
|
full
|
||||||
|
};
|
||||||
Ok(Expr::StreamFrom { source: full_source, mode: None })
|
Ok(Expr::StreamFrom { source: full_source, mode: None })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1321,4 +1338,52 @@ view counter =
|
||||||
other => panic!("expected Stream, got {other:?}"),
|
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:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
23
examples/compose-search-map.ds
Normal file
23
examples/compose-search-map.ds
Normal 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}"
|
||||||
|
]
|
||||||
|
]
|
||||||
28
examples/compose-widgets.ds
Normal file
28
examples/compose-widgets.ds
Normal 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
17
examples/widget-map.ds
Normal 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
18
examples/widget-search.ds
Normal 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
|
||||||
|
]
|
||||||
Loading…
Add table
Reference in a new issue