← Files PicsartARCHIVED FILE

docs/widget-specs/shotlist-board.md

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

↓ Download file

See the change to this file →

# Widget spec — `picsart_shotlist_board`

The scene's shots as editable cards, story first. Each card shows what happens
and what is said as text areas the user types straight into, then six decisions
as pills that open **a picture rather than a list**.

**This board plans; it does not review takes.** Statuses, take tiles,
accept/reshoot, start-frame approval and the inline design panel were all removed
on 2026-08-21 when the board was rebuilt on its approved mock. A take is judged
in the scene editor (`picsart_scene_editor`), which owns the verdict, the user's
own trims and splits, and the comments. The retired panel's spec is kept at `shot-design-panel.retired.md`.

## Tool input

```ts
{
  scenes?: Array<{                    // preferred: ALL scenes in one call
    id: string,                       // 'sc01'
    slug: string,                     // 'INT. GARAGE — DAY'
    note?: string,
    shots: Array<{
      id: string,                     // '12A'
      action: string,                 // one action, one sentence
      line?: string,                  // omit for a silent shot
      assets?: string[],              // ['@cal','@garage_day'] — joined to one line
      values?: {                      // the six decisions; omitted = board default
        size?, move?, lens?, light?, palette?, sec?
        // `pace` was the seventh until 2026-09-04; a value sent under it is dropped
      }
    }>
  }>,
  // Legacy single-scene shape still accepted: scene + slug + shots.
  film?: string,
  note?: string
}
```

Pass **every scene of a planning pass in one call** — one approval covers the
set. Five boards each firing their own feedback is the documented
flunky-confirmation failure.

A `values` entry the board has no preset for **is kept and displayed verbatim**,
never snapped to a neighbour. A payload saying a shot runs 15s is describing that
shot; rendering "3s" because 15 is not one of the six offered lengths would be
the board lying about the plan. It stays 15s until the user picks something else,
and comes back as 15s.

## Layout

680px column, one bordered card, then per scene: a header, its shot cards, an
add-a-shot row. One footer for the whole board.

- **Scene header**: a 34px accent glyph, `sc01 · INT. GARAGE — DAY`, and a meta
  line counting the live shots and their total seconds — "3 shots · 20s — type
  straight into any shot". Both exclude cut shots, so cutting one shows in the
  header immediately.
- **Shot card** (`bg-soft`, r12): the id as a badge, the asset tags on one line,
  a `Reset` that appears only once the shot differs from the payload, and four
  28px icon buttons — up, down, duplicate, cut. A cut card dims to 0.72, strikes
  its id through, replaces its body with "Cut from the scene — nothing will be
  generated for it." and an `Undo`. **Cut is reversible on the board**; nothing
  is deleted.
- **The two text areas** carry uppercase labels naming what belongs in them —
  `ACTION — WHAT HAPPENS IN FRAME` (84px) and `LINE — WHAT IS SAID` (56px, "Leave
  empty for a silent shot"). Fixed height, no resize handle: the card is a row in
  a list, and a hand-dragged text box would break the rhythm.
- **Six pills**, three per row: `Shot size` · `Camera move` · `Lens` · `Light` ·
  `Colour` · `Length`. Each carries a 30px schematic of its current choice, the
  control's name above that choice, and a caret. A seventh, `Temp`, was removed
  on 2026-09-04: it put one tempo adjective into every cut header and its slow
  default made every film slow. Rhythm is written into the action and `Length`.
- **Recap line** under the pills — "Wide · Locked off · 35mm · 7s" — the four
  decisions worth reading without opening anything.
- **Add a shot**: a dashed full-width row. A new shot continues the scene's own
  letter run and inherits the previous card's tags.
- **Footer**: how many shots are edited ("2 shots edited — your changes go with
  the approval." / "Edit anything you like, then approve the list."), the **cost
  estimate** on its own line beneath it, and **`Approve scene list`** —
  `Approve shot list` when the board carries several scenes.
- **The cost estimate** — "12 shots to generate — ≈ 384 credits." Every card
  here becomes one charged generation, so this is the price of pressing the
  button, shown beside the button. Quoted with `picsart_preflight` (free) once
  per DISTINCT shot length and multiplied by the shots at that length: cost is
  linear in duration, so sampling one shot would misprice any mixed-length
  board. Debounced 400ms and re-quoted only when a length changes or a shot is
  added or cut.
  - Absent entirely when the caller named no `model` — the same length costs
    different amounts on different models, so quoting against a guess would be
    inventing a price.
  - **Overstated on a run-shot film (2026-09-03).** The plugin now generates as
    much of the film as fits the model's ceiling as one multi-shot take — a
    *run* — rather than one generation per card (`film-scenes`), so "every card becomes one charged generation" is no
    longer how the film is bought: a run's cost is the sum of its shots' `sec`
    plus two end handles, snapped once, and the board's per-card figure (a
    handle pair and a snap per card) comes out higher. The plugin reads the
    board's number as "what these shots would cost alone" and says the run
    total in chat; the fix is the run marker in `PLATFORM-ASKS.md` §11.
  - If ANY length fails to price, the total is **withheld**, not summed over the
    ones that worked: "12 shots to generate — cost estimate unavailable." A
    number covering nine of twelve shots reads as the whole bill.
  - A failed quote never reads as free and never blocks approving.
  - **Approving the list is not approving the spend (2026-09-07).** This number
    tells the user what the list implies; the purchase is quoted again per
    dispatch, in chat, with `picsart_preflight`, and waits for a yes there
    (`film-scenes`). A board approval carried into a dispatch is how runs went
    out unquoted.

### The panel

420px, and mostly picture. On the left the current (or hovered) option at 16:9
with its name and one line of why; on the right a 150px scrolling list of
options, each with the same schematic at 30px and a tick on the chosen one. The
control's help text sits along the bottom.

**Hovering an option previews it in the big frame without committing.** That is
the whole reason the panel is this wide: camera moves are compared by looking,
not by reading labels — and with 51 of them, reading is not an option.

The third pill in each row (`Lens`, `Length`) anchors its panel to the
right edge, or it would overflow the card. Panels open downward, and **flip up
when they would overrun the board's bottom edge** — the host iframe is sized to
content and clips what spills past it, and an absolutely positioned panel adds no
content height, so an overrun is gone rather than scrolled to. The bound is the
board box, deliberately not the viewport: in a host that scrolls the widget, a
viewport bound would make a panel change direction under the user mid-scroll.

**Every panel is height-capped and scrolls** — `min(420px, 70dvh)`, shared as
`.pop-scroll`. This is what makes the 48-option `Camera move` list usable: the
panel had no cap and no overflow at all, so its tail was clipped by the
content-sized iframe with no scroll container to scroll — unreachable on a phone.
`dvh` and not `vh`, because inside a widget iframe `vh` resolves against the
iframe, which is content-sized and can be taller than the screen. A panel
anchored far down the board can still bottom out below the fold; the cap makes it
scrollable, not repositioned.

**One panel is open at a time, and it closes on Escape or on a press outside
IT** — not outside the board. Pressing another pill closes this one as that one
opens; pressing the pill that owns it closes it; pressing inside it selects. The
press is `pointerdown` in the capture phase, which is also what makes dismissal
work under touch, where a synthesised `mousedown` is best-effort.

### The schematics

Every option is drawn, not described:

- **Shot size** — a figure inside the frame, shoulders and head at true
  proportion, with eyes/nose/mouth appearing only once the head is big enough to
  carry them. Wide puts a small figure on a ground line; ECU crops the skull off
  the top of frame.
- **Camera move** — **a generated clip of the move where one exists, and arrows
  around a frame outline where it does not.** A schematic can say "a line goes
  right"; it cannot say what a truck looks like as opposed to a pan, which is
  the distinction the fragments claim to teach. So the big panel plays the probe
  (`genre-previews/_camera/` in the plugin repo: one shared start frame per move
  group, the move fragment the only thing that varies) and the 30px row chip
  stays the arrow — a clip at that size is mud, and an arrow is the better cue
  for scanning a list of fifty. Under `prefers-reduced-motion` the panel shows
  the poster frame instead.

  **The fallback is not optional.** Eighteen of the fifty moves have been probed;
  the rest draw their schematic, and thirty schematics serve them all, because a
  drone push-in and a dolly push-in are the same path at different altitudes and
  the label carries the difference the drawing cannot.
- **Lens** — the field of view as a wedge from the camera with the subject pinned
  at a fixed distance, so only the angle changes between options. Labelled in
  degrees, named in millimetres. Ten anchors, not the mock's six: the wedge is
  parametric, so the four extra rungs cost nothing to draw — and four of the
  mock's six carried the wrong number for their lens (50mm as 36°, 135mm as 14°,
  24mm as 75°, 18mm as 94°), which is a thing you can only notice by putting both
  ladders side by side.
- **Light** and **Colour** — the real thing rather than a diagram (see below).
- **Length** — the number, large.

These draw white-on-charcoal on their own opaque ground, so they read identically
in light and dark the way a photograph does. The one themed colour in them is the
brand accent on the lens wedge — that is the app pointing, not the picture.

### Colour belongs to each shot

The Colour control offers `auto` and the seventeen palette presets from
`../../skills/film/references/PRESETS-LOOKS.md`. The film setup carries only
scheme and hue. Seed a shot's palette as `auto` unless it needs a separate
colour decision, and keep any selected palette compatible with that scheme.
A non-auto palette reaches the shot prompt whether seeded or user-edited.
The board keeps its palette preview stills and 60/30/10 chip bars.

## Feedback → `shotlist_board_feedback` v3

Per `WIDGET-FEEDBACK.md`: `scope`, `verdict: 'approved'`, and per scene the
**final list in `order`** with every shot's six `values`, its text, its
`recap`, and at most one of `cut` / `added` / `edited`. Publish state silently
while the user edits.

v2 sent a diff (`edits` as changed keys); v3 sends the finished list, because the
board is now where the plan is written rather than annotated — and a diff against
a payload the assistant may have already reshaped is harder to apply than the
list itself. `verdict` is always `approved`: the board has one button, and edits
are the plan, not objections to it.

## Build notes

`presets.ts` transcribes the mock's option lists, labels, notes and help strings
character-for-character, except Colour (see above); `artwork.tsx` transcribes its
schematic geometry exactly. Drift from the mock is a bug — with one class of
exception, recorded here so it does not read as drift.

**Every vocabulary the film shares is imported, not transcribed.** Camera move,
shot size, arrangement, camera height, lens, light and colour all come from
modules the film-setup console and the review board read too, and which the
plugin's own `PRESETS-CAMERA.md` / `PRESETS-LIGHTING.md` / `PRESETS-ACTING.md`
define. Where the mock's list was shorter than the reference's, the reference
wins: the board now offers seven sizes rather than five, ten lens anchors rather
than six, and seventeen lights rather than six. What stays the mock's, exactly,
is the **drawing** — the fig geometry, the wedge and the gradient ramp.

The board imports **nothing** from `shot-designer/` any more — it draws its own
schematics and no longer borrows that console's stylesheets. A draft persisted by
the previous board is discarded rather than rendered: the old shape
(`{ order, removed, edits, comments, takeActions, designs, generalComment }`)
would white-screen the card, which reads `values` on first render.

SHA-256: 8d8b5e7288206581628510c44df987b10f5e66ffc8f5051e699d8f7f97d7f281