Added 'blocked' signal that checks frozen grid cells below active piece.
Collision check branches on py to inspect the correct grid row (13-19).
Gravity only drops when not blocked. Lock triggers on blocked state.
Freeze writes piece into correct row based on py (7 rows supported).
Added grid rendering for rows 13-15 (total 7 visible frozen rows).
Build output: 84KB
Refinement pass:
- DotAccess: unwraps Signal/Derived before field lookup
- UnaryOp: uses unification instead of manual matching
- Call: unifies each arg with param type, applies subst to return
- List: unifies all element types (not just first)
- If/else: unifies both branches, checks condition is Bool
- When/else: unifies body with else body, checks condition
- Match: unifies all arm types for consistency
- Assign: checks assigned value compatible with variable type
- ForIn: binds iteration variable from Array element type
Tests: 39 ds-types (up from 34), 164 workspace total, 0 failures
Type System Completion:
- Add unify() with occurs check for proper type variable binding
- Add apply_subst() to chase type variables through substitution map
- Add SignalInfo/SignalClass for graph-based signal classification
- Add check_program_with_signals() accepting optional signal graph data
- Push Dom effect handler scope automatically when checking view blocks
- Wire unification into BinOp, comparison, and logical operator inference
- Include List/Record literals in source signal heuristic
Tests: 34 ds-types tests (up from 11), 159 workspace total, 0 failures
- DREAMSTACK.md: rewritten with accurate counts (48 examples, 14
components, 136 tests), full CLI reference, architecture diagrams,
quick start guide, comparison table, and phased roadmap
- IMPLEMENTATION_PLAN.md: rewritten with all 10 phases showing
accurate completion status, current capabilities, and next steps
- BITSTREAM_INTEGRATION.md: updated test count (82 → 136)
- USE_CASES.md and STREAM_COMPOSITION.md: already current, unchanged
Compiler fixes:
- Component props with signal-dependent expressions now wrapped as
reactive getters (() => expr) at call site
- Component declarations handle fn-typed props via
{ get value() { return props.x(); } } for live reactivity
- Container style: prop wrapped in DS.effect when expr contains .value
- Timer merging: all same-interval 'every' statements grouped into
single setInterval with one DS.flush()
Breakout game improvements:
- Classic row order: blue top (far), red bottom (near paddle)
- All 5 rows have full collision detection (was only 2)
- Faster ball (4px/frame) and paddle (40px/keypress)
- Score/Lives badges now update in real-time
All 48 examples compile, 136 tests pass
Performance:
- All every N statements with same interval now merge into one setInterval
- Pong: 24 timers → 1, Breakout: 38 timers → 1 (single DS.flush per frame)
New examples:
- game-breakout.ds: brick-breaker with 5×10 colored bricks, keyboard, audio
- beats-viewer.ds: step sequencer spectator via relay
Fixes:
- _playTone/_playNoise early-exit when freq/duration <= 0
- Breakout score race: score+bounce checks before brick destruction
- Score sound effects in pong (220Hz/440Hz sawtooth)
All 48 examples compile, 136 tests pass
- New pong-viewer.ds: full visual court rendered from streamed state
- Extended container prop signal detection: js_val.contains('.value')
fixes DotAccess on stream proxies (game.value.p1y) not being reactive
- Verified two-tab relay demo: player → relay → viewer syncs in real-time
- All 46 examples compile, 136 tests pass
- Native DreamStack pong game with visual court (stack container),
CSS-positioned paddles/ball, 30fps game loop, AI tracking, auto-serve
- Parser: containers (column/row/stack) now support leading props
e.g. column { style: '...' } [children]
- Codegen: fixed signal detection for container layout props
(strip .value suffix for signal graph lookup)
- All 136 tests pass, 45 examples compile
Relay HTTP /meta endpoint:
- GET /meta → JSON with all channel stats
- GET /meta/{name} → JSON with specific channel stats, schema, current state
- Uses TCP peek to intercept raw HTTP before WS handshake
- CORS headers for browser access
Signal deduplication:
- _lastSentValues tracks last-sent value per signal
- JSON.stringify comparison skips unchanged values
- Prevents redundant WS frames from derived signals
Periodic auto-sync:
- Every 50 diff batches, source sends full SignalSync (0x30)
- Relay can compact its cache instead of accumulating infinite diffs
- Resets pending_signal_diffs in relay cache
All 57 relay tests pass. All 9 examples pass.
- New streaming-dashboard.ds: first example combining components + streaming
- 4 Card grid with Badge, Button, match, stream receivers
- Receives live data from counter, clock, stats streams
- Button callbacks (Refresh/Reset) work within stream context
- All 8 examples pass regression (47,799 bytes)
- Duplicate prop keys now merge into Block expressions
click: a then click: b → Block([a, b])
- All actions fire in one click (was silently overwriting)
- streaming-mood.ds upgraded to semicolons
- streaming-stats.ds upgraded to semicolons
- Browser-verified: Happy button fires 3 actions (mood+color+energy)
- All 7 examples pass regression
- Added can_be_pattern() with look-ahead disambiguation
- Ident only treated as pattern if followed by -> or (
- Keywords (row/column/when/each) correctly terminate match
- Match arms now support: "active" -> row [ Badge + text ]
- Siblings after match (text, button, row) no longer consumed
- Project Manager updated with rich row/Badge match arms
- All 6 existing examples pass regression
- Dashboard with metrics, match status, progress bars
- Task Manager with dynamic add/remove via push/remove
- Team directory with member cards and status badges
- Settings with toggle and about section
- 64,622 bytes, zero console errors, all 11 components used
- Home/Counter/Todos/About with hash navigation
- State persists across route changes
- Uses existing router infrastructure (no compiler changes)
- navigate keyword, matchRoute, _route signal
- Imports Card, Badge, Button from components/
- Counter with callback props, Greeting with when/else
- Mood match expression with Badge, Todo dynamic lists
- Full init → build flow verified (51,401 bytes, 0 errors)
- When/else inside slots: anchor parentNode is null during initial effect
Fixed with named effect function + requestAnimationFrame retry
- Match parser now terminates on ] } else tokens (works inside containers)
- Updated Progress/Badge components
- Added examples/showcase.ds: 5-section demo exercising all features
- Recursive file watching: catches imported file changes from registry/
- Auto-detect project root: walks up to find registry/ or examples/ dir
- Auto-open browser on startup (xdg-open/open/cmd)
- Verified HMR live reload: 0ms recompile on file save
- AST: When(cond, body) -> When(cond, body, Option<else_body>)
- Parser: optional 'else -> expr' after when body
- Codegen: reactive DOM swap with anchor comments
- Signal graph + type checker updated for 3-arg When
- Component prop signal wrapping: .value compatible accessors
- Added examples/when-else-demo.ds
Codegen: BinOp::Div now emits Math.trunc(l / r)
- Clock displays 0:1:30 instead of 0.016:1.5:30
- Affects both emit_expr and predicate_to_js
Lexer: removed duplicate 'in' keyword mapping
- InKw at line 312 is canonical, removed old In at line 338
Examples: added project-tracker.ds (each loops + cards)
Add compose-metrics.ds (Layer 1): receives counter+clock+stats,
derives uptime/events/status, re-streams on /peer/metrics. This app
is BOTH a receiver and a source.
Add compose-master.ds (Layer 2): receives chained metrics from
Layer 1 + mood direct from Layer 0. Demonstrates multi-layer
signal composition with independent stream mixing.
Verified: Uptime: 51s, Total Events: 9 flowing through the full
three-layer chain to the master dashboard.
The 'default' relay channel accumulated stale Source connections from
previous sessions, causing frame delivery issues to Receivers on
/stream/default. Moving counter to an explicit /peer/counter channel
(matching clock, stats, mood pattern) fixes the composition dashboard.
All 4 streams now show live data: Count: 3, Doubled: 6.
Add 3 new streaming apps with explicit output declarations:
- streaming-clock.ds: output hours, minutes, seconds (tick private)
- streaming-stats.ds: output total, average, max (sum private)
- streaming-mood.ds: output mood, energy, color (clicks private)
Add compose-dashboard.ds that receives all 4 streams via unique
relay channels (/stream/default, /stream/clock, /stream/stats,
/stream/mood) into a single dashboard view.
Each app demonstrates selective signal registration — only declared
outputs are streamed, internal state remains private.
Add 'output: field1, field2' syntax to stream declarations to control
which signals are exposed over the relay. Only listed signals are
registered in _signalRegistry (and thus streamed). Omitting output
streams all signals (backwards-compatible).
Also strips internal sync metadata (_pid, _v) from receiver state
so composition consumers only see clean signal values.
Parser: parse comma-separated idents after 'output:' key
AST: Vec<String> output field on StreamDecl
Codegen: conditional _registerSignal, delete _pid/_v on receive
Example: stream counter on 'ws://...' { mode: signal, output: count, doubled }
- 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.
- _signalVersions: monotonic counter per signal, incremented on each local mutation
- Diffs include _v: {name: version} for version comparison
- _applyRemoteDiff only applies if remote version >= local version
- Prevents stale overwrites when both devices edit simultaneously
- onopen snapshot includes version map for late-joiner consistency
- bind: input handler now emits _streamDiff so typed text syncs
- Peers broadcast full state snapshot on WS open (late joiners sync)
- Removed dead _handleRemoteInput 0x31 case (old source/receiver model)
- Preserved scroll (0x50), pointer, and key handlers