← Files PicsartARCHIVED FILE

docs/widget-specs/motion-setup.md

8.74 KB · Oct 10, 2026 · 18:02 UTC

↓ Download file

See the change to this file →

# Widget spec — `picsart_motion_setup`

> A **new** widget in `pa-ai-models-mcp-server/server/widgets/src/motion-setup/`, built and
> served like `film-setup` (its closest analog). Older drafts named this `mp_motion_setup`.

The setup console — a **Cascade-styled panel** where the designer sets the video-wide
motion frame: output format (ratio/resolution), fps, and energy. **Collecting these here,
in the widget, is the intended experience.** The format questions are good ones; what is
wrong is asking them in a bare Claude dialog. So this panel — not an the host's question interface
menu — is where they are answered. It opens **prefilled with inferred defaults** (ratio
from the frames' aspect, fps 30, energy read from the content) so the designer one-click
confirms rather than fills a form.

**Total duration defaults to "Derived from the motion"** — a checkbox **checked by default**,
so the length is the sum of the holds (each grown to fit its own animation) and nothing has
to be decided up front. A designer who wants a specific runtime **unchecks it and sets a
target**. That target is a *soft budget*: holds scale toward it where there is slack, but
never below each hold's motion-minimum — so a too-short target **lengthens** the video rather
than clipping an animation. Derive is the default because pacing should fall out of the
motion; the target is there for the designer who genuinely needs a fixed length.

**The standard Claude the host's question interface dialog is a degraded last resort only** — it appears
when this panel is not deployed (see the plugin's known blocker), and it is exactly the
off-brand experience this Cascade console exists to replace. A bare dialog in the transcript
means the panel has not shipped, not that a dialog was the right surface.

## Design system — Cascade

Rendered in the **Cascade design system**, re-expressed for the MCP iframe **exactly the way
the existing gen-ai widgets do it** — it reuses their `shared/foundation/themes.css` +
`shared/styles.css`, not a fresh port. The tokens (magenta primary `#C209C1`, the neutral
ramp, radii, status colors) live as a **host-color-independent, WCAG-AA-corrected** local
layer: never a host `--color-*` token (MCP hosts inject their own and some are not
contrast-safe), and both `[data-theme="dark"]` and `@media (prefers-color-scheme: dark)` are
mapped for parity. Body background transparent; the type face is **Inter**
(`--font-sans`/`--font-display`) — the same as `film-setup` and the other gen-ai widgets,
**not Gilroy**, and no external font host is referenced. This is what makes the panel read as
native Picsart rather than a generic dialog — the whole point of using it instead of
the host's question interface.

## Tool input

```ts
{
  frames?: Array<{ id: string, url: string, w?: number, h?: number }>,  // for the ratio inference + strip
  suggested?: {                       // your inferences from the material, each with a one-line why
    selections?: Partial<Selections>  // seeded silently; the console validates each id and drops unknowns
  },
  current?: Selections                // previously locked state; always wins over `suggested`
}
```

`suggested` is how the console opens on the material rather than on its defaults: pass
the ratio the frames already are, a hold that fits their count against a sensible
runtime, and an energy read from the design. A wrong prefill reads to the user as a
decision someone already made — so pass only what the material actually decides.
`current` (a re-open) always beats `suggested`.

## Selections

```ts
type Selections = {
  ratio: '9:16' | '16:9' | '1:1' | '4:3' | '3:4',   // the compositor/model-safe five
  fps: 24 | 30 | 60,
  hold: number,                 // default seconds each frame holds; per-frame override lives on the frame-board
  transition: string,           // default seam effect id (see references/TRANSITIONS.md); '' = hard cut
  transitionDuration: number,   // default seam window in seconds
  energy: 'calm' | 'standard' | 'energetic',  // global motion register: eases + transition intensity
  duration: { mode: 'derived' | 'target', seconds?: number }
    // 'derived' (DEFAULT, box checked): total = sum of holds, each ≥ its motion.
    // 'target': a soft length; holds scale toward `seconds` but never below their motion-minimum.
}
```

- **`ratio` constrains more than the look** — it decides which frames get letterboxed
  vs. filled, and (in Phase 2) which video models are usable. The five listed render
  everywhere; keep scope crops (2.39, 21:9) out — shoot 16:9 and crop in the edit.
- **`energy` is an inferred register, never a question.** `calm` → softer eases; `energetic`
  → snappier eases and punchier default transitions. You **infer it from the design** (a
  dense UI walkthrough vs. a calm hero reveal) and proceed; the designer adjusts it by
  saying "punchier," not by being asked. It carries **no duration target** — a punchier
  register means snappier *motion*, not a shorter *deadline*; total length still derives
  from the animations. It sets defaults only; the editor overrides any one element.
- **`hold` / `transitionDuration` are defaults, not locks.** The frame-board sets
  per-frame hold and per-seam transitions on top; this console just seeds them so a
  first assembly is watchable without touching every seam.

## Layout

Full width, four stacked regions, minimum height 280px.

- **Strip** — the frames as a horizontal thumbnail row in order, so the ratio choice is
  judged against the real design, not an abstract chip.
- **Format** — ratio tiles (each showing a frame letterboxed into it), an fps segmented
  control, and a **Duration** control: a *"Derived from the motion"* checkbox **checked by
  default**; unchecking it reveals a target-seconds field (the soft-budget path).
- **Motion** — the `energy` selector (three tiles, each a looping micro-preview of the
  same two frames transitioning at that register), plus default-transition and
  default-hold controls that update live from the energy pick but can be nudged.
- **Confirm** — a `summary` read-out sentence and one **Lock** button.

## Behavior

Picking `energy` **re-seeds** `transition`, `transitionDuration` and the eases, and the
micro-preview re-renders — but a value the user then nudges by hand is theirs and is
not re-seeded by a later energy change. Same "your edit is never overwritten" rule the
editor's drag follows.

The console **applies `suggested` silently** and validates every id against its own
options, dropping what it does not recognise — so a prefill the material did not really
decide never appears as a preselected answer.

## Feedback

`picsart_motion_setup` renders the console and returns only the input you passed. The
decision arrives later, on **Lock**, as one payload:

```json
{
  "type": "motion_setup_feedback", "version": 1,
  "selections": {
    "ratio": "9:16", "fps": 30, "hold": 2.0,
    "transition": "slide", "transitionDuration": 0.5, "energy": "standard",
    "duration": { "mode": "derived" }
  },
  "summary": "Vertical 9:16 · 30 fps · Slide 0.5s · Standard energy · length from the motion.",
  "verdict": "locked"
}
```

- Store `selections` into `motion.json.target` (ratio/width/height/fps) and as the seam
  defaults; store `summary` verbatim to quote the decision back without reassembling it
  from ids.
- `verdict` is always `locked` — the console has one button, and every control ships
  answered.
- **Pass the format back in however it came up.** `suggested.selections.ratio` should
  accept the id, the label, or the plain word (`vertical` / `9:16` / `portrait` /
  `tiktok` all resolve to `9:16`) — the ids are opaque, and a suggestion sent as `9:16`
  that gets validated-and-dropped is how "I asked for vertical" becomes a landscape
  frame preselected as though someone chose it.

## Model protocol

Same as `scene-editor` and the film boards:

- **Opening the console is the whole turn.** No prose above or below it — it states its
  own ask.
- **After opening it, stop and wait.** The decision arrives on Lock; do not speculate
  about what they will pick or ask them to retype it in prose.
- **Read the console's app context on the return turn** — the `motion_setup_feedback`
  payload is attached there, not in the visible sentence.

## Fallback

When the panel is not available, **do not interview the designer.** Infer the format from
the design (ratio from the frames' aspect, fps 30, energy as a register read from the
content), **state the inferred choices in one line they can override** — e.g. *"9:16,
30fps — say if you want it different"* — and proceed. Write the same `motion.json.target`
either way. **Never** turn setup into an the host's question interface menu: the design already answers
the ratio, and energy and duration come from the motion, not from a target the designer is
asked to pick.

SHA-256: 2702e81e11959a5ff0ecf80e4ae94b1a1cc876080b6b69cb301cb201dda4f50f