Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Changelog

All notable changes to `@zablab/solar` are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.0] — chantier Solar action runner

### Added

- **`Patch.action` descriptor** on the wire protocol — Solar now
reconstructs dense patches locally rather than receiving them
frame-by-frame. Six built-in kinds : `count-up`, `curve-path`,
`text-reveal`, `stagger-group`, `reorder`, `mask-reveal`.
Patches without `action` flow through the existing
`transitions.ts` mapper unchanged — fully backward compatible.
- **`animate/action-runner.ts`** dispatcher + per-kind sub-runners
in `animate/runners/`. Unknown kinds raise
`UnknownActionKindError` ; hosts can register custom kinds via
`registerActionRunner(kind, fn)`.
- **`animate/flip.ts`** — single source of truth for FLIP. Solar's
`reorder` runner consumes it directly ; Prism's preview
flip-runtime imports it via `@zablab/solar/animate/flip`.
- **`animate/easing-resolver.ts`** — resolve `EasingRef` (string id or
inline spring) into a CSS easing string plus a `t → eased t`
function.
- **`PrismScene` public class** at `scene/prism-scene.ts` exposing
`mount`, `unmount`, `playAnimation`, `stopAnimation`, `on`/`off`,
`connectToOrion`, `disconnectFromOrion`, `setScene`. Lets any web
host run a Prism-authored scene without Pulsar, CEF, or Electron.
- **DOM binder** (`scene/binder.ts`) — one-way bindings via
`data-anim-path` / `data-anim-attr`.
- **Examples** : `examples/embed-vanilla/` (UMD `<script>` tag) and
`examples/embed-react/` (forwardRef wrapper).
- **Docs** : `docs/embed-on-website.md` (integration guide),
`docs/action-descriptors.md` (protocol reference).
- **Packaging** : multi-entry ESM (`dist/solar.js`,
`dist/animate/flip.js`), UMD bundle (`dist/solar.umd.js`),
ESM alias (`dist/solar.esm.js`), public types
(`dist/solar.d.ts`). `npm publish --dry-run` is green.

### Changed

- `react`, `react-dom`, `framer-motion` moved from `dependencies` to
`peerDependencies`. Hosts supply their own copies — Solar links
against them via `external` in both Vite configs.
- `package.json` `main` now points at the ESM bundle ; `unpkg` /
`jsdelivr` fields target the UMD build for CDN consumers.

### Backward compatibility

- The existing `mount()` API is unchanged. ADR 002 wire messages
(snapshot, delta, scene_changed) are unchanged. The
`Patch.action` field is additive ; legacy patches keep their
semantics.
- Once published, the `PrismScene` class surface is contract.
Breaking it requires a major bump per ADR 003.

## [0.1.1] — animation bundle field

- `RenderBundle.animations?` field carried through but not yet
dispatched (chantier animation-engine A8 + B-pivot).

## [0.1.0] — initial release (2026-05-02)

- Scene runtime bundle for the Zablab broadcast platform. Same
bundle in Pulsar CEF, Prism webview, and editor preview iframe.
21 changes: 16 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@ authoring.
| Prism live control panel | `control` | Scene + operator overlay |
| Editor preview iframe | `test` | Scene + adapter mocker + state inspector |

## Public API
## Three consumers, one API

The same bundle backs three hosts with two public entry points :

```ts
// Live host — Pulsar CEF browser source, Prism live control panel,
// editor preview iframe. Drives off Orion's WS state stream.
import { mount } from "@zablab/solar";

const handle = mount({
Expand All @@ -27,13 +31,20 @@ const handle = mount({
token: showToken,
mode: "broadcast",
});
```

```ts
// Web embed — any HTML page, no Pulsar / CEF / Electron. Drives off
// a Prism-exported sceneJson + named animations.
import { PrismScene } from "@zablab/solar";

// later
handle.setToken(rotatedToken);
handle.disconnect();
const scene = new PrismScene({ sceneJson });
scene.mount(document.querySelector("#container")!);
scene.playAnimation("Score Update", { score_to: 1891 });
```

The complete typed surface lives in `src/types/index.ts`.
Full integration guide : [`docs/embed-on-website.md`](./docs/embed-on-website.md).
Action descriptor reference : [`docs/action-descriptors.md`](./docs/action-descriptors.md).

## Status

Expand Down
163 changes: 163 additions & 0 deletions docs/action-descriptors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# Action descriptors — `Patch.action` reference

This document is the contract for the `Patch.action` extension of
the Solar wire protocol (`src/transport/protocol.ts`). It is the
authority Prism's compiler targets when producing the deltas Solar
consumes.

The descriptor model is **additive and backward-compatible**. A
patch without `action` flows through the existing
`animate/transitions.ts` mapper untouched. A patch with `action` is
handed to the dispatcher in `animate/action-runner.ts`, which routes
to the matching sub-runner based on `action.kind`.

Once `@zablab/solar` is published, the **shape of `ActionDescriptor`
and the names of `ActionKind` are public contract**. Adding a new
kind is a minor bump ; renaming or removing a kind is a major bump.

---

## 1. Shape

```ts
interface Patch {
path: string;
value: unknown;
transition?: Transition;
action?: ActionDescriptor;
}

interface ActionDescriptor {
kind:
| "count-up"
| "curve-path"
| "text-reveal"
| "stagger-group"
| "reorder"
| "mask-reveal";
params: Record<string, unknown>;
easing?: string | { stiffness: number; damping: number };
duration_ms?: number;
stops?: Array<{ at_pct: number; value: unknown; easing?: string }>;
curve?: {
anchors: Array<{
t_pct: number;
value: number;
in_tangent?: { dt: number; dv: number };
out_tangent?: { dt: number; dv: number };
}>;
sample_hz: 30 | 60;
};
child_selector?: {
kind: "index" | "all" | "css-selector";
value: number | string;
};
}
```

## 2. Supported kinds

### `count-up`

Numeric tween of a single leaf path.

| Param | Type | Default | Description |
| ---------- | ------ | ------- | ------------------------------------------ |
| `from` | number | `0` | Starting value. |
| `to` | number | `patch.value` if numeric, else `0` | Ending value. |
| `decimals` | number | `0` | Decimal places for intermediate writes. |

Writes to `patch.path` at every animation frame via the store.

### `curve-path`

Sample a Bézier-anchored curve and write each sample.

Reads `action.curve.anchors[]` and `action.curve.sample_hz`. Tangents
default to `{dt:0,dv:0}` (linear segments) when omitted.

### `text-reveal`

DOM-side staggered reveal of children matching the configured
selector (default `[data-anim-unit]`). Each child animates `opacity`
and `translateY` ; the runner additionally toggles
`data-anim-state="in"` so CSS can hook into the reveal lifecycle.

| Param | Type | Default | Description |
| ------------- | ------ | ------- | ------------------------------------ |
| `stagger_ms` | number | `30` | Delay between consecutive children. |
| `per_unit_ms` | number | `240` | Duration of each unit's animation. |
| `from_opacity`| number | `0` | Starting opacity. |
| `from_y` | number | `8` | Starting translateY in px. |

Resolution order for the target element : `[data-anim-path=<patch.path>]`
→ `[data-anim-id=<last segment>]` → the mount target itself.

### `stagger-group`

Generic flavour of `text-reveal` with different defaults
(`stagger_ms=60`, `per_unit_ms=320`, selector `[data-anim-child]`).

### `reorder`

FLIP-animated reorder of a list. Operating mode :

1. Capture FIRST positions of all `[data-flip-id]` children of the
target root.
2. Write `patch.value` (the new ordering) to the store. React
re-renders the list with the new order.
3. After one animation frame, measure LAST, INVERT each delta, PLAY
`translate(0,0)` via WAAPI.

The FLIP technique is provided by `@zablab/solar/animate/flip` —
Prism's preview consumes the same module directly so both runtimes
share a single FLIP implementation.

| Param | Type | Default | Description |
| ------------ | ------ | ------------------ | ------------------------------------ |
| `selector` | string | `[data-flip-id]` | Override the FLIP marker selector. |
| `duration_ms`| number | `action.duration_ms ?? 400` | Animation duration in ms. |

### `mask-reveal`

Animate the target's `clip-path` from a hidden state to fully
revealed.

| Param | Value | Default |
| ----------- | --------------------------------------------------------------------- | ---------------- |
| `direction` | `left-to-right` `right-to-left` `top-to-bottom` `bottom-to-top` `center-out` | `left-to-right` |

## 3. Easing

`action.easing` accepts :

- A string id resolved by `animate/easing-resolver.ts` :
`linear`, `ease-in`, `ease-out`, `ease-in-out`, `cubic-in`,
`cubic-out`, `cubic-in-out`.
- An inline spring config : `{ stiffness, damping }`. Approximated
as `cubic-bezier(0.22, 1, 0.36, 1)` for the CSS facet.

Omitting `easing` defaults to `cubic-out`.

## 4. Compatibility guarantees

- A `Patch` without `action` is rendered exactly as in Solar v0.1 —
the existing `transitions.ts` test suite remains green without
modification.
- Unknown `action.kind` raises `UnknownActionKindError` at dispatch
time so the host's `animation:error` event can surface the bad
payload.
- Hosts can register custom kinds via `registerActionRunner(kind, fn)`
; built-in kinds always take precedence (the registry overwrites
in-place).

## 5. Related modules

| Module | Purpose |
| ------------------------------------- | ----------------------------------------------- |
| `src/transport/protocol.ts` | Wire types — `Patch`, `ActionDescriptor`, … |
| `src/animate/action-runner.ts` | Dispatcher. |
| `src/animate/runners/*.ts` | Per-kind implementations. |
| `src/animate/flip.ts` | FLIP — shared with Prism preview. |
| `src/animate/easing-resolver.ts` | `EasingRef` → CSS string + `t → eased t`. |
| `src/scene/prism-scene.ts` | Public `PrismScene` class consuming all of above. |
Loading
Loading