From b5a399a9069ce8c780bcc1aa6636944e1b61dccd Mon Sep 17 00:00:00 2001 From: Hongbin Li Date: Mon, 20 Jul 2026 10:10:56 -0700 Subject: [PATCH 1/2] docs: add READMEs for timeline, editor, and tween packages Every package now has a README. Add package-level docs for @vosjs/timeline, @vosjs/editor, and @vosjs/tween matching the existing house style, and update the root README's Packages table to include @vosjs/tween and @vosjs/cli. Drop the CLI from the roadmap now that it has shipped. --- README.md | 4 +++- packages/editor/README.md | 41 ++++++++++++++++++++++++++++++++++ packages/timeline/README.md | 43 ++++++++++++++++++++++++++++++++++++ packages/tween/README.md | 44 +++++++++++++++++++++++++++++++++++++ 4 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 packages/editor/README.md create mode 100644 packages/timeline/README.md create mode 100644 packages/tween/README.md diff --git a/README.md b/README.md index 0fa127d..35250e7 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,9 @@ | [`@vosjs/core`](./packages/core) | [![npm](https://img.shields.io/npm/v/%40vosjs%2Fcore)](https://www.npmjs.com/package/@vosjs/core) | The engine: config compiler, validation schema (Zod), runtime template generator, addon registry, and types. | | [`@vosjs/elements`](./packages/elements) | [![npm](https://img.shields.io/npm/v/%40vosjs%2Felements)](https://www.npmjs.com/package/@vosjs/elements) | The element system: text / image / SVG / video / audio renderers for Three.js overlays, shipped as a typed ESM factory **and** an injectable IIFE bundle. | | [`@vosjs/timeline`](./packages/timeline) | [![npm](https://img.shields.io/npm/v/%40vosjs%2Ftimeline)](https://www.npmjs.com/package/@vosjs/timeline) | Deterministic timeline math for video editing: keyframe sampling, GSAP-compatible pure easings, source-time remapping (trim/split). | +| [`@vosjs/tween`](./packages/tween) | [![npm](https://img.shields.io/npm/v/%40vosjs%2Ftween)](https://www.npmjs.com/package/@vosjs/tween) | Records a GSAP-dialect timeline into a per-element tween IR (extract / edit / deterministically sample), delegating 1:1 to a real backend for live playback. | | [`@vosjs/editor`](./packages/editor) | [![npm](https://img.shields.io/npm/v/%40vosjs%2Feditor)](https://www.npmjs.com/package/@vosjs/editor) | Headless video-editor infrastructure: patch-based document store (undo/redo), live-edit classifier, editor bridge client, timeline view-model math. | +| [`@vosjs/cli`](./packages/cli) | [![npm](https://img.shields.io/npm/v/%40vosjs%2Fcli)](https://www.npmjs.com/package/@vosjs/cli) | Command line: render deterministic videos and stills from vos configs, headlessly — for your terminal, CI, or an AI agent. | > `three` and `gsap` are **optional peer dependencies** — you bring your own versions, and the engine never bundles them. @@ -76,7 +78,7 @@ Releases are managed with [Changesets](https://github.com/changesets/changesets) - **Plugin SDK** (`@vosjs/plugin-sdk`) — a unified `definePlugin()` contract so addons, element renderers, schema extensions, and codegen hooks can be contributed by third-party packages. - **Browser adapter** (`@vosjs/web`) — a Vite-friendly dynamic addon loader whose import map is plugin-contributed. -- **CLI & scaffolding** (`@vosjs/cli`, `create-vos`). +- **Scaffolding** (`create-vos`) — one-command project starters on top of [`@vosjs/cli`](./packages/cli). ## License diff --git a/packages/editor/README.md b/packages/editor/README.md new file mode 100644 index 0000000..370fd74 --- /dev/null +++ b/packages/editor/README.md @@ -0,0 +1,41 @@ +# @vosjs/editor + +> Headless editing infrastructure for vos compositions — a patch-based document store, the live-edit classifier, the editor-mode playback bridge, element-edit commit helpers, and timeline view-model math. + +[![npm](https://img.shields.io/npm/v/@vosjs/editor.svg)](https://www.npmjs.com/package/@vosjs/editor) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/vosjs/vos/blob/main/LICENSE) + +Part of [vos](https://github.com/vosjs/vos), the open visual operating system behind [vosso](https://vos.so). These are the shared mechanisms every vos editor needs, with **zero UI opinion**. Apps own their document schemas, lowerings, and all rendering; this package owns only the mechanics. Pairs with [`@vosjs/timeline`](../timeline) for the underlying time math and with the `@vosjs/core` playback bridge. + +## Install + +```bash +pnpm add @vosjs/editor +``` + +## What it gives you + +- **`createProjectStore`** — a patch-based document store (Immer) with undo/redo, drag coalescing, and a forward patch log. The app's document is the source of truth; lowered compositions are derived from it. +- **`classifyEdit`** — the live-edit tier classifier that keeps editing fast: a program-string change → warm `LOAD`, a data change → `SET_DATA`, a duration change → `SET_DURATION` (with `LOAD` fallback). Program-string equality is the structural hash. +- **`createEditorBridgeClient`** — the host-side client for the engine's editor-mode playback bridge: element hit-testing, bounds, and ephemeral property overrides for drag previews. +- **Element-edit commit helpers** — turn on-canvas drags into durable, undoable config patches (`nudgeElementRecipe`, `scaleElementRecipe`, `rotateElementRecipe`, `cssDeltaToDesign`, …). +- **Timeline view-model math** — px↔time mapping (`toPx`/`toTime`), ruler ticks, magnetic snapping, plus the `LaneAdapter` contract so apps can define their own timeline lanes. + +## Example + +```ts +import { createProjectStore, classifyEdit } from '@vosjs/editor' + +const store = createProjectStore({ doc: initialDoc }) + +store.apply((draft) => { + draft.duration = 12 +}) + +// decide how the running player should apply the change — no full reload for a data edit +const command = classifyEdit(prevLowered, nextLowered) +``` + +## License + +[MIT](https://github.com/vosjs/vos/blob/main/LICENSE) © vosso diff --git a/packages/timeline/README.md b/packages/timeline/README.md new file mode 100644 index 0000000..c1841e3 --- /dev/null +++ b/packages/timeline/README.md @@ -0,0 +1,43 @@ +# @vosjs/timeline + +> Deterministic time math for editable vos compositions — keyframe sampling, GSAP-compatible pure easings, and source-time remapping (trim / split / speed). + +[![npm](https://img.shields.io/npm/v/@vosjs/timeline.svg)](https://www.npmjs.com/package/@vosjs/timeline) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/vosjs/vos/blob/main/LICENSE) + +Part of [vos](https://github.com/vosjs/vos), the open visual operating system behind [vosso](https://vos.so). This is a pure evaluation **library**, not a document format: apps embed its value types (keyframes, segments, speed spans) inside their own project documents and evaluate them with the same functions on both sides of the player bridge. No DOM, no engine coupling, no RNG or wall-clock — every function is a pure function of its arguments, so `seek(t)` renders identically on the host and inside a running program. + +## Install + +```bash +pnpm add @vosjs/timeline +``` + +## What it does + +- **Keyframe sampling** — `sample()` interpolates a `KeyframeTrack` at any time with a resolved easing; `lerpArray` and `sortKeyframes` are the primitives underneath. +- **GSAP-compatible easings** — `EASINGS` / `resolveEase()` provide pure easing functions matching GSAP's names (including `css-bezier(…)` dialect eases), so a recorded animation samples the same curve the author saw. +- **Source-time remapping** — `mapTime`, `sourceToTimeline`, `totalDuration`, `rateAt`, `segmentRate`, and `splitBySpeed` remap between output time and source (footage) time across trims and speed changes. Editors keep spans anchored in source; the remap projects them into output. +- **Segment edits** — `trimSegment`, `splitSegments`, `removeSegment`, `normalizeSegments` operate on segment lists with a `MIN_SEGMENT_LENGTH` floor. + +## Two evaluation contexts + +The same math runs host-side (import from `@vosjs/timeline`) and inside a compiled program (inline the runtime string from `@vosjs/timeline/bundle`), so a timeline you edit in an app evaluates bit-identically when the program plays back. + +```ts +// host +import { sample, mapTime, resolveEase } from '@vosjs/timeline' + +const value = sample(track, t, resolveEase) +const sourceTime = mapTime(segments, outputTime) +``` + +```ts +// inside a program template +import { timelineRuntimeCode } from '@vosjs/timeline/bundle' +// inline timelineRuntimeCode into the render template; it exposes the same functions +``` + +## License + +[MIT](https://github.com/vosjs/vos/blob/main/LICENSE) © vosso diff --git a/packages/tween/README.md b/packages/tween/README.md new file mode 100644 index 0000000..a55fd76 --- /dev/null +++ b/packages/tween/README.md @@ -0,0 +1,44 @@ +# @vosjs/tween + +> Record a GSAP-dialect timeline into a structured, per-element tween IR — a recording facade that delegates 1:1 to a real tween backend while capturing the animation as data you can extract, edit, and deterministically sample. + +[![npm](https://img.shields.io/npm/v/@vosjs/tween.svg)](https://www.npmjs.com/package/@vosjs/tween) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/vosjs/vos/blob/main/LICENSE) + +Part of [vos](https://github.com/vosjs/vos), the open visual operating system behind [vosso](https://vos.so). vos configs author animation as GSAP-dialect `createTimeline` functions. This package lets that same authoring surface become **data**: the recorder is shaped like the slice of `gsap` those functions use, captures every tween as a `TweenSpec` (with GSAP position parameters resolved to absolute time), and — given a real `gsap` backend — delegates 1:1 so live playback is unchanged. + +## Install + +```bash +pnpm add @vosjs/tween +``` + +## The pieces + +- **Recorder** (`createTweenRecorder`, `RecordingTimeline`) — a `gsap`-shaped facade that records tweens as `TweenSpec`s. Pass a real `gsap` backend to delegate playback 1:1, or none to record only. +- **Extraction** (`extractTimeline`, `buildTracks`) — fold the recorded specs into per-target keyframe tracks (`TargetTrack` / `TimelineDoc`): the neutral model a per-element timeline editor edits. +- **Deterministic sampler** (`createSampler`) — evaluate a recorded timeline with no GSAP present. `seek(t)` becomes a pure function, which is what makes server rendering and frame-accurate export reproducible. +- **Spec player** (`createSpecPlayer`, `contextResolver`) — play a `TweenSpec[]` that lives in a document's data, resolving targets against a live context each frame (the timeline-as-data path). +- **Helpers** — `staggerOffsets`, `parseVars`, target tagging (`tagTarget`/`readTag`), and extraction-scope stubs (`makeElement`, `runCreateTimeline`, …). + +## Example + +```ts +import gsap from 'gsap' +import { createTweenRecorder, extractTimeline, createSampler } from '@vosjs/tween' + +// record a config's createTimeline against the facade (delegating to real gsap) +const recorder = createTweenRecorder({ backend: gsap }) +createTimeline(ctx, content, duration) // uses recorder as ctx.gsap + +// fold into per-element tracks, then sample deterministically — no gsap needed +const doc = extractTimeline(recorder.specs) +const sampler = createSampler(doc) +const frame = sampler.seek(1.5) +``` + +A subpath `@vosjs/tween/bundle` provides the sampler/spec-player runtime as an inlinable string for injection into a compiled program template. + +## License + +[MIT](https://github.com/vosjs/vos/blob/main/LICENSE) © vosso From 6bf7f3d6a7257b2c615fccacca4f476be855b1b5 Mon Sep 17 00:00:00 2001 From: Hongbin Li Date: Mon, 20 Jul 2026 10:33:39 -0700 Subject: [PATCH 2/2] docs: fix camera example and document lint + element types Fix the quick-start camera field (the schema discriminates on `preset`, not `type`), add the missing `@vosjs/core/lint` subpath to both the root and core tables, and document the linter and the three render-template modes in the core README. Add an element-types table to the @vosjs/elements README. --- README.md | 3 ++- packages/core/README.md | 16 +++++++++++++++- packages/elements/README.md | 10 ++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 35250e7..64419fb 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ import { compileVosConfig, vosConfigJsonSchema } from '@vosjs/core' const config = { version: 2, scene: { background: '#000' }, - camera: { type: 'perspective', position: [0, 0, 5] }, + camera: { preset: 'perspective', position: [0, 0, 5] }, // functions are authored as strings, compiled into executable code createContent: '(ctx) => { /* build your Three.js scene with ctx.THREE */ }', } @@ -58,6 +58,7 @@ The compiled template is an HTML/JS document you can render in an iframe, captur | `@vosjs/core/schema` | Zod schemas, validators, config migrations | | `@vosjs/core/addons` | Three.js addon / post-processing registry | | `@vosjs/core/extract` | Config extraction from LLM/text output | +| `@vosjs/core/lint` | Determinism + GSAP-dialect linters for configs | | `@vosjs/core/types` | Pure type definitions | ## Development diff --git a/packages/core/README.md b/packages/core/README.md index f042cea..2ab9078 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -37,7 +37,20 @@ vosConfigJsonSchema.parse(config) // validate const template = compileVosConfig(config) // → runnable template string ``` -The compiled template is an HTML/JS document you can render in an iframe, capture to video, or snapshot to an image. +The compiled template is a single HTML/JS document. One template powers all three modes — `generateRenderTemplate({ mode })` builds it for `playback` (interactive iframe), `capture-video` (frame-by-frame WebCodecs encode), or `capture-thumbnail` (single-frame snapshot) — so what you preview is exactly what you export. + +## Determinism & linting + +The engine owns a single master clock and every render is a pure function of time, so the same config produces the same frames anywhere. `@vosjs/core/lint` guards that contract before you render: + +```ts +import { lintVosConfig, hasDeterminismErrors } from '@vosjs/core/lint' + +const issues = lintVosConfig(config) +if (hasDeterminismErrors(issues)) throw new Error('non-deterministic config') +``` + +`lintVosConfig` flags non-deterministic patterns (`Date.now()`, `Math.random()`, wall-clock reads), and `lintVosDialect` checks the GSAP authoring dialect. ## Subpath exports @@ -49,6 +62,7 @@ The compiled template is an HTML/JS document you can render in an iframe, captur | `@vosjs/core/schema` | Zod schemas, validators, config migrations | | `@vosjs/core/addons` | Three.js addon / post-processing registry | | `@vosjs/core/extract` | Config extraction from LLM/text output | +| `@vosjs/core/lint` | Determinism + GSAP-dialect linters for configs | | `@vosjs/core/types` | Pure type definitions | ## License diff --git a/packages/elements/README.md b/packages/elements/README.md index 1bd6ab1..c7c49ae 100644 --- a/packages/elements/README.md +++ b/packages/elements/README.md @@ -13,6 +13,16 @@ Part of [vos](https://github.com/vosjs/vos), the open visual operating system be pnpm add @vosjs/elements three ``` +## Element types + +| Type | Renders as | +| --- | --- | +| `text` | A text mesh, with optional per-character `split` for staggered animation | +| `image` | An image plane (`src` loaded through the shared asset cache) | +| `svg` | A rasterized SVG plane | +| `video` | A video plane driven by the master clock | +| `audio` | A non-visual audio element, synced to the master clock (no mesh) | + ## Two entry points ### `@vosjs/elements` — typed ESM factory