Commit Graph
184 Commits
Author SHA1 Message Date
Miguel Ángel 6092785348 fix(studio-server): preview loads root-path media, images and CSS from the project, as render does (#6552)
* fix(studio-server): preview loads root-path media, images and CSS from the project, as render does

* fix(studio-server): preview writes root paths project-relative, the form Studio reads back
2026-10-27 12:29:17 +00:00
Miguel Ángel 859281a60d fix(studio-server): preview resolves root-path module imports to the project (#6534)
* fix(studio-server): preview resolves root-path module imports to the project

* test(studio-server): preview's own scripts make no module import; lint flags a broken import map
2026-10-27 09:48:42 +00:00
Miguel Ángel 5d7cbc2171 feat(lint): warn when a sub-composition's linked stylesheet names its root id (#6533)
* feat(lint): warn when a sub-composition's linked stylesheet names its root id

* chore(skills): regenerate the skills manifest

* fix(lint): the linked-stylesheet hint names fixes that lint clean
2026-10-27 08:48:57 +00:00
Miguel Ángel 5a28b4e7eb fix(lint): a media src whose file name holds # or ? says to write it percent-encoded (#6495)
* fix(lint): a media src whose file name holds # or ? says to write it percent-encoded

* refactor(lint): the url-safe hint encodes with the shared encodeUrlPath
2026-10-27 01:10:49 +00:00
Abdullah M. Nazif 5279b6f476 fix(parsers): resolve media source writes in their lexical scopes (#5255) 2026-10-26 18:59:36 +00:00
Aron Lee 95aba51e20 test(lint): cover three caption rules that had no tests (#5436)
* test(lint): cover three caption rules that had no tests

Refs #5388

* test(lint): pin the caption CSS gate on the group textShadow rule

Refs #5388
2026-10-26 17:25:18 +00:00
Muhtasim Munif Fahim 64cb6556ef fix(lint): add fix hints to the variables-entry and CSS parse findings (#5447)
Closes #5392
2026-10-26 14:57:23 +00:00
Miguel Ángel 0d7d5ee723 fix(lint): a room-tone audio over a cut is not a double source (#6105) 2026-10-25 23:07:26 +00:00
Miguel Ángel e003815c30 feat(lint): flag CSS rules that select on the inline style attribute (#6261)
* feat(lint): flag CSS rules that select on the inline style attribute

* docs(lint): keep the new and corrected rule entries to short lines
2026-10-25 23:06:38 +00:00
Akshar Patel 04df17cccb fix(sdk): keep font and image declarations after object edits (#6281)
* fix(sdk): preserve font and image declarations after object edits

* fix(studio): preserve variable object defaults when editing declarations

* fix(studio): show font and image values in bind warnings
2026-10-25 23:01:32 +00:00
Miguel Ángel 33685ef2be fix(core): a self-closed audio group no longer hides the clips after it (#6344)
* fix(core): a self-closed audio group no longer hides the clips after it

HTML does not close a custom element on `/>`, so `<hf-audio-group id="sfx" />` takes in every
later sibling, and the runtime's inert style (`display:none`) hid them in preview, snapshot and
render. The style is now `display:contents`: an empty group still makes no box, and anything
inside one renders as written. A lint warning names the elements a group holds, and the docs and
mixing reference show the group closed.

* style(core): cut the audio group comments to the comment length limits

* fix(core): a muted audio group still hides what sits inside it

A muted bus (`data-hidden`) is hidden by an inline display:none that the bus rule outranked, so
clips inside a muted self-closed bus kept painting while their audio and video frames dropped.
2026-10-25 21:44:32 +00:00
Miguel Ángel ad6771c435 feat(lint): warn when a scene's styles sit outside its template (#6317)
A sub-composition file with a <template> is mounted from the template alone, so a <style>
in its <head> (or a stylesheet link in its body) never applies in preview, snapshot or render.
Warn on it, with the fix in the message: move it inside <template>.
2026-10-25 15:26:20 +00:00
Miguel Ángel 2bff65b0cd feat(lint): warn when a round-capped line hidden by its dash settings can still show a dot (#6291)
* feat(lint): warn when a round-capped line hidden by its dash settings can still show a dot

* docs(lint): shorten the round-cap dash rule entry
2026-10-25 07:25:17 +00:00
James RussoandClaude Opus 5.5 685cd75a92 fix(studio): load GSAP core when only a GSAP plugin is on the page (#6260)
* fix(studio): load GSAP core when only a GSAP plugin is on the page

The Studio preview checked for GSAP core by matching "gsap" anywhere in a
script src, so the MotionPathPlugin it had just injected (served from
gsap@x/dist/) counted as core. A composition with a motion path and
Studio-edited motion but no GSAP script of its own then got the plugin and
no core, and its animations did not run.

Detect core by file name (gsap.js, gsap.min.js, or a vendored
gsap-<version>.min.js) on any host or path, and inject Studio motion's core
before the MotionPathPlugin so the plugin can register onto it. Apply the
same core-only check to the sub-composition preview fallback and the
missing_gsap_script lint rule.

Refs #6205

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* refactor(studio): keep GSAP core detection within the comment budget

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(studio): load GSAP core ahead of head plugins and only on a real core script

Studio motion and sub-composition previews now insert GSAP core before the
first GSAP plugin script in the head, so an authored MotionPathPlugin or
CustomEase registers on core instead of running first. Core detection keys on
a core script (src, inlined marker, license banner or library body), not on
usage like gsap.registerPlugin() or gsap.defaults(). The missing_gsap_script
lint rule drops the same usage-based and plugin-banner shortcuts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 02:37:08 +00:00
Akshar Patel e2129c3915 fix(lint): allow scoped descendant zoom (#6254)
* fix(lint): allow scoped descendant zoom

* chore(lint): account for scoped zoom bundle size

* fix(lint): retain canvas zoom guard for runtime identity

* fix(lint): preserve runtime identity in selector functions

* fix(lint): retain identity across uncertain selector branches
2026-10-25 01:53:04 +00:00
Akshar Patel f21c3e3e7e fix(lint): accept GLSL script blocks without JavaScript syntax errors (#6227) 2026-10-25 00:28:34 +00:00
Akshar Patel 035a5c0ae1 fix(lint): accept final font-family descriptors without semicolons (#6085) 2026-10-25 00:27:51 +00:00
Miguel Ángel c324a40a87 fix(cli): lint accepts one composition file as well as a project directory (#6202) 2026-10-24 08:22:53 +00:00
Rudy Mizrahi Celekli cb40dfb456 fix(lint): reject parser-invalid variable defaults (#5314) 2026-10-24 08:15:52 +00:00
Akshar Patel 629dd8fb76 fix(lint): ignore inactive mask declarations (#6186) 2026-10-24 07:18:27 +00:00
Abdullah M. Nazif 191caf943a fix(lint): check timeline registration before explicit font loads (#5363) 2026-10-24 04:11:29 +00:00
Abdullah M. Nazif a1b23f1e4d fix(lint): read actual font CSS declarations (#5221) 2026-10-24 04:11:15 +00:00
yi111andYi-111-a 0648f1ed8d fix(lint): recognise .avif and .m4a in the media src kind rule (#4779)
The media_src_kind_mismatch rule classified .mp3 and .jpeg but not .m4a or
.avif, so an <img> pointed at an audio file or a <video> at a still slipped
through for those two formats. Add .avif to IMAGE_SRC_EXT and .m4a to
AUDIO_SRC_EXT.

.ogg stays out: its container can carry Theora video as well as Vorbis or
Opus, so the extension does not name the kind. .m4a is the opposite case --
it is audio-only, and video in the same MP4 container is .m4v, which
VIDEO_SRC_EXT already lists. The .ogg case keeps its existing coverage and
gains an explicit unknown-extension case alongside it.

Refs #4382

Co-authored-by: Yi-111-a <Yi-111-a@users.noreply.github.com>
2026-10-24 00:43:28 +00:00
yi111 3fa94b2156 test(lint): cover three media rules that had no tests (#5402)
duplicate_media_discovery_risk, self_closing_media_tag and
placeholder_media_url were defined in media.ts but no test in
media.test.ts mentioned any of their codes.

Adds a trigger case per rule (asserting code and severity) plus
negative cases that pin the boundary each rule actually keys on:
one fingerprint field differing for duplicate discovery, an
explicitly closed tag, and a real src.

Each new test was checked to fail against a disabled rule.
2026-10-24 00:28:17 +00:00
Miguel Ángel d1aeb31bf4 fix(lint): tell an agent where to move a video nested in a timed wrapper (#6113)
* fix(lint): tell an agent where to move a video nested in a timed wrapper

The missing-start hint said to add data-start="0" even when the video sat
inside a timed wrapper, which then trips the never-both rule. The hint now
names the wrapper and its start, and following it lints clean.

* fix(lint): hint the innermost timed wrapper of a nested video

The hint used the outermost timed ancestor, so scene > clip > video told the
agent the scene timing. It now takes the innermost enclosing timed element,
counts nesting depth so a closed sibling no longer hides its parent, omits the
duration when the wrapper has none, and leaves audio on the plain hint.

* fix(lint): a closed timed sibling no longer counts as a wrapper of the next video

The nesting check compared how many closing tags sat between a timed element
and a video with how many same-name opening tags did, ignoring order, so an
unrelated later sibling cancelled the close. It now walks them in document
order and stops when the element closes.
2026-10-23 06:42:06 +00:00
Miguel Ángel a8ac55dc30 fix: data-volume multiplies the volume lane in preview and render (#6068)
* fix: data-volume multiplies the volume lane in preview and render

* fix: a clip's data-volume plays ahead of its audio effects, in preview and render

* feat(studio): the media panel shows a clip's Gain beside its Volume level

* fix(audio): cap gain times volume lanes through each ramp

* chore(core): account for shared volume envelopes in package budget

* fix(core): restore static gain when a playing lane is removed
2026-10-22 20:12:49 +00:00
WaterrrForeverandClaude Opus 5.5 688229314f feat(skills)!: publish seven knowledge skills aligned with Studio and retire the workflow skills (#5651)
* refactor(skills): fold six domain skills into hyperframes and media-use guides

hyperframes-core, -animation, -keyframes, -creative and -studio move to
skills/hyperframes/{core,animation,keyframes,creative,studio}/, and
hyperframes-audio moves to skills/media-use/mix/. Each moved SKILL.md becomes
a frontmatter-free GUIDE.md so installers that scan recursively for SKILL.md
no longer see them as skills; the published list goes from 21 to 15.

Relative links, code paths and skill-name mentions are rewritten to the new
locations, the catalog surfaces, marketplace core-skills list, fallback core
set, module-copy targets and skills-manifest.json follow, and
`hyperframes skills update` now removes the retired skills (RETIRED_SKILLS)
when they are still installed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(skills): slim the core entries and give media-use a HeyGen and a free path

The four entries every run opens are rewritten to what an agent cannot infer:
hyperframes 21.8 KB to 7.7 KB, hyperframes-cli 24.8 KB to 6.9 KB,
hyperframes-registry 9.9 KB to 4.3 KB, media-use 9.7 KB to 4.9 KB. Detail moved
whole into references (routing close calls and creator edits, CLI pin upgrades,
catalog search tiers, CLI conventions and feedback reports).

- hyperframes: state table, routing, the render contract, a terminal work loop
  (check, snapshot, preview before render), the desktop-app hand-off, and a
  table of the folded guides.
- media-use: one table of HeyGen versus free local tools per need; HeyGen
  sign-in, credits and the CLI's video-agent, avatar, clone, lipsync, translate
  and clipping commands; every free path works with no account.
- scripts/check-core-skills.mjs holds entries and descriptions to budget and
  checks every taught `npx hyperframes` command and path; runs in lint.
- Core entries are exempt from the formatter: its table padding cost ~1.5 KB
  per entry on every run.
- Content tests follow the text to its new home.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): plugin runs skip skills update; media-use explains HeyGen sign-in and TTS setup

The smoke eval showed a presenter request never reaching media-use and the
free voiceover failing on a missing kokoro-onnx; plugin runs also ran the
skills update the plugin rules forbid.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore(skills): drop a committed Python bytecode file and ignore bytecode

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(skills): align the entry with the Studio skills and keep the workflows' measured facts

The entry drops routing and the intent interview and carries the contract, the
editable-project rules and the work loop. Studio's format, runtimes, motion,
look and captions references come over for terminal use, frame presets move to
assets/, media-use gains Studio's mixing and local-command references, and
film.md and overlays.md keep what the workflow skills measured.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(skills)!: retire the workflow skills and folded guides; publish seven knowledge skills

The skill pack now publishes exactly seven skills - hyperframes,
hyperframes-cli, hyperframes-registry, media-use, figma, slideshow and
remotion-to-hyperframes - carrying knowledge, not process. The eight
workflow skills, the folded domain guides and the routing/interview
references are deleted; their measured facts already live in the
hyperframes references (format, runtimes, motion, look, captions, film,
overlays) and media-use's mixing reference.

- bake-basemap.mjs and locate.mjs move to skills/hyperframes/scripts/
  (bake-basemap gains --help); extract-audio-data.py moves to
  media-use/audio/scripts/ for the caption motion reference.
- skills update: the eight workflows join RETIRED_SKILLS so existing
  installs prune them (by lock attribution only, since their names are
  generic); naming a retired skill reports where its knowledge went and
  succeeds instead of failing as unknown.
- Capture prompts point at the hyperframes skill's references/film.md;
  the live-map warning names the new bake-basemap path.
- Content tests that pinned knowledge are retargeted to the kept
  references; tests of deleted workflows, scripts and process go.
- Catalog surfaces, templates and docs describe the seven skills.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): resolve the local.md captions link and allow capture's output files in citations

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): regenerate the skills manifest from tracked files only

The committed media-use hash included an ignored __pycache__ bytecode file present
in the local checkout, so CI's clean checkout hashed differently.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(docs): restore generated catalog pages and docs navigation to main

Catalog PRs commit item sources only; CI regenerates the pages from the edited
registry sources. docs/docs.json is catalog navigation, so its Workflows group
rename is left for a non-catalog PR.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* build(cli): raise the CLI size budget for the skill references it bundles

The CLI bundles skills/hyperframes, which now carries the folded domain guides and
frame presets (155 KB to 1.08 MB in git). Budgets are the CI-measured size plus 5%:
packed 7526634, unpacked 22593213.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test: keep repo-sweep guards meaningful after the workflow skills left

The last shell ffprobe caller and most root index.html examples lived in retired
skills. The shell detector is now pinned on a literal command line, and the
structure-rule sweep needs more than 5 roots (11 remain) rather than 30.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(skills): slim figma, slideshow and remotion to the Studio shape; restore the storyboard board

slideshow 33.5 KB to 4.9, figma 18.2 to 6.2, remotion-to-hyperframes 11.0 to
4.9, each keeping the facts only it holds (the figma motion helpers, verify gate,
storyboard decoding and usage beacons; present and hand-off; the Remotion lint,
SSIM check and API map). The board page format from the storyboard recipe comes
back as hyperframes/references/storyboard.md: the desktop app's Storyboard tab
reads boards/storyboard.html by its frame, title and time classes. The entry and
the init template now say to snapshot the moments an edit changed before
replying, which the terminal eval found skipped in 22 of 38 runs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): manifest without local bytecode; Studio's edit prompt names kept files

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): render when the person asks for the file or for no stops, not only after approval

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): keep the voiceover carve in media-use; presets stage their own fonts

Review follow-ups on #5651:
- carve.mjs and its tests move to skills/media-use/audio/scripts; the audio
  docs, mixing.md and two Studio comments point there.
- code-editorial's FRAME.md says to copy its bundled WOFF2s and add the
  @font-face blocks, instead of promising the retired build-frame.mjs.
- media-use states the host rule before the HeyGen sign-in steps.
- storyboard-format marks transition_in as a note the agent builds itself.
- Two stale citations (creator-editing-recipes.md, sub-compositions.md).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore(skills): regenerate the manifest without local bytecode

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): the CLI render step follows step 4's rule, not approval alone

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(ci): run the core-skills check from lint again after the main merge

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(skills): films that tell a story start from three storylines; maps use real imagery

Opus 5.5 arm D read motion.md and applied it, but its launch film was a
feature list (pain, three feature cards, proof, logo): film.md left story
to the model. And the map case never opened film.md, so it drew Natural
Earth outlines instead of baking satellite tiles.

- references/story.md: the generic half of Studio's moment-film story and
  craft (one thing, three storylines with world and device, the hook,
  beats, worlds, copy, brand, tells of a generated film). The person picks
  a storyline, or the agent takes its recommendation when asked for the
  file or for no stops.
- The entry's work loop names the storyline step for films over ~15 s; the
  references table routes stories to story.md and maps to film.md.
- film.md points at story.md and gives maps and real places a section.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): story.md follows the entry: no voiceover unless asked

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): bake-basemap starts from a globe and stops waiting out its idle timeout

The Opus 5.5 map run spent 13 of its 38 minutes on parallel bakes that
stalled and 15 more recording 300 frames, after rewriting the script because
it could only start on a flat map.

- PROJECTION=globe with a seeded starfield and atmosphere, and FROM/PAN to
  turn the globe to CENTER before the dive.
- Each frame polls for loaded tiles instead of waiting for an "idle" event
  that had often already fired: 30 frames went from 198-219 s to 10-13 s
  locally with byte-identical frames.
- Progress prints seconds a frame and minutes left; film.md shows the globe
  call and says to run one bake at a time.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): render in the foreground; wait on a detached render's log, never pgrep -f

Both Opus 5.5 explainer runs started a detached render, then waited with
`until ! pgrep -f "hyperframes render"`, which matches its own loop: each
render took 88-117 s and the wait sat for the tool's full 10 minutes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(skills): route each kind of film; storylines carry look and sound; add review and sound

The entry routes by request (launch, explainer, music, data, vertical, 3D,
character) to a self-contained file under references/types/, and a film
over ~15 s now gets music and sounds on its visible actions unless the
person asked for silence. Each storyline names its look and its sound, so
picking one picks both; STORYBOARD.md frames carry `reads` and `sound`.

review.md is the whole-film pass: the check warnings, the review sheets,
seven questions to look for, P0/P1/P2 notes and REVIEW.md. motion.md gains
eases by role, six title entrances, a reading-time rule and nine measured
transitions; look.md a design plan before the first scene, with presets
kept for decks and named picks. story.md adds the second default that
generated design falls into, with what to do instead.

The blank template and format.md no longer teach the fade-and-slide-up
entrance on a dark canvas.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(cli): check warns on unread lines, dead stretches, small type, empty frame 0 and silence

A coding agent cannot watch its film, so check now measures what a viewer
notices over time. A new `craft` section holds warnings only (exit 0
unless --strict), sampled on the dense 8 fps grid the overlap pass
already seeks, skipped for --range checks:

- line_unread: a reading-size line (>= 30 px tall at 1080) on screen
  under 0.4 s for 1-2 words, else under 3/4 of 0.5 s + 0.3 s a word;
  the film's last line needs 2 s. Short lines are re-measured between
  samples to ~0.01 s before judging. Captions-track and
  data-layout-ignore text are exempt.
- dead_stretch: more than 3 s where the keepsMoving liveness signature
  does not change, outside the final 3 s and any data-hold element;
  quiet when sweep_static fires or a whole-composition keepsMoving
  assertion owns the limit.
- text_below_floor: text under 2.6% of the short side (28 px on 1080)
  held 0.5 s, one finding per file listing every element.
- first_frame_empty: frame 0 paints only the background.
- film_silent: over 15 s with no unmuted audio or audible video.

Each message names the fix and the skill reference to read. keepsMoving
now also leaves the film's last 3 s (a still end card) out of its
static runs, so the two agree.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(cli): snapshot --review captures a beat sheet, seam strips and a phone sheet

snapshot --review picks the frames that show a film over time instead of
evenly spaced ones, in the same single browser session:

- review-beats.jpg: one settled frame per scene at its start + 0.3 s,
  plus one per timed data-beat="name" element, labelled time and id.
  Scenes are the composition's top-level timed clips and sub-composition
  hosts, read like `timeline` reads rows (captions and audio excluded)
  and timed by the runtime's start resolver over the live page.
- review-seam-NN.jpg: frames at -0.2, -0.1, 0, +0.1, +0.2 s around
  every cut (each scene start, and any scene end no scene starts at).
- review-phone.jpg: the beat frames at 360 px wide.

--composition scopes the review to one sub-composition; with fewer than
two scenes the beats spread evenly and a note says so. snapshot also
gains --json: one envelope with every frame's path and time and, under
--review, the scenes, beats and seams behind the sheets; progress moves
to stderr.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(cli): craft warnings stay out of --strict; a hold gets a real next action, not a caret

Craft findings (unread lines, dead stretches, small type, an empty first
frame, silence) are judgment calls a film may break on purpose, so
--strict keeps gating defects only. The check reference no longer
suggests a blinking caret to keep a still frame moving.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(cli): review spreads beats when every scene starts together; map credits skip the type floor

A map film whose basemap and overlay both start at 0 s got one beat
frame; the even spread now keys on distinct scene starts. film.md tells
agents to mark the imagery credit data-layout-ignore and back it, since
the new type floor flagged it on the first real run.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(cli): review frames each scene at its middle too, not only 0.3 s in

On the first real explainer, 0.3 s into a scene showed a bare colour
field before its title entered. Each scene longer than about a second
and a half now adds a frame at its middle, so the sheet shows what the
scene builds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(skills): a film is built from a numeric SPEC.md, with defaults banned by name and a look test

Studied in full: flxrnc's Motion Lab (vague vs directed prompt), 0xMovez's
course, EianLu's riso skill, agentchengfeng's style samples and the two
Opus 5.5 guides. Their common point: adjectives are where the default look
lives, so the agent now writes SPEC.md before any scene (GOAL, OUTPUT,
TIME, LOOK, MOTION, SHOTS, BANNED, QA), with a number for every adjective.

spec.md bans the defaults by name, unless the brand or reference uses
them (a dark void with small things floating, glow and particles, the
cream-serif-mono-label look, fade-and-slide-up, corner brackets), and
grows that list after the first render. Each storyline gets a 3 s test of
its hero shot before the pick; a look is written as the rules of one
medium. Composition numbers: the hero fills 40% of the frame, one hero
move per shot, the shot's information in within 2.5 s then a held poster.

motion.md adds springs by damping ratio, shorter exits, overlap, stagger
and the beat grid; review.md reviews as a harsh director and grows BANNED;
vertical.md takes the riso skill's measured safe band; explainer.md the
shapes of information.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): story before look; every object reads as itself; posters only for info cards

The first spec-driven C02 swapped the dark default for a flat Swiss
poster, and the story went with it: two black bars nobody read as mirrors,
slogans instead of cause and effect, a storyline picked for its picture.

The spec now serves the story: a storyline is picked for the story a
stranger would follow, the look test asks first whether a stranger
understands what they see, and SPEC.md gains an OBJECTS line giving each
thing the material and details that make it read without a label. The
2.5 s held-poster rule is limited to information cards; in a story film
every shot causes the next. Explainers start from a person or situation
the viewer knows. Review names every object as a stranger would and
treats one that needs explaining as a P0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): storylines are three films in words, each with a device and a form

The three storylines are pitched as text and the person picks from them;
nothing is built for a storyline before the pick, so the 3 s hero-shot
look test is gone. Ideas pair a device with a form, five or more first,
keeping the three that differ most, and the one stop states the rest in
an assumption line that names every paid item beside its free option.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* ci: list main's new embedded-captions hf-cli test among the retired workflow files

The merge with main removed it with the rest of the retired skill, and the
deletion guard rejects any removal it has not been told about.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(core): keep the audio preset comment within core's size budget

The comment's new citation to media-use's mixing reference made the published
package 14 bytes larger than its budget, which sits at main's exact size.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(cli): craft keeps its own 8 fps grid; comments back within the checks

Review on #5651 found that the merge moved craft onto the overlap pass's
times, which main caps at 180, so a brief line in a long film fell between
samples and line_unread missed it. Craft now builds its own grid at 8 fps up to
600 samples, as before the merge, and a 60 s film with a line shown for 0.2 s
pins it.

The Comments check passes again: REVIEW.md and STORYLINES.md join the files a
skill writes into a project, the comment lines this branch added come back
under each file's share at the merge-base, and the audio preset comment drops
a word the check reads as history.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 19:19:31 +00:00
Miguel Ángel 50e7e8df69 fix(lint): check no longer fails projects that inline the GSAP library (#6018)
* fix(lint): check no longer fails projects that inline the GSAP library

non_deterministic_code skips a script that is GSAP itself (producer inlining comment
or GSAP banner), using the detector missing_gsap_script already had, now one shared
helper. The author's own Math.random/Date.now calls are still flagged.

* fix(lint): only GSAP's own code is exempt from the determinism check

Exempt the parsed GSAP UMD statement that carries its banner, not every script that
mentions GreenSock. Strings, comments, spoofed banners and author code before or after
the library in the same script are checked again.

* refactor(lint): name the GSAP blanking step instead of commenting it

* fix(lint): exempt only GSAP's verified wrapper call from the determinism check

Recognise GSAP's UMD wrapper by structure (factory IIFE called with this and a function,
with a real define.amd member) anywhere in the tree, and blank only that call. Author code
joined by a comma or wrapped in an outer IIFE is checked; define.amd text is not enough.

* fix(lint): a bundle wrapping GSAP no longer inherits its determinism exemption

The define.amd check reads only the wrapper's own body, and each GSAP banner belongs to the
wrapper right after it, else the innermost one containing it. An outer wrapper or UMD bundle
around pasted GSAP keeps its author code checked.

* fix(lint): a banner inside a bundle no longer exempts the bundle around GSAP

A banner introduces the wrapper after it across whitespace, ! ; ( and comments, so producer
comments and statement separators bind to GSAP itself. The fallback owner for a banner
inside a wrapper must wrap no other wrapper, so a bundle around GSAP never inherits it.

* fix(lint): author code bundled with GSAP keeps its randomness checked

A GSAP banner now exempts only the UMD wrapper call that starts right after
it (skipping whitespace, comments, `!`, `;` and `(`). The fallback that gave
a banner to the innermost enclosing wrapper is gone: Rollup's UMD output for
an ESM import of GSAP keeps the banner inside the one factory it shares with
author code, so that fallback exempted the whole bundle, author
Math.random() and Date.now() included.

Tradeoff: unminified dist/gsap.js pasted inline has its banner inside its
own factory, so it is flagged again, as on main. The minified gsap.min.js
and producer-inlined copies stay exempt.

* test(lint): the Rollup bundle case reuses the GSAP banner constant

* fix(lint): a script name that mentions gsap no longer exempts the bundle it heads

The producer's inlined-source comment now marks library code only when its
URL path is an npm gsap dist file (gsap or gsap@<version>, then /dist/<file>.js,
query and hash ignored). A Rollup/Terser bundle of GSAP plus author code
inlined from gsap-composition.js, scene.js?vendor=gsap or another domain's
gsap-demo/scene.js keeps its own Math.random() and Date.now() checked.
2026-10-21 17:21:44 +00:00
Miguel Ángel 18207de1c4 fix(lint): place each scene's audio at its host's start in the audio overlap check (#6005)
* fix(lint): place each scene's audio at its host's start in the audio overlap check

* fix(lint): drop internal tags from two comments

* fix(lint): check audio in scenes chained by a reference start
2026-10-21 06:24:33 +00:00
lbianlbianandClaude Opus 5.5 178eea5634 test(lint): make catalog and scaling tests pass on Windows (#5579)
structure.catalog.test.ts found root compositions with
endsWith("/index.html"), which never matches the backslash paths
path.join returns on Windows, so it counted 0 roots and failed.
It now compares basename(file) to "index.html".

The stripJsStringLiterals scaling test read one scan's CPU time from
process.cpuUsage, which Windows updates in ~15.6ms ticks. The 10k-input
scan finished inside one tick, read 0ms and made the ratio Infinity.
Each sample now repeats the scan until it has spent 50ms and divides
by the run count; the asserted ratio is unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 06:19:23 +00:00
Miguel Ángel 5658f70065 fix(core): color grading applies to canvas compositions (#5860)
* fix(core): color grading applies to canvas compositions

data-color-grading on a <canvas> was ignored by the runtime and refused by
lint, the Studio panel and the CLI writer. One source-tag list in the grading
contract now feeds all of them. A canvas is sized from its drawing buffer and
re-uploaded on every seek and transport tick.

* fix(core): keep the comment share of grading files at the base
2026-10-19 00:38:53 +00:00
Miguel Ángel d1123299f8 fix(lint): back-to-back audio clips no longer count as overlapping after float rounding (#5837)
duplicate_audio_track compared raw float ends, so 21.03 + 0.1 (21.130000000000003)
overlapped a clip starting at 21.13. The one-instant span helpers move from core's
clipFacts to parsers, which lint and core both depend on; core re-exports them and
the rule uses spansOverlap.
2026-10-18 22:15:23 +00:00
Miguel Ángel a971c2bb25 test(lint): shader lint tests pass on macOS, where the temp folder is a symlink (#5851)
projectShaders reports real paths, and macOS's temp folder is /var, a link
to /private/var, so the tests' expected paths never matched there. The
fixture root is now the real path.
2026-10-18 16:33:06 +00:00
Miguel Ángel e6ce733796 fix(lint): stop flagging dir on <html>; a render test now guards RTL export (#5744) 2026-10-17 03:25:12 +00:00
Miguel Ángel eab6ca15b1 fix(cli): timeline lists clips nested in a timed element and lint flags ones that never show (#5732)
* fix(cli): timeline lists clips nested in a timed element and lint flags ones that never show

* docs(lint): wrap the nested-clip entries

* docs(skills): format the nested-clip note and refresh the skills manifest

* fix(cli): keep timeline edits from colliding with a clip's own wrapper

* fix(cli): cut clips nested in a wrapper at their sub-composition's in-point

* refactor(lint): split the nested-clip visibility checks to keep the audit green

* style(cli): restore the timeline test's formatting
2026-10-17 02:27:17 +00:00
Rudy Mizrahi Celekli 0bff8342c3 perf(lint): strip stylesheet comments once per canvas zoom scan (#5712) 2026-10-16 19:09:14 +00:00
Miguel Ángel d4c8d76fff fix(lint): warn on unbounded cosine powers in shaders (#5640) 2026-10-15 05:40:38 +00:00
Xuanru Li 45825faa5d fix(inspector): grep timeline windows, continuation, candidates and timeline text from the Canary dogfood (#5595)
* fix(inspector): report nested hosts' authored window, one location per source line

The runtime strips data-duration/data-end from nested composition hosts after
boot and keeps them as data-hf-authored-*. Timeline inspection built its
resolver without authored attrs, so a host with data-duration="4" reported
its GSAP timeline length (3.72s) instead.

Timeline text and events[].sources listed the same file:line once per mapped
snippet (creation, caller, dependency), so a one-line script printed one
location up to nine times.

* refactor(inspector): carry the dedupe reason in the helper's name

* fix(inspector): end continuation on a page that fits no snippet; fill budget with candidates

* fix(inspector): timeline groups by component, names attr values, eases, film end

* fix(studio-server): stamp hf ids into a file without reformatting it

* fix(inspector): find an element's tag before its served hf id reaches the source

* fix(inspector): live element map shows authored classes and text, not the playhead's

* fix(inspector): collapse script-built text so the element map ignores the playhead

Class names stay in source order, a runtime-stripped id is shown as id="…"
rather than a selector, and a grouped row names which children it covers.

* fix(inspector): keep paging, stamping, and the live map still on the reported edges

A snippet larger than the page ended the walk, so later snippets could not
be reached. Raw text ended at a longer tag name, which could stamp an id
inside a script. An empty or mixed source element showed live text, and an
unknown duration read as a film that ends at 0s.

* fix(inspector): mint ids for an empty hf id and keep an unquoted class

An empty data-hf-id was treated as already assigned, so stamping left it
blank while the older stamper minted one. An unquoted class was read as no
class, so the live map dropped it.

* fix(inspector): narrow markup before reading a child tag

skipMarkup returns a boolean, so the build still sees Markup where a tag
is required. Narrow on the kind first so the inspector typechecks.

* fix: quiet check false positives and name the ancestor of an id-less target

Endpoint contact, disjoint fromTo pairs, and a negative z-index under a
stacking-context parent were warnings with bad advice. Layout and contrast
blamed a dot grid, a circular avatar's panel, and a visibility override.
A faint texture veil is no longer the click target.

* fix(lint): drop a history sentence and six gsap comments

The comments check treats "load-bearing" as history, and gsap.ts added six
comment lines. The names already carry what those lines said.

* fix(parsers): ignore an empty id written inside another attribute

The empty-id scan matched the first data-hf-id="" in the tag, including one
inside a quoted title. It now skips quoted text, so the real attribute is minted.

* fix(lint): keep real covers and a negative z-index under a card

A gradient scale-down treated every fill like a radial dot, so a panel
that starts transparent stopped counting as a cover. A stacking context
on any ancestor cleared a negative z-index, and a faint wrapper made
images inside it unpickable.

* fix: keep an emoji from shifting a stamped id

The quote mask replaced one emoji with one space, so the id landed before
the closing quote. A radial cover that turns opaque after a clear stop
stays fully painted.

* fix(lint): score a letter-only dot as a dot, not a color name

namesAColor saw #fff and rgba as words, so those dot grids counted as a
full cover. Hex and rgb colors are stripped before that word check.
2026-10-14 09:16:20 +00:00
Xuanru Li 8e4de38be1 fix(keyframes): report unresolved GSAP timing instead of guessing rest poses (#5526)
* fix(keyframes): report unresolved GSAP timing instead of guessing rest poses

* fix(keyframes): drop later times when a delay cannot be resolved

Boolean(null) treated an unreadable delay as no delay,
so the next cursor-relative tween kept a guessed start.

* fix(keyframes): keep a staggered rest pose in Studio

The parser stops are what Studio holds, so a staggered from keeps its rest
pose there. The CLI reports that pose as unresolved, and an unread position
no longer seeds the next tween at zero.
2026-10-13 04:52:22 +00:00
Yash Rastogi b75fa6d996 test: add tests for audio_reactive_single_tween_per_group and gsap_group_selector_keyframes (#5404)
Resolves #5390
2026-10-13 03:39:50 +00:00
Yash Rastogi 33cffbf06b test: add coverage for slideshow_unresolved_ref and subcomposition_root_styled_by_class (#5399)
Resolves #5391
2026-10-13 03:39:34 +00:00
Leonel Rivas 9713e93a52 fix(lint): read a sub-composition's linked stylesheet from the project root (#5084)
A sub-composition that links a shared stylesheet with a plain path like
assets/fonts.css was only looked up next to its own HTML file, so lint
reported font_family_without_font_face for fonts that stylesheet declares.
The ../ spelling is rejected by invalid_parent_traversal_in_asset_path, so
no spelling passed.

Lint still looks next to the sub-composition first and now falls back to
the project root, which is where the preview and the bundler load the link
from. Links that start with / or contain a .. segment keep their old lookup.
A folder with the stylesheet's name is skipped instead of throwing EISDIR.

Fixes #5074
2026-10-13 01:28:07 +00:00
Miguel Ángel a9823f719d fix(lint): a double-quoted media src with an apostrophe is read whole, not reported missing (#5411)
* fix(lint): a double-quoted media src with an apostrophe is read whole, not reported missing

* fix(lint): every media src read goes through the attribute parser, decoded

* refactor(lint): the src pattern only finds the tag; the attribute parser reads the value

* fix(lint): a media src whose name starts with a quote character is checked too

* fix(lint): the missing-audio check skips audio tags that scripts build as strings

* docs(lint): mediaSrcTagRe's comment states the src rule without the history
2026-10-10 19:28:42 +00:00
Miguel Ángel 9825614d91 fix(studio): Studio's own text reads without em or en dashes (#5414)
* fix(studio): text in Studio reads without em or en dashes

* fix(studio): the dash check also catches HTML entities, and peak reads Unavailable

* test(studio): the dash check test covers every entity form

* fix(studio): messages Studio shows from lint, sdk, core and the renderer drop dashes too

* fix(producer): the gate check stops early on the engine's new fallback wording
2026-10-10 19:08:30 +00:00
Miguel Ángel 5ec2dd2e12 fix(lint): check passes valid sub-compositions and scene-relative asset paths (#5337)
* fix(lint): check passes valid sub-compositions and scene-relative asset paths

* fix(lint): keep the browser lint build free of node modules

* refactor(lint): tidy sub-composition lint helpers and guard bare node imports

* fix(lint): resolve stylesheet asset paths from the stylesheet's own folder

* fix(lint): catch every written form of a path that climbs out of the project

* fix(parsers): sub-composition asset paths written with backslashes load in preview

* fix(parsers): an encoded backslash stays in the file name and a leading backslash stays absolute

* fix(lint): an encoded %2f counts as a separator when checking for a climb out of the project
2026-10-09 19:54:58 +00:00
Abdullah M. Nazif 9111d9f97e fix(lint): parse inline script type and source attributes (#5220) 2026-10-08 20:23:55 +00:00
Abdullah M. Nazif b4c847aca2 fix(lint): read attribute values with the HTML parser (#5209) 2026-10-08 20:23:18 +00:00
Abdullah M. Nazif c03aa9224d fix(lint): detect optional managed media calls (#5210) 2026-10-08 20:23:08 +00:00
Abdullah M. Nazif 00729a566d fix(slideshow): validate nested hotspot entries (#5211) 2026-10-08 20:20:56 +00:00
Miguel Ángel eff786383d fix(core): preserve SVG icons in loaded compositions (#5096)
* fix(core): namespace SVG ids in runtime-mounted compositions

* fix(core): keep SVG selectors and captured ids working after loaded scenes rename them

* chore(core): generated artifacts carry a plain do-not-edit banner

* test(catalog): record the font subsets the new scene-script installer requests

* test(catalog): keep earlier mirror recordings, as re-records only add
2026-10-06 11:02:51 +00:00