chore: import upstream snapshot with attribution
regression / regression-shards (style-16-prod style-9-prod style-17-prod iframe-render-compat variables-prod mp4-h265-sdr, shard-4) (push) Has been cancelled
regression / regression-shards (style-4-prod style-11-prod style-2-prod animejs-adapter typegpu-adapter parallel-capture-regression, shard-5) (push) Has been cancelled
regression / regression-shards (style-7-prod style-8-prod style-10-prod css-spinner-render-compat webm-transparency mp4-h264-sdr webm-vp9, shard-3) (push) Has been cancelled
regression / regression-shards (sub-composition-video style-18-prod raf-ball-render-compat font-variant-numeric sub-comp-t0 sub-comp-id-selector, shard-7) (push) Has been cancelled
Windows render verification / Detect changes (push) Has been cancelled
Windows render verification / Preflight (lint + format) (push) Has been cancelled
Windows render verification / Render on windows-latest (push) Has been cancelled
Windows render verification / Tests on windows-latest (push) Has been cancelled
CI / Detect changes (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Fallow audit (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Producer: integration tests (push) Has been cancelled
CI / Producer: unit tests (push) Has been cancelled
CI / File size check (push) Has been cancelled
CI / Test: skills (push) Has been cancelled
CI / Skills: manifest in sync (push) Has been cancelled
CI / CLI: npx shim (macos-latest) (push) Has been cancelled
CI / CLI: npx shim (ubuntu-latest) (push) Has been cancelled
CI / CLI: npx shim (windows-latest) (push) Has been cancelled
CI / SDK: unit + contract + smoke (push) Has been cancelled
CI / Test: runtime contract (push) Has been cancelled
CI / Studio: load smoke (push) Has been cancelled
CI / Smoke: global install (push) Has been cancelled
CI / CLI smoke (required) (push) Has been cancelled
CI / Semantic PR title (push) Has been cancelled
Player perf / Detect changes (push) Has been cancelled
Player perf / Preflight (lint + format) (push) Has been cancelled
Player perf / player-perf (push) Has been cancelled
Player perf / Perf: drift (push) Has been cancelled
Player perf / Perf: fps (push) Has been cancelled
Player perf / Perf: parity (push) Has been cancelled
Player perf / Perf: scrub (push) Has been cancelled
Player perf / Perf: load (push) Has been cancelled
preview-regression / Detect changes (push) Has been cancelled
preview-regression / Preflight (lint + format) (push) Has been cancelled
preview-regression / Preview parity (push) Has been cancelled
preview-regression / preview-regression (push) Has been cancelled
regression / regression (push) Has been cancelled
regression / Detect changes (push) Has been cancelled
regression / Preflight (lint + format) (push) Has been cancelled
regression / regression-shards (hdr-regression style-5-prod style-3-prod mov-prores, shard-1) (push) Has been cancelled
regression / regression-shards (overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence portrait-edge-bleed, shard-6) (push) Has been cancelled
regression / regression-shards (style-13-prod style-6-prod vignelli-stacking gsap-letters-render-compat audio-mux-parity, shard-8) (push) Has been cancelled
regression / regression-shards (style-15-prod hdr-hlg-regression style-1-prod many-cuts vfr-screen-recording render-symlinked-assets, shard-2) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Docs / Validate docs (push) Has been cancelled
Sync skills to ClawHub / Publish changed skills (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:58:35 +08:00
commit 85453da49f
4031 changed files with 710987 additions and 0 deletions
+557
View File
@@ -0,0 +1,557 @@
---
title: "Edit Operations"
description: "The complete EditOp catalog for dispatch(), can(), and batch()."
---
Every mutation in the SDK is expressed as an `EditOp` — a plain data object with a discriminated `type` field. You can submit ops individually through `dispatch()`, validate them with `can()`, or group them into a single undo/persist step with `batch()`.
<Tip>
Every element op requires an explicit `target` (an `HfId` string or `HfId[]` array). There is no selection-implicit mutation — the SDK never reads the current selection to decide what to edit. The typed methods on `Composition` (such as `comp.setText()` and `comp.setStyle()`) are convenience sugar that construct and dispatch these same ops.
</Tip>
## dispatch, batch, and can
### dispatch
```typescript
comp.dispatch(op: EditOp, opts?: { origin?: unknown }): void
```
Applies `op` immediately. Emits a `patch` event, persists if an adapter is attached, and records a history entry. The optional `origin` is forwarded verbatim in the resulting `PatchEvent`; use it to label the source of the change (e.g. `"user"`, `"agent"`, or your own string constant).
```typescript
comp.dispatch(
{ type: "setStyle", target: "hf-title", styles: { color: "#FFD60A" } },
{ origin: "agent" },
);
```
### batch
```typescript
comp.batch(fn: () => void, opts?: { origin?: unknown }): void
```
Groups all `dispatch()` calls made inside `fn` into a single undo step, a single persist write, and a single `patch` event. Use `batch()` when several mutations belong together logically.
```typescript
comp.batch(() => {
comp.dispatch({ type: "setText", target: "hf-title", value: "Launch Day" });
comp.dispatch({ type: "setStyle", target: "hf-title", styles: { fontSize: "96px" } });
comp.dispatch({ type: "setTiming", target: "hf-title", start: 0.5, duration: 3 });
});
```
### can
```typescript
comp.can(op: EditOp): CanResult
```
Dry-runs `op` without mutating the document. Returns `{ ok: true }` when `dispatch(op)` would succeed, or `{ ok: false; code: string; message: string; hint?: string }` when it would be a no-op or error.
Use `can()` as a feature-detection gate before rendering controls or applying optional operations:
```typescript
const result = comp.can({
type: "setGsapTween",
animationId: "anim-1",
properties: { ease: "power3.out" },
});
if (result.ok) {
comp.setGsapTween("anim-1", { ease: "power3.out" });
} else {
console.warn(result.code, result.message);
}
```
Stable `code` values for `ok: false`:
| Code | Meaning |
|------|---------|
| `E_TARGET_NOT_FOUND` | The `target` hf-id does not exist in the document. |
| `E_NO_ROOT` | The document has no root element (empty HTML). |
| `E_NO_GSAP_TIMELINE` | Op requires a parsed GSAP timeline; parser engine not yet active. |
| `E_NO_GSAP_SCRIPT` | Op requires a GSAP `<script>` block; none found in the document. |
<Note>
Several Phase-3b GSAP ops (`addGsapTween`, `setGsapTween`, `removeGsapTween`, `addGsapKeyframe`, `setGsapKeyframe`, `removeGsapKeyframe`, etc.) return `{ ok: false, code: 'E_NO_GSAP_TIMELINE' }` from `can()` until the parser engine ships. `dispatch()` still applies those ops structurally even when `can()` returns false.
</Note>
See also: [`Composition`](/sdk/reference/composition) for the typed-method wrappers, [`Types`](/sdk/reference/types) for `CanResult` and `EditOp`.
---
## Element edits
These ops target one or more elements by explicit hf-id. `target` accepts a single `HfId` string or an `HfId[]` array; when an array is given the op is applied to each id individually within a single batch.
| `type` | Key fields | What it does |
|--------|-----------|--------------|
| `setStyle` | `target`, `styles` | Merges CSS inline styles. `null` values remove that property. |
| `setText` | `target`, `value` | Replaces the element's direct text content. |
| `setAttribute` | `target`, `name`, `value` | Sets or removes an HTML attribute. `null` removes it. Does not touch `style`, `class`, or `data-hf-*`. |
| `setTiming` | `target`, `start?`, `duration?`, `trackIndex?` | Updates one or more timing attributes (`data-start`, `data-duration`, `data-track`). Omitted fields are unchanged. |
| `setHold` | `target`, `hold` | Sets an elastic hold window; see `ElasticHold` shape below. |
| `moveElement` | `target`, `x`, `y` | Repositions the element by setting `data-x` / `data-y` (not CSS `left`/`top`). |
| `removeElement` | `target` | Removes the element and all its children from the document. Inverse of `addElement`. |
### setStyle
```typescript
comp.dispatch({
type: "setStyle",
target: "hf-card",
styles: {
borderRadius: "24px",
backgroundColor: "#1A1A1A",
color: null, // removes the color property
},
});
```
`styles` keys are camelCase property names, matching `CSSStyleDeclaration` convention.
### setText
```typescript
comp.dispatch({
type: "setText",
target: ["hf-headline", "hf-sub"],
value: "Coming soon",
});
```
### setAttribute
```typescript
comp.dispatch({
type: "setAttribute",
target: "hf-logo",
name: "src",
value: "/assets/logo-v2.png",
});
// Remove an attribute:
comp.dispatch({
type: "setAttribute",
target: "hf-video",
name: "autoplay",
value: null,
});
```
### setTiming
```typescript
comp.dispatch({
type: "setTiming",
target: "hf-title",
start: 1.5,
duration: 3,
trackIndex: 0,
});
```
### setHold
`hold` is an `ElasticHold` object:
```typescript
comp.dispatch({
type: "setHold",
target: "hf-badge",
hold: {
start: 2,
end: 5,
fill: "freeze", // "freeze" | "loop"
},
});
```
### moveElement
Sets the element's position via `data-x` / `data-y` attributes. Coordinates are in composition-space pixels.
```typescript
comp.dispatch({
type: "moveElement",
target: "hf-logo",
x: 120,
y: 48,
});
```
---
## Structure
These ops mutate document structure (add/remove/reorder elements, apply class-level styles, or change composition metadata). They do **not** take a `target` field in the same form as element edits.
| `type` | Key fields | What it does |
|--------|-----------|--------------|
| `addElement` | `parent`, `index`, `html` | Inserts an HTML fragment. Returns the minted hf-id via the typed `comp.addElement()` method. |
| `reorderElements` | `entries` | Sets inline `z-index` on one or more elements to reorder their stacking. |
| `setClassStyle` | `selector`, `styles` | Merges CSS rule styles for a class selector. `null` values remove properties. |
| `deleteAllForSelector` | `selector` | Removes all elements matching the CSS selector from the document. |
| `setCompositionMetadata` | `width?`, `height?`, `duration?` | Updates top-level composition dimensions and/or total duration. |
### addElement
`parent` is the hf-id of the parent element, or `null` to insert at the document body root. `index` is the zero-based sibling index (append if `>= childCount`). `html` must be a single-root HTML fragment and must not contain `<script>`.
```typescript
comp.dispatch({
type: "addElement",
parent: "hf-scene-1",
index: 2,
html: '<div class="clip" data-start="3" data-duration="2">New layer</div>',
});
```
Use the typed `comp.addElement(parent, index, html)` method to get back the minted hf-id.
### reorderElements
Each entry sets `z-index` on one element. Elements must be non-statically positioned for `z-index` to take effect — the caller must ensure `position` is set.
```typescript
comp.dispatch({
type: "reorderElements",
entries: [
{ target: "hf-bg", zIndex: 0 },
{ target: "hf-logo", zIndex: 10 },
{ target: "hf-text", zIndex: 20 },
],
});
```
### setClassStyle
```typescript
comp.dispatch({
type: "setClassStyle",
selector: ".caption",
styles: { fontSize: "14px", fontWeight: "600" },
});
```
### deleteAllForSelector
```typescript
comp.dispatch({
type: "deleteAllForSelector",
selector: ".debug-overlay",
});
```
### setCompositionMetadata
```typescript
comp.dispatch({
type: "setCompositionMetadata",
width: 1920,
height: 1080,
duration: 30,
});
```
---
## Variables
| `type` | Key fields | What it does |
|--------|-----------|--------------|
| `setVariableValue` | `id`, `value` | Sets a composition variable's current value by id. Value may be a string, number, boolean, `FontValue`, or `ImageValue`. Refuses to create an undeclared variable. |
| `declareVariable` | `decl` | Creates a new variable declaration, or fully replaces an existing one (type, label, default, and all other schema fields — not just the value). The only op that creates the schema entry from scratch. |
| `removeVariable` | `id` | Removes a variable's declaration entirely. Live `var.{id}` overrides and `data-var-*` DOM references are left untouched. |
```typescript
// Scalar variable
comp.dispatch({
type: "setVariableValue",
id: "brandColor",
value: "#6C5CE7",
});
// Font variable (object-valued — never a CSS string)
comp.dispatch({
type: "setVariableValue",
id: "brand-font",
value: {
name: "Inter",
source: "https://fonts.googleapis.com/css2?family=Inter:wght@400;700",
},
});
// Image variable (object-valued)
comp.dispatch({
type: "setVariableValue",
id: "hero-image",
value: {
url: "/assets/hero.jpg",
alt: "Product hero",
fit: "cover",
},
});
// Create a new variable declaration
comp.dispatch({
type: "declareVariable",
decl: {
id: "brandColor",
type: "color",
label: "Brand color",
default: "#6C5CE7",
},
});
// Remove a variable's declaration
comp.dispatch({ type: "removeVariable", id: "brandColor" });
```
---
## GSAP tweens
These ops add, edit, and remove GSAP tween entries in the composition's GSAP script block. They operate by `animationId` — a stable string identifier minted when a tween is created. Use `comp.addGsapTween()` or `addWithKeyframes` to mint a new id; the typed wrapper returns it directly.
<Note>
Several GSAP ops require the parser engine to be active. Until it ships, `can()` returns `{ ok: false, code: 'E_NO_GSAP_TIMELINE' }` for these ops. `dispatch()` still applies the op structurally.
</Note>
| `type` | Key fields | What it does |
|--------|-----------|--------------|
| `addGsapTween` | `target`, `tween` | Adds a new tween for a target hf-id. Returns the minted `animationId` via the typed method. |
| `setGsapTween` | `animationId`, `properties` | Partially updates an existing tween's `GsapTweenSpec` fields. |
| `removeGsapTween` | `animationId` | Removes the tween entirely. |
| `removeGsapProperty` | `animationId`, `property`, `from?` | Removes one animated property from a tween. `from: true` removes from `fromProperties`; otherwise removes from `toProperties` / `properties`. |
### addGsapTween
`tween` is a `GsapTweenSpec` object:
```typescript
comp.dispatch({
type: "addGsapTween",
target: "hf-title",
tween: {
method: "from",
position: 0,
duration: 0.8,
ease: "power3.out",
fromProperties: { opacity: 0, y: 40 },
},
});
```
`GsapTweenSpec` fields:
| Field | Type | Description |
|-------|------|-------------|
| `method` | `"from" \| "to" \| "fromTo" \| "set"` | GSAP tween method. |
| `position` | `number \| string` | Timeline position. Accepts numbers (seconds) or label-relative strings (`"intro+=0.5"`). |
| `duration` | `number` | Tween duration in seconds. |
| `ease` | `string` | GSAP ease string (e.g. `"power3.out"`). |
| `fromProperties` | `Record<string, unknown>` | Start-state properties (used by `from` and `fromTo`). |
| `toProperties` | `Record<string, unknown>` | End-state properties (used by `fromTo`). |
| `properties` | `Record<string, unknown>` | Animated properties for `to` tweens. |
| `repeat` | `number` | Repeat count (`-1` = infinite). |
| `yoyo` | `boolean` | Reverse on alternating repeats. |
| `stagger` | `number \| Record<string, unknown>` | Stagger config for multi-target tweens. |
### setGsapTween
Only the fields you supply are changed; omit any field to leave it unchanged.
```typescript
comp.dispatch({
type: "setGsapTween",
animationId: "anim-1",
properties: { ease: "elastic.out(1, 0.3)", duration: 1.2 },
});
```
### removeGsapProperty
```typescript
// Remove 'scale' from the to-properties of an existing tween
comp.dispatch({
type: "removeGsapProperty",
animationId: "anim-1",
property: "scale",
});
// Remove 'opacity' from the from-properties
comp.dispatch({
type: "removeGsapProperty",
animationId: "anim-2",
property: "opacity",
from: true,
});
```
---
## Keyframes
Keyframe ops work with tweens that use CSS `@keyframes`-style percentage arrays rather than a single `fromProperties`/`toProperties` shape.
| `type` | Key fields | What it does |
|--------|-----------|--------------|
| `setGsapKeyframe` | `animationId`, `keyframeIndex`, `position?`, `value?`, `ease?` | Updates one keyframe by index inside an existing keyframed tween. |
| `addGsapKeyframe` | `animationId`, `position`, `value` | Appends a new keyframe at a given percentage position. |
| `removeGsapKeyframe` | `animationId`, `percentage` | Removes the keyframe at a specific percentage. |
| `removeAllKeyframes` | `animationId` | Clears all keyframes from a tween, leaving the tween shell intact. |
| `convertToKeyframes` | `animationId`, `resolvedFromValues?` | Converts a `from`/`to`/`fromTo` tween into keyframe form. `resolvedFromValues` provides live computed values for the 0% stop. |
| `materializeKeyframes` | `animationId`, `keyframes`, `easeEach?`, `resolvedSelector?` | Writes a complete keyframe set to an existing tween, replacing any previous keyframes. |
| `addWithKeyframes` | `targetSelector`, `position`, `duration`, `keyframes`, `ease?` | Creates a new keyframed tween for the given CSS selector. Returns minted `animationId` via typed method. |
| `replaceWithKeyframes` | `animationId`, `targetSelector`, `position`, `duration`, `keyframes`, `ease?` | Atomically removes an existing tween and inserts a new keyframed tween. |
| `splitIntoPropertyGroups` | `animationId` | Splits a multi-property keyframed tween into one tween per animated property. |
| `splitAnimations` | `originalId`, `newId`, `splitTime`, `elementStart`, `elementDuration` | Splits one tween into two at `splitTime`. The second half gets `newId`. |
| `unrollDynamicAnimations` | `animationId`, `elements` | Converts a selector-targeted tween that matches multiple elements into per-element keyframe tweens. |
### materializeKeyframes
`keyframes` is an array of `{ percentage, properties, ease? }` objects:
```typescript
comp.dispatch({
type: "materializeKeyframes",
animationId: "anim-3",
keyframes: [
{ percentage: 0, properties: { opacity: 0, y: 30 } },
{ percentage: 100, properties: { opacity: 1, y: 0 }, ease: "power2.out" },
],
easeEach: "none",
resolvedSelector: "#hf-title",
});
```
### addWithKeyframes / replaceWithKeyframes
`position` is a number (seconds) — unlike `GsapTweenSpec.position`, label-relative strings are not accepted here.
```typescript
// Create a new keyframed tween:
comp.dispatch({
type: "addWithKeyframes",
targetSelector: "#hf-card",
position: 1.5,
duration: 0.6,
keyframes: [
{ percentage: 0, properties: { scale: 0.8, opacity: 0 } },
{ percentage: 100, properties: { scale: 1, opacity: 1 } },
],
ease: "back.out(1.7)",
});
// Replace an existing tween atomically:
comp.dispatch({
type: "replaceWithKeyframes",
animationId: "anim-4",
targetSelector: "#hf-card",
position: 1.5,
duration: 0.6,
keyframes: [
{ percentage: 0, properties: { scale: 0.9 } },
{ percentage: 100, properties: { scale: 1 } },
],
});
```
<Note>
After `replaceWithKeyframes`, position-derived tween IDs renumber. Re-query `comp.getElement(id).animationIds` to discover the new ID rather than assuming it matches the old one.
</Note>
`KeyframeSpec` fields:
| Field | Type | Description |
|-------|------|-------------|
| `percentage` | `number` | Keyframe stop position (0100). |
| `properties` | `Record<string, number \| string>` | CSS / GSAP properties at this stop. |
| `ease` | `string` | Ease applied from this stop to the next. |
| `auto` | `boolean` | GSAP endpoint flag — emitted as numeric `_auto: 1`. |
---
## Labels
GSAP timeline labels mark named positions (in seconds) in the master timeline. Labels are referenced in `GsapTweenSpec.position` as strings like `"intro"` or `"intro+=0.5"`.
| `type` | Key fields | What it does |
|--------|-----------|--------------|
| `addLabel` | `name`, `position` | Adds a named label at a timeline position (seconds). |
| `removeLabel` | `name` | Removes a named label. |
```typescript
comp.dispatch({ type: "addLabel", name: "intro", position: 0 });
comp.dispatch({ type: "addLabel", name: "outro", position: 8 });
// Tween positioned relative to a label:
comp.dispatch({
type: "addGsapTween",
target: "hf-cta",
tween: {
method: "from",
position: "outro-=0.5",
duration: 0.4,
fromProperties: { opacity: 0 },
},
});
comp.dispatch({ type: "removeLabel", name: "intro" });
```
---
## Arc paths
Arc path ops control the motion path of a GSAP tween — the curve along which an element travels.
| `type` | Key fields | What it does |
|--------|-----------|--------------|
| `setArcPath` | `animationId`, `config` | Creates or replaces the arc-path config on a tween. |
| `updateArcSegment` | `animationId`, `segmentIndex`, `update` | Updates one segment's curviness or control points. |
| `removeArcPath` | `animationId` | Removes the arc-path from a tween, reverting to a straight-line path. |
### setArcPath
`config` shape:
```typescript
comp.dispatch({
type: "setArcPath",
animationId: "anim-5",
config: {
enabled: true,
autoRotate: true, // boolean, or a number (degrees offset)
segments: [
{
curviness: 1.5,
cp1: { x: 200, y: -80 }, // first control point
cp2: { x: 400, y: 20 }, // second control point
},
],
},
});
```
| Field | Type | Description |
|-------|------|-------------|
| `enabled` | `boolean` | Whether the arc path is active. |
| `autoRotate` | `boolean \| number` | `true` = auto-rotate to follow path tangent; a number offsets the rotation by that many degrees. |
| `segments` | `Array<{ curviness?, cp1?, cp2? }>` | Per-segment path definition. `curviness` is a GSAP Bezier curviness value; `cp1`/`cp2` are control-point coordinates in composition space. |
### updateArcSegment
```typescript
comp.dispatch({
type: "updateArcSegment",
animationId: "anim-5",
segmentIndex: 0,
update: { curviness: 2, cp1: { x: 220, y: -100 } },
});
```