- Add cliff.toml for git-cliff changelog generation (one-line entries,
no commit body dumps, improve/refine prefixes mapped)
- Add @changesets/cli config and README in .changeset/
- Add release.sh script with per-package version bumps from changesets,
changeset-driven per-crate changelog updates, and --all/--dry-run flags
- Switch all crates from workspace version to independent version = "0.1.0"
- Generate clean root CHANGELOG.md and per-crate CHANGELOGs with [0.1.0]
- Retag v1.0.0 → v0.1.0 to match actual crate versions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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
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.
- 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
- 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 '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
- Added /peer/{name} route to relay: all clients are equal peers
- handle_peer: binary broadcast to all other peers, catchup for late joiners
- Simplified runtime: single /peer/ WS replaces dual source+receiver
- _peerId: random 8-char ID prevents self-echo from broadcast
- _pid in each diff JSON, filtered in _applyRemoteDiff
- Fix URL routing: /source/default and /stream/default paths
- _streamDiff sends via both source WS and receiver WS (INPUT flag)
- Source rebroadcasts 0x31 diffs from receivers to all other receivers
- Echo loop guard prevents infinite rebroadcast