Reactive signals
One nested writable state bag whose individual leaves can be animated independently.
class PlaybackPanel extends Actor {
playback = reactiveSignal({
currentTime: 0,
selection: { start: 0, end: 0 },
markers: [{ frame: 30, label: "Beat" }],
});
}
Every nested field becomes a reactive scalar with its own set, tween and transition.
The shape is authored once; the leaves move on their own schedules.
Reading and writing leaves
// Read
this.playback.currentTime();
this.playback.selection.start();
// Write immediately
this.playback.currentTime.set(120);
// Tween one leaf
yield* this.playback.currentTime.tween(240, 0.5);
Arrays
Reactive arrays expose structural methods alongside the scalar ones:
this.playback.markers.push({ frame: 60, label: "Drop" });
this.playback.markers.replaceAt(0, { frame: 15, label: "Intro" });
this.playback.markers.removeAt(0);
Root transition
When several fields move together as one logical change, transition from the root rather than tweening leaves in parallel. The whole bag becomes a single authored event.
yield* this.playback.transition(
(progress, from) => ({
...from,
currentTime: lerp(from.currentTime, 240, progress),
selection: { start: 0, end: lerp(from.selection.end, 240, progress) },
}),
0.4,
);
Write rules
- Disjoint same-frame writes compose. Two writes to different paths at the same frame both land.
- Same-path same-frame animated writes fail loudly. Two animations competing for one leaf at one frame is ambiguous, so the runtime raises rather than picking a winner. That is intentional: a silent last-write-wins would make the ambiguity invisible until a seek produced the other answer.