Render a configurable iMessage conversation inside a properly framed phone, then a brand end card. Uses the original send/receive sounds and a shared frame timeline for text, typing, scrolling and sound. Free local Playwright + ffmpeg assembly; optional image/music generation belongs to separate gated capabilities.
59
74%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
Fix and improve this skill with Tessl
tessl review fix ./skills/ads/capabilities/render-imessage-chat/SKILL.mdMake a texting-story ad with the chosen contact names and phone time, a proportional phone, an inset Dynamic Island and the original iMessage sounds. The brand, story and background can change. Images are optional and can appear anywhere in the conversation; product links and music are optional too.
This rebuild preserves the shell and sounds from the approved Clinikally Goa build. It fixes missing identity binding, the island touching the screen edge, light-mode header colors, early/missing group names and capture timing drift. Every movie frame and sound cue uses the same timeline; browser startup cannot trim the beginning or ending. No paid API is needed to render or repair the UI.
On a 9:16 canvas the phone now sits clear of the TikTok/Reels controls by
default: the newest message, including the punchline, always stays above the
bottom caption band and left of the button rail (safe_area).
Read the reference and authoring rules.
The calling recipe supplies NEW names, copy, brand facts and real assets.
scripts/config.example.json is a fictional, runnable example, never defaults.
thread.participants, thread.title.thread.clock, preserving the user's chosen text.thread.messages. Casual spelling and emojis are allowed.attachment entries at their authored positions in
thread.messages; either participant may send them. Never reorder by type.theme: "dark" | "light".background_image; neutral when absent.dynamic_island: true | false; true floats inside the screen.render.sh --music; no bed means SFX only.Exactly one participant is self:true. A DM has two participants; its header
reads the other participant's name and derives the first initial unless supplied.
A group requires a title and named contacts. Missing names fail before capture.
There is no demo-name fallback. Changing the config changes the visible name.
When a name changes, update any derived initials too. Keep explicit user-supplied
initials only when they still match the requested identity.
Requires Node 18+, Python 3, ffmpeg with libx264, ffprobe and Playwright Chromium.
Install dependencies in the fetched scripts folder, then launch/close that
script's own Chromium before any optional paid image/music call. Preserve its
cwd, NODE_PATH and PLAYWRIGHT_BROWSERS_PATH. gooseworks doctor --renderer-script "/absolute/path/scripts/record-chat.js" can check that runtime. If unavailable,
use a bounded free createRequire(actualScript) launch/close probe (15-second
launch timeout, 20-second whole-process limit). Cache presence alone is not proof.
cd scripts
npm ci
# Install Chromium only if the free launch probe says it is missing:
# npx playwright install chromium
node record-chat.js --config /absolute/path/config.json --out-dir /absolute/path/working/preview --preview-only
bash render.sh --config /absolute/path/config.json --out /absolute/path/finals/master-final.mp4
# Optional: append --music /absolute/path/bed.mp3Preview produces chat.html, chat-preview.png and
master-chat.safe-area.json; the HTML exposes window.__renderAt(seconds) and
window.__safeAreaReport() (canvas-pixel boxes of the newest row and any
data-safe-keep sheet or dialog) for frame inspection. Full render keeps those,
master-chat.mp4, .timeline.json, .sfx.json, the end-card HTML/PNG/MP4 and
the finished master. The recorder checks the safe area on every output frame and
fails on a violation. check-render.py verifies dimensions, audio stream,
frame count, complete ending and the safe-area report;
python3 scripts/check-render.py --safe-area <work>/master-chat.safe-area.json
checks a preview alone. Review the ACTUAL master after every repair;
these technical checks do not establish creative acceptance.
Individual record-chat.js, render-end-card.js and stitch.sh commands remain
available. render.sh produces the 1080×1920 recipe master. The chat recorder
also supports even preview dimensions; the phone must fit with a margin.
thread or thread_path. Relative files resolve against config.json.from participants. Types: text, typing, timestamp,
attachment, tapback. Reactions target an earlier message ID and carry an emoji. Typing immediately precedes a received text/attachment from the same
person. Self messages type in the composer, including complete emoji graphemes.src local image/data URI, presentation:"photo" for a
photo or "rich-link" for image + flush meta card + title/domain/chevron.
Optional dwell_sec overrides its default 3.6-second reading hold.
Text-only chats need no images. One or several attachments can come first,
between any messages or last; preserve the user's placement and sender.
Never require an opening photo or a product image at a fixed beat.thread.clock sets the displayed status-bar time, such as 10:24 or
18:07. Bind the chosen time; do not replace it with a demo time. Only when
absent does the shell use its neutral 9:41 fallback. In-chat timestamp labels
are separate message inputs, not a required fixed timestamp.image_path, or real logo_svg_path, logo_image_path,
inline logo_svg or wordmark_text, brand colors, CTA and optional benefits.
stars defaults to 0. Ratings require approved proof_text.
An artwork path replaces the complete template; check its copy and CTA first.timing can override the
named pacing fields in record-chat.js; ending hold must be at least 0.5 seconds.safe_area keeps the conversation clear of the platform controls (QA-60).
Omitted: on for 9:16 canvases, off for other shapes. true uses the
review-finished-ad bands (top 220, bottom 400, right 140, left 0 px at
1080×1920, scaled to the canvas). An object such as {"bottom":480} overrides
single bands in output pixels; omitted keys keep the defaults. false restores
the old centred full-height phone exactly.safe_area on, the phone is the largest proportional size whose
conversation viewport sits inside the zone (an 8 px inset), centred unless that
is unsafe, then moved only as far as needed. At 1080×1920 that is zoom about
1.915 with the phone 16 px from the top. Every row is clipped to that viewport,
so the newest row is safe on every frame. The composer, home bar and group
avatars may sit in the bands; typed text reappears as the newest row.zoom is a ceiling while safe_area is on: kept when safe,
otherwise lowered to the safe maximum with a log line (the recipe seed
zoom: 2.1 becomes about 1.915). Set safe_area:false to keep it exactly.The send/receive MP3s are byte-identical to both archived Clinikally and Wonderbly builds. Keep them. No substitute ringtone, notification-cascade sound or generated pop. One cue per real text/attachment; none for typing or composer keystrokes. Picture reveals and cues share fixed output-frame times; the audible onset follows the first visible reveal frame. The mixer strips leading silence and limits peaks.
Full checkouts use assets/sfx. Text-only catalog packages use the hash-checked
scripts/sfx-embedded.json fallback. Keep that file byte for byte. --sfx-dir
can override the source explicitly. Missing, silent, corrupt or LFS-pointer audio
stops the render. After an intentional MP3 replacement, regenerate the embedded
copy with python3 tests/test_stitch.py --write-embedded.
Run node --test tests/test_chat.js, node --test tests/test_safe_area.js and
python3 -m pytest tests/test_stitch.py. Run one test file at a time; each test
opens and closes one Chromium.
Browser tests cover changing names/time/background, text-only chats, attachment
placement, blank-name rejection, inset hardware, dark/light chrome, group labels
after typing, Unicode composer text and long threads. test_safe_area.js walks
every frame of tests/fixtures/long-group-thread.json (16-message group thread,
wrapped punchline) and asserts the newest row stays above y 1520 and left of
x 940, that a bottom sheet in the band fails, and that safe_area:false keeps
the old layout. CI runs both browser files. Audio tests cover
fetched-package delivery and limited overlapping cues.
Fix the configuration error and rerender locally. UI defects never justify paid
generation. Watch the final for the selected name, readable bubbles, smooth scroll,
correct sender labels and sounds, complete last message and correct brand end card.
Use review-finished-ad for brand/copy review when called by the recipe.
The current renderer combines the fixed-frame repair with the lessons from the live-capture audit. Read [[references::references/imessage-reference.md]] before authoring.
PHONE.keep to the screen bottom and mark sheets data-safe-keep.cc3e518
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.