Files
roboco/motion/README.md
T
63212899ca [2f806123] Video: release 0.25.0 (#535)
* fix(release): CI wait polls the prod rung; escape the header tooltip apostrophe

get_latest_ci_conclusion defaults to the ladder's head rung, so
wait_for_ci searched slave for a release commit that lives on master
and timed out after 40 minutes with the run already green. The wait
now passes the prod branch explicitly. Also fixes the
react/no-unescaped-entities error that turned master's Panel CI red.

* [2f806123] feat(motion): add release-0.25.0 composition extending panel-demo register

* [2f806123] fix(scope): revert out-of-scope backend and panel changes from video branch

* [2f806123] fix(scope): revert out-of-scope backend and panel changes from video branch

* [2f806123] fix(scope): restore out-of-scope files from current origin/master after stale-master revert

* [2f806123] fix(scope): restore motion/pnpm-workspace.yaml from origin/master

* [2f806123] docs(motion): add release-0.25.0 composition example to README

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
Co-authored-by: UX/UI Developer 1 <ux-dev-1@roboco.tech>
Co-authored-by: UX/UI Documenter <ux-doc@roboco.tech>
2026-07-16 08:43:10 +02:00

11 KiB
Raw Blame History

motion

In-repo HyperFrames composition package. UX/UI devs author bespoke marketing-video compositions here on a normal delivery branch; once a composition merges, the orchestrator's render loop tars this directory and POSTs it to the video-renderer sidecar (T1 rewrote its render core to @hyperframes/producer), which renders the HTML to MP4 with Chromium. This package never renders itself — its own gate (pnpm test) is static: a vitest HTML-structure smoke against the authored files. The render-truth check happens on the sidecar, against the merged files.

Adding a composition

Compositions live under compositions/<composition_id>/. Each composition is one directory containing the HTML/CSS/JS for that clip; one HTML file per orientation (the sidecar points inputPath at a single HTML file, so per-orientation branching belongs in its own file, not inside one file).

  1. Create compositions/<your_clip>/vertical.html and square.html carrying the HyperFrames render params on <html>: data-width, data-height, data-duration (seconds), data-fps. Timed visible elements get class="clip" plus data-start, data-duration, and data-track-index.
  2. Add a shared theme.css with @font-face declarations (fonts are vendored under public/fonts/*.woff2 — never load from a CDN, the render is offline) and the color/type tokens for the clip. (Skip this if you're building in the panel-demo register below — kit/kit.css already owns the reset + fonts.)
  3. Ship a props.js with default window.__PROPS__ + window.__ORIENTATION__ values for local preview. The sidecar OVERWRITES this file at render time with the real per-release values — your HTML loads it via <script src="props.js"></script> before any inline script that reads the globals.
  4. Add <your_clip>.test.js (vitest) asserting the HTML-structure invariants — dimensions, props.js wiring, theme link, at least one class="clip" element, no CDN scripts. See release-announcement.test.js as a template.
  5. pnpm preview (alias for hyperframes preview <file>) opens the local preview server; pnpm lint runs hyperframes lint over both orientations; pnpm test runs the smoke gate that CI runs.

release-announcement — props shape

The composition reads window.__PROPS__ (written into props.js by the sidecar at render time):

{
  script: string,      // one or two sentences — the voiceover-style hook
  version: string,     // e.g. "0.19.0" — rendered as "v0.19.0"
  highlights: string[],// shipped-feature bullets; only the first 4 render
}

window.__ORIENTATION__ is also set by the sidecar, but each HTML file is for ONE orientation (the sidecar points inputPath directly at compositions/release-announcement/vertical.html or square.html), so the orientation is known at author time and there is no runtime branch. Vertical is 1080×1920 (TikTok + X mobile), square is 1080×1080 (X timeline). Width is always 1080; only the height (and so the available vertical canvas) changes. Square uses a tighter bottom pad (72) and scan-line top (72) than vertical (148 / 104) — those differences are baked into the respective HTML files.

Design bar for future compositions

This composition is the library's reference point — match its restraint, don't reinvent the palette per clip. Dials (see the org's design-bar doctrine): variance ~6, motion ~6, density ~4 — an energetic landing/marketing register, not a dense dashboard.

  • Colorcompositions/<id>/theme.css is the single source: a near-black ink field (never pure #000), warm off-white text (never pure #fff), and one accent color used with intent (a label, a rule, a marker — not washed across the frame). Reuse the theme tokens; don't hardcode new hex values per composition.
  • TypeShare Tech Mono (a single static weight, 400 Regular — no bold/italic exists for this family) for the one big headline moment, paired with Inter as the clean workhorse body face for everything else. Both are vendored under public/fonts/*.woff2 and loaded via @font-face in theme.css rather than fetched from a CDN — rendering never depends on network access or on whatever fonts happen to be installed on the render host. At least two weights of the body face, so hierarchy comes from more than just size.
  • Motion — entrances are CSS keyframe animations with a restrained easing (cubic-bezier(0.22, 1, 0.36, 1) — smooth settle, no overshoot, approximating Remotion's spring({damping:17, mass:0.7, stiffness:140}) without the bounce), staggered across elements via animation-delay rather than all firing on frame 0. Keep one continuous ambient motion (here: the scanning accent line) so the frame is never fully static once entrances land. Every animation should earn its place — hierarchy, or a beat of pacing, not motion for its own sake.
  • Layout — anchor content asymmetrically (this clip sits in the lower two-thirds, left-aligned); avoid a perfectly centered card, which reads as a generic template rather than a designed frame.
  • AI tells to avoid — no default AI-purple gradient wash, no centered-everything, no emoji as design elements, no one-font-one-size, no em dash in on-screen copy (voiceover script, highlight bullets, kicker text) or filler verbs ("Elevate", "Seamless", "Unleash", "Next-Gen").

Panel-demo kit (kit/)

kit/ is a second register alongside the release-announcement's text-card style: reusable pk--namespaced CSS/HTML that recreates the control panel's look (dark chrome, task cards, status pills, toasts, a typing reveal, a cursor) so a composition can simulate the product actually being used, instead of announcing it over a headline. Use the text-card register (release-announcement's pattern) for version/feature announcements with no product visuals; use the demo register (kit/) whenever the story is "watch this happen in the app" — a task moving through the panel, a feature being triggered, an agent doing something visible.

compositions/panel-demo/ is the reference composition: a task title types into an intake field, a card materializes in a column, a cursor clicks it done, a toast confirms, out on "roboco.tech". Start a new demo-register composition from its structure and kit.css's classes rather than reinventing the panel's chrome per clip. See kit/README.md for the full piece-by-piece reference.

Release-specific example: release-recap (0.18.0 - 0.20.0)

compositions/release-recap/ is a demo-register clip built on kit/, not the release-announcement text-card style — the CEO rejected an earlier text-card cut of this same occasion ("Build this in the panel-demo register... the video must show the product moving... Do not invent a new visual language"). It ships the same two orientations as every other composition — vertical.html (1080×1920) and square.html (1080×1080) — sharing props.js and the offline-render constraints, but no theme.css of its own since kit/kit.css owns the look.

The story is "three releases shipped in six days": a single intake types "3 releases in 6 days", then three release cards (v0.18.0, v0.19.0, v0.20.0) cycle through the same kanban slot — each card is absolutely positioned at the same spot inside the column and painted after the previous, so the later card's solid background fully covers the one before it, a beat swap that reuses panel-demo's exact single-card geometry per orientation instead of stacking three cards' worth of height (which would collide with the toast in the square cut). Each beat gets its own status-pill flip (in progress -> completed) and its own cursor click at the same parked position (only the first click glides in; the other two are click-only, x0==x1/y0==y1), then one toast and the "roboco.tech" outro once all three land.

Preview / test this composition

npx hyperframes preview compositions/release-recap/vertical.html
npx hyperframes preview compositions/release-recap/square.html
pnpm test   # runs vitest on all *.test.js under motion/

captions.json

Like every release composition, this one ships a tracked captions.json next to the HTML holding the X and TikTok captions the render pipeline proposes alongside the MP4, self-verifying character counts against each platform's limit:

{
  "composition_id": "release-recap",
  "platforms": {
    "x":      { "caption": "...", "char_count": 136, "limit": 280,  "within_limit": true },
    "tiktok": { "caption": "...", "char_count": 365, "limit": 2200, "within_limit": true }
  }
}

The smoke test (release-recap.test.js) asserts this schema, checks the counts, and regression-guards no em dashes in on-screen copy or captions (the design-bar violation QA caught on the prior text-card cut of this task).

Release-specific example: release-0.25.0

compositions/release-0.25.0/ is a panel-demo kit clip for the RoboCo v0.25.0 release. It builds on the kit/ register instead of the text-card style, so it has no theme.css of its own.

The story is "governance gets a better UI": the CEO types "Governance gets a better UI" into the panel intake, then three shipped feature cards — Env ladder, Collision map, Metrics donut — enter a kanban column and flip from in progress to completed. A cursor clicks through the cards, a stats overlay flashes "1 release / 25 agents / 1 human", and a toast confirms "v0.25.0 shipped / I approved once. 25 agents did the rest." The clip closes on the "roboco.tech" outro.

The composition reuses the same pk-frame chrome, pk-column/pk-card, pk-pill, pk-cursor, pk-toast, and pk-outro pieces from kit/, plus the typing reveal wired through props.js. Each feature card uses the pk-pill--swap-out / pk-pill--swap-in pattern from panel-demo and release-recap to replace the in progress pill with completed on the same beat.

Preview / test this composition

pnpm preview:release-0.25.0
pnpm test   # release-0.25.0.test.js is picked up by vitest

props.js shape

{
  introText: string,   // text that types into the panel intake field
  toastTitle: string, // headline inside the shipping toast
  toastBody: string,   // sub-line inside the shipping toast
}

window.__ORIENTATION__ is set for local preview only; the sidecar overwrites both globals at render time.

captions.json

Same schema as release-recap: one captions.json next to the HTML with self-verified X and TikTok captions:

{
  "composition_id": "release-0.25.0",
  "occasion": "release: RoboCo v0.25.0",
  "platforms": {
    "x":      { "caption": "...", "char_count": 170, "limit": 280,  "within_limit": true },
    "tiktok": { "caption": "...", "char_count": 347, "limit": 2200, "within_limit": true }
  }
}

Smoke-test invariants

release-0.25.0.test.js extends the panel-demo register checks: both vertical.html (1080×1920) and square.html (1080×1080) parse with the HyperFrames params, the kit CSS/JS wiring is present, three feature cards each carry a progress-to-completed pill swap, the cursor and toast appear, the outro shows "roboco.tech", no external scripts are loaded, and no em dashes slip into on-screen copy or captions.