* [c71959c3] feat(motion): add release-0.27.0 panel-demo clip Extends the panel-demo kit register (kit/) with a v0.27.0 release composition: identity cold open, four feature cards (WAF real-IP resolution, Telegram Mini App V6, Codex/Gemini providers, cost-tiered routing) flipping from in-progress to completed, a camera that pushes per beat via pk-camera data-shots, a cursor that clicks and travels via pk-cursor data-waypoints, a receipt stats overlay, a shipped toast, and the roboco.tech outro. Ships vertical.html, square.html, props.js, captions.json, and a vitest smoke test mirroring release-0.26.0's invariants. * [c71959c3] fix(motion): implement genuinely non-uniform card-beat gaps in release-0.27.0 clip The four .pk-card animation-delay values were uniform 5.0s/10.0s/15.0s/20.0s in both vertical.html and square.html -- byte-for-byte identical to release-0.26.0's metronomic spacing the craft bar bans -- despite the prior decision_log claiming 5.5s/6.4s/5.3s varied gaps had been added. Card delays are now 5.0/10.5/16.9/22.2 (real 5.5s/6.4s/5.3s gaps). Camera data-shots, cursor data-waypoints, and the stats/toast/outro tail in both files are re-warped with a shared piecewise-linear time function anchored at each card's new beat, so nothing desyncs and the tail still fits inside the fixed 40s runtime. Added a regression test asserting the four card gaps are not all identical. * [c71959c3] docs(motion): add release-0.27.0 composition section to README --------- Co-authored-by: UX/UI Developer 1 <ux-dev-1@roboco.tech> Co-authored-by: UX/UI Documenter <ux-doc@roboco.tech>
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).
- Create
compositions/<your_clip>/vertical.htmlandsquare.htmlcarrying the HyperFrames render params on<html>:data-width,data-height,data-duration(seconds),data-fps. Timed visible elements getclass="clip"plusdata-start,data-duration, anddata-track-index. - Add a shared
theme.csswith@font-facedeclarations (fonts are vendored underpublic/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.cssalready owns the reset + fonts.) - Ship a
props.jswith defaultwindow.__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. - Add
<your_clip>.test.js(vitest) asserting the HTML-structure invariants — dimensions, props.js wiring, theme link, at least oneclass="clip"element, no CDN scripts. Seerelease-announcement.test.jsas a template. pnpm preview(alias forhyperframes preview <file>) opens the local preview server;pnpm lintrunshyperframes lintover both orientations;pnpm testruns the smoke gate that CI runs.
Captions
propose_video's x_caption/tiktok_caption args (self-verified against each platform's limit in the tracked captions.json — see the release examples below) follow their own per-platform shape, not a shared template:
- X — a hook line naming the concrete capability, then 1-2 lines of specifics (real feature names/numbers beat adjectives), a short outro. No hashtag spam. Target well under 240 characters so the 280 limit never truncates mid-sentence.
- TikTok — hook + 2-3 short punchy lines + 3-5 relevant niche hashtags max (not generic
#tech #aifiller). - Same slop-ban as the "AI tells to avoid" design-bar rule below: no em dashes, no filler verbs ("Elevate", "Seamless", "Unleash").
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.
- Color —
compositions/<id>/theme.cssis 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. - Type — Share 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/*.woff2and loaded via@font-faceintheme.cssrather 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'sspring({damping:17, mass:0.7, stiffness:140})without the bounce), staggered across elements viaanimation-delayrather 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").
Visual design bar (demo/kit register)
The design bar above governs the text-card register; kit/ pieces answer to the same restraint, but the tells are different — a demo clip fails by looking like a slide deck wearing the panel's colors, not by looking like a marketing card. Ground every rule below in the actual pk-* classes (kit/kit.css) and the shipped release compositions before inventing a new pattern; the vendored craft references now living in skills/references/ (see the Cinematography section below) back every rule here with the underlying design theory.
- Spacing and hierarchy —
pk-column's 56px left inset is fixed, and the kit's card padding (32px/36px) and title size (34px) are the vertical-cut baseline — square cuts legitimately tighten them per orientation (release-0.25.0/square.htmlruns 18px/24px padding and a 28px title), but that's a per-orientation override, never a per-beat one. Don't crowd a second full card into the same beat just because there's vertical room:release-0.25.0gives each card its own ~5s scene, andrelease-recapcaps its visible stack at three compact cards with tight margins. A card's own internal rhythm (title, a gap, the chip row, another gap, the status pill) is load-bearing — don't add a second meta row or a second pill just to fill space. - Beat density and pacing variation — identical entrance intervals across many beats (every 5.0s via
animation-delay, asrelease-0.25.0's cards do — beats ride delayed CSS animations, neverdata-startclip windows; see the clip-window rule below) are fine for a receipt-style listing where the viewer is meant to learn the rhythm, but the entrance itself must still vary: swap--pk-easefor a springy overshoot (cubic-bezier(0.34, 1.4, 0.64, 1), asrelease-0.25.0does on its cards) on at least one beat type so the library doesn't share byte-identical timing everywhere. Past ~4 evenly-spaced beats the interval itself starts to read metronomic — vary it, or break the pattern with a different-shaped beat (a receipt, a stat overlay) before the count gets there. - Chip and pill color-variant discipline —
pk-pillvariants are STATUS-semantic (progress/review/approved/completedmap onto real lifecycle states); never pick one for how it looks against the frame.pk-chipvariants are CATEGORY-semantic — reuse the same variant for the same real-world category across a whole clip (and across the library), rather than rotating colors per beat for visual variety. A frame carrying four-plus chip colors with no categorical reason reads decorative, not designed. - Camera + cursor + beat rhythm reading as filmed — treat each card's build (entrance), breathe (pill hold while the camera settles or pushes), and resolve (pill swap, cursor click) as its own three-phase unit, and point
pk-camera'sdata-shotsat whichever card is actually completing rather than a fixed frame the beats happen to pass through —release-0.25.0's shot list re-centers on each card in turn. Chainpk-cursorwaypoints with room for the idle-hand sway (kit.js) to run between legs; a cursor that jumps beat to beat with zero rest reads like a pointer teleporting, not a hand moving. - Anti-generic tells — a uniform grid of same-size cards with no hierarchy difference (reach for a one-card-per-scene sequence like
release-0.25.0, orrelease-recap's compact three-card stack, instead); the same ease and duration on every entrance across a whole clip (skills/references/motion-principles.md's "same ease on every tween" guardrail is a CSS problem too, not just a GSAP one); and decorative motion with no product meaning —pk-frame__statusdot's pulse earns its place because "Live" really is pulsing, but a second unrelated glow added only to fill a frame doesn't.
For visual vocabulary beyond what kit/ covers today, see skills/hyperframes-catalog-index.md — it names which effects map onto an existing pk-* piece and which would need a new one.
Cinematography & rhythm (demo register)
A panel-demo clip is a FILM of software being used, not a screenshot with captions. Before authoring, write a shot list: for every beat, where is the camera, where is the cursor, what changes on screen, and what caused it. Then build to it. The specific tells that get a cut bounced:
- A locked-off camera. Wrap the scene in
pk-cameraand drive it withdata-shots(seekit/README.md): open slightly tight, push toward each beat's focal point (scale <= 1.08, translate <= ~160px — the audience should feel it, not see it), pull wide for a reveal, settle to end. A static frame for more than ~8s is dead air. - A cursor that doesn't behave like a hand. Drive it with
data-waypoints: it fades in, travels with eased legs, rests with an idle sway (kit adds this), clicks with a visible cause→effect (the click precedes the thing it triggers), and leaves the frame — it never pops in, freezes pixel-still, or blinks out mid-scene. - A metronome. Identical beat lengths with identical flat entrances read as a slideshow. Vary entrance energy (the kit cards take a springy overshoot well), let a beat breathe after a click, and give the climax (stats/receipt) a different rhythm than the build.
- Verify motion, not stills. After
request_render, sample PAIRS of frames ~0.5s apart around each cursor/camera beat and compare positions — a single frame proves presence, only a pair proves movement. - The vendor's authoring doctrine is vendored in
skills/. Readskills/hyperframes-core.md(composition contract),skills/hyperframes-keyframes.md(seek-safe keyframes across runtimes), andskills/hyperframes-creative.md(beat planning) before authoring — they are the official HyperFrames agent skills, vendored at a pinned upstream commit (Apache-2.0, header in each file; re-vendor when bumping@hyperframes/producer).skills/hyperframes-creative.mdin turn points atskills/references/house-style.md,video-composition.md,beat-direction.md, andmotion-principles.md— four of its own reference docs, vendored the same way, that cover palette/lazy-defaults, video-medium scale and density, per-beat rhythm planning, and ease/speed/direction variance in more depth than the summary above. Note the vendor's primary seek-safe animation primitive is GSAP tweens registered onwindow.__timelines— this kit's CSS-animation register is a house pattern, and the clip-window rule below is its empirically-derived seek-safety companion. - Clip windows are for structural layers only. The renderer's per-clip visibility scheduler drifts badly behind the encoded timeline on long compositions (measured live: a 40s cut whose per-beat clips only reached the ~19s mark by the final frame — the entire tail silently missing from the MP4). Give
class="clip"+data-start/data-durationonly to full-length structural layers (the cold-open, the panel frame), and drive every BEAT inside them with the kit's pattern instead: base-hidden styles (opacity: 0) plus a delayed CSS animation (animation-delay+forwards/bothfill) — those run on the correct clock. If a beat must also disappear, give it an exit animation, not a clip window.
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 current revision runs 40 seconds total (up from an earlier 14s cut) so every feature card is fully visible before the next one enters. The story is still "governance gets a better UI": the CEO types "Governance gets a better UI" into the panel intake at 3.6s, then four shipped feature cards enter the kanban column one per scene and flip from in progress to completed:
- Env ladder — enters at 5.0s, completes at 6.6s ("Dev to prod, one rung at a time.").
- Collision map — enters at 10.0s, completes at 11.6s ("See who touched what before you review.").
- Metrics donut — enters at 15.0s, completes at 16.6s ("90 days of real task flow.").
- Notification bell — enters at 20.0s, completes at 21.6s ("Real read/ack actions on every alert.").
Each card gets roughly five seconds of fully visible time before the next card enters. A cursor clicks the column at 22.0s, the stats overlay shows "1 release / 25 agents / 1 human" from 24.0s to 32.0s, the toast "v0.25.0 shipped / I approved once. 25 agents did the rest." runs from 30.0s to 38.0s, and the "roboco.tech" outro lands at 36.0s and holds through the end.
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. The X caption was updated to include the Notification bell feature and now totals 216 characters:
{
"composition_id": "release-0.25.0",
"occasion": "release: RoboCo v0.25.0",
"platforms": {
"x": { "caption": "...", "char_count": 216, "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 data-duration="40" and the HyperFrames params, the kit CSS/JS wiring is present, four feature cards each carry a progress-to-completed pill swap and include the "Notification bell" text, 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.
Release-specific example: release-0.26.0
compositions/release-0.26.0/ is a panel-demo kit clip for the RoboCo v0.26.0 release, mirroring the structure and pacing of release-0.25.0. It builds on the kit/ register instead of the text-card style, so it has no theme.css of its own.
The composition runs 40 seconds total. The story is "the control plane gets a lock" (the hero tagline): the CEO types "The control plane gets a lock" into the panel intake at 3.6s, then four shipped security/feature cards enter the kanban column one per scene and flip from in progress to completed:
- Off the public net — enters at 5.0s, completes at 6.6s ("127.0.0.1 only, nginx does the rest."). The orchestrator API is now bound to localhost; public internet access is closed.
- 3 forges, 1 API — enters at 10.0s, completes at 11.6s ("GitHub, Gitea, GitLab. Pick your forge."). Three forge providers (GitHub, Gitea, GitLab) are now first-class citizens behind one unified REST API.
- Telegram cockpit — enters at 15.0s, completes at 16.6s ("Today brief, approvals, chat. One socket."). The Telegram Mini App V4 becomes a real client with live task dashboard, actionable approvals, and multi-channel communication over a single WebSocket.
- Guard mode: active — enters at 20.0s, completes at 21.6s ("fastapi-guard stops watching, starts blocking."). The content-security guard flips from passive monitoring to active enforcement on request payloads.
Each card gets roughly five seconds of fully visible time before the next card enters. A cursor clicks the intake at 4.8s (submit), then witnesses each card completing without further clicks (the agents do the work), then a second click at 30.8s (acknowledge the toast). The stats overlay shows "1 release / 3 forges / 0 leaks" from 24.0s to 32.0s, the toast "v0.26.0 shipped / I approved once. 25 agents shipped it." runs from 30.0s to 38.0s, and the "roboco.tech" outro lands at 36.0s and holds through the end.
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 to replace the in progress pill with completed on the same beat. The stats overlay uses display typography (Share Tech Mono) and the accent color to emphasize the "3 forges" metric, reinforcing the release's security + multi-provider focus.
Preview / test this composition
pnpm preview
pnpm test # release-0.26.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 prior releases: one captions.json next to the HTML with self-verified X and TikTok captions. The X caption totals 182 characters (within the 280 limit); the TikTok caption 419 characters (within the 2200 limit):
{
"composition_id": "release-0.26.0",
"occasion": "release: RoboCo v0.26.0",
"platforms": {
"x": { "caption": "v0.26.0 is out.\nOrchestrator API is off the public internet now.\nGitHub, Gitea, GitLab: one API.\nTelegram cockpit is a real client.\nI approved once. 25 agents shipped it.\nroboco.tech", "char_count": 182, "limit": 280, "within_limit": true },
"tiktok": { "caption": "v0.26.0 is out.\n\nThe orchestrator API is off the public internet. No more raw access to the control plane.\n\nThree forges are first class now: GitHub, Gitea, GitLab. One API, one review flow, pick your forge.\n\nThe Telegram Mini App is a real client now: today brief, approvals, chat, all live off one socket.\n\nfastapi-guard is active enforcement. Not passive anymore.\n\nI approved once. 25 agents shipped it.\n\nroboco.tech", "char_count": 419, "limit": 2200, "within_limit": true }
}
}
Smoke-test invariants
release-0.26.0.test.js extends the panel-demo register checks: both vertical.html (1080×1920) and square.html (1080×1080) parse with data-duration="40" and the HyperFrames params, the kit CSS/JS wiring is present, four feature cards each carry a progress-to-completed pill swap and include the exact titles ("Off the public net", "3 forges, 1 API", "Telegram cockpit", "Guard mode: active"), the stats overlay renders the three-line receipt (1 release / 3 forges / 0 leaks), 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.
Release-specific example: release-0.27.0
compositions/release-0.27.0/ is a panel-demo kit clip for the RoboCo v0.27.0 release, mirroring the structure and pacing of release-0.25.0/release-0.26.0. It builds on the kit/ register instead of the text-card style, so it has no theme.css of its own.
The composition runs 40 seconds total. The story is "four brains, zero bans" (the hero tagline and the panel intake text): the CEO types "Four brains, zero bans" into the panel intake at 3.6s, then four shipped feature cards enter the kanban column one per scene and flip from in progress to completed:
- WAF knows the real IP — enters at 5.0s, completes at 6.6s ("Tailscale hops don't get banned anymore."). The WAF now resolves the real client behind host-proxy hops via
ClientIpResolutionMiddleware, instead of banning the operator's own Tailscale-tunneled device. - Telegram Mini App V6 — enters at 10.5s, completes at 12.1s ("Real chat scopes, wallet-style metrics."). The Mini App gets a native-type premium overhaul: borderless cards, a wallet-style metrics tab, honest Chat scopes, and a live Board decide sheet.
- Codex and Gemini join — enters at 16.9s, completes at 18.5s ("Four model providers, one gateway now.").
ModelProvider.OPENAI(Codex CLI) andModelProvider.GEMINI(Gemini CLI) join Anthropic and Grok on the agent-provider roster. - Cost-tiered routing — enters at 22.2s, completes at 23.8s ("Save a routing mix, restore it anytime."). Cost-tiered model routing plus saved routing presets let an operator snapshot and restore a full routing configuration.
Unlike release-0.25.0/release-0.26.0's uniform 5.0s card-beat gaps, this composition deliberately varies the spacing (5.5s/6.4s/5.3s between successive card entrances — delays 5.0/10.5/16.9/22.2s) per the craft bar's anti-metronome rule in "Beat density and pacing variation" above; the camera shots, cursor waypoints, and the stats/toast/outro tail are all re-warped with a shared piecewise-linear time function so nothing desyncs against the shifted beats. A cursor clicks the intake at 4.8s (submit), then witnesses each card completing without further clicks, the stats overlay shows "1 release / 4 providers / 0 bans" from 25.7s to 33.0s, the toast "v0.27.0 shipped / I approved once. 25 agents shipped it." runs from 31.0s, and the "roboco.tech" outro lands at 36.3s and holds through the end.
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 to replace the in progress pill with completed on the same beat.
Preview / test this composition
npx hyperframes preview compositions/release-0.27.0/vertical.html
npx hyperframes preview compositions/release-0.27.0/square.html
pnpm test # release-0.27.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 prior releases: one captions.json next to the HTML with self-verified X and TikTok captions. The X caption totals 234 characters (within the 280 limit); the TikTok caption 499 characters (within the 2200 limit):
{
"composition_id": "release-0.27.0",
"occasion": "release: RoboCo v0.27.0",
"platforms": {
"x": { "caption": "v0.27.0 is out.\nThe WAF used to ban my own phone behind Tailscale. Now it knows the real client.\nTelegram Mini App V6, Codex and Gemini joined the roster, cost-tiered routing shipped.\nI approved once. 25 agents shipped it.\nroboco.tech", "char_count": 234, "limit": 280, "within_limit": true },
"tiktok": { "caption": "v0.27.0 is out.\n\nThe WAF used to ban my own phone behind a Tailscale hop. Now it resolves the real client. Zero false bans.\n\nTelegram Mini App V6 shipped: real chat scopes, wallet-style metrics, a live Board sheet.\n\nCodex and Gemini just joined Claude and Grok on the model roster. Four providers, one gateway.\n\nCost-tiered routing plus saved presets: snapshot a routing mix, restore it whenever.\n\nI approved once. 25 agents shipped it.\n\nroboco.tech\n\n#buildinpublic #aiagents #devtools #indiehackers", "char_count": 499, "limit": 2200, "within_limit": true }
}
}
Smoke-test invariants
release-0.27.0.test.js extends the panel-demo register checks: both vertical.html (1080×1920) and square.html (1080×1080) parse with data-duration="40" and the HyperFrames params, the kit CSS/JS wiring is present, four feature cards each carry a progress-to-completed pill swap and include the exact titles ("WAF knows the real IP", "Telegram Mini App V6", "Codex and Gemini join", "Cost-tiered routing"), the stats overlay renders the three-line receipt (1 release / 4 providers / 0 bans), the cursor and toast appear, the outro shows "roboco.tech", no external scripts are loaded, no em dashes slip into on-screen copy or captions, and — the finding a prior revision cycle on this composition caught — the four card-beat gaps are asserted non-uniform (new Set(gaps).size > 1), regression-guarding against silently reusing release-0.26.0's identical 5.0s/5.0s/5.0s spacing.