Commit Graph
237 Commits
Author SHA1 Message Date
Miguel Ángel 1d9b3673fa fix(catalog): encode module aliases before constructing script code (#5093) 2026-10-05 23:32:46 +00:00
Miguel Ángel f835f83164 fix(catalog): 3D previews load their scripts under the docs host's script policy (#5090)
* fix(catalog): 3D previews load their scripts under the docs host's script policy

* fix(catalog): preserve script text through HTML parsing

* docs(catalog): describe the shared script bundle
2026-10-05 15:52:51 -07:00
Miguel Ángel 39a693c0a4 fix(engine): a render fails when a composition's own script throws before its timeline binds (#5033)
* fix(engine): a render fails when a composition script throws before its timeline binds

* fix(engine): an unrelated page error no longer fails a render whose timeline registers late

* fix(engine): only the composition's own scripts fail a render, local or distributed

* style(engine): format the script failure change

* fix(engine): a third-party browser rejection no longer fails a render

* refactor(engine): one page URL for navigation and error attribution

* fix(engine): a rejection after a hash change or in an iframe no longer fails a render

* fix(engine): a widget's error no longer fails a render once its script is inlined

* fix(core): name injected and inlined scripts without building code from library input

* fix(engine): a third-party embed's eval or inserted script no longer fails a render

* fix(producer): a widget's inline handler or a sub-composition no longer misreports a render

* refactor(engine): read each script response once and keep comments within 120 characters

* fix(engine): a scene that throws after registering its timeline no longer cuts a render short

* fix(engine): a composition script failure names the error and its real cause

* fix(engine): a failed VFX chain fails the render at once instead of shipping a missing layer

* fix(core): a VFX layer that is empty on one frame renders empty instead of failing or going stale

* fix(core): a VFX layer that grows from zero size renders instead of staying missing

* fix(core): a VFX layer hidden or late while empty renders once it has a size

* test(producer): cover a VFX layer that is 0×0 under a host that starts later

* test(producer): pin both VFX 0×0 marks, with a GSAP-shaped wipe and a real late host

* test(producer): a late VFX host with a GSAP wipe catches a walk that skips hidden hosts
2026-10-05 16:45:18 +00:00
Miguel Ángel b3d2b82fe9 feat(core): the renderer's font pipeline is importable from @hyperframes/core/fonts/embed (#5041)
* feat(core): the renderer's font pipeline is importable from @hyperframes/core/fonts/embed

* style(core): format the font pipeline and state the bundled-subset invariant

* test(producer): repin the test lane map after the font tests moved to core

* fix(core): images build fonts once, CLI bundles one pipeline, font tests pass on Windows

* test(core): font retry tests get a budget no slow machine can exhaust

* test(core): the Retry-After budget test fails when the budget is ignored
2026-10-05 04:54:53 +00:00
Miguel Ángel 16871a1796 fix: preview and bundled scripts run where and when the author put them (#5014)
* fix(core): bundled local scripts keep their own place, defer and async

The bundler joined every local <script src> into one inline script at the first one's position. That dropped defer and async (a deferred main.js ran before a deferred CDN gsap), moved later files ahead of CDN scripts between them, and let one file's error stop the rest. Each local script is now inlined where it stands with its attributes; a deferred or async one keeps its tag with its file as a data: URL. Inline-script merging also no longer folds in nomodule scripts, which dropped the attribute.

* fix: preview scripts run after the runtime when the bundler fails, and inlined files are escaped

- When the bundler returns nothing or throws, the preview places the runtime
  where the bundled page does (end of head) instead of after the composition's
  scripts or not at all; core's placement moves to insertRuntimeTag.
- Every HTML-spec JavaScript MIME type counts as a classic script for merging
  and the web-font gate.
- Inlined local files and merged runs are escaped with escapeInlineScriptSource.

* fix: deferred local scripts run late without data: URLs, and authored runtime reads survive

- Local defer/async scripts are inlined in their own tag with the attribute
  kept; the font-gate runner (and its old-runtime fallback) runs an inline
  classic defer script after the classic scripts, in document order, so a page
  policy that allows inline scripts but not data: still runs them.
- stripEmbeddedRuntimeScripts matches the bootstrap attribute and runtime file
  names on the tag, not runtime words in an authored script's text.
- The preview applies its runtime on every path, so an adapter page without
  one still gets it; the inline escaper writes <!-- as \x3C!--, valid in a
  unicode regex.

* fix: a deferred inline script waits for the deferred CDN script before it to load

* fix: an authored script that mentions the runtime marker no longer stops the runtime being added

* fix: only inlined deferred files run late, head ones included, and renders skip a CDN runtime link

* fix: deferred head files run as before, and one selector owns the deferred-file rule

* fix: a local file's legacy <!-- comment no longer breaks its merged run, and escapes keep case

* fix: a local file inlined on its own keeps working with a legacy <!-- comment

* test(core): a local non-JavaScript file keeps its text as written
2026-10-04 22:11:55 +00:00
miga-heygenandMiguel Ángel 173103dfde docs(skills): resolve cross-skill contradictions from the doctrine audit (#5028)
* docs(skills): resolve cross-skill contradictions from the doctrine audit

Section 1 of #5025, each direction taken from the code or the shared
contract:
- async timeline build is supported (runtime rebinds an async-registered
  timeline; lint only flags registering before the build finishes), so
  animation and keyframes now say register after the build completes
- data-track-index is a Studio row, display only; layering is z-index
- the final gate is `check`, which reruns lint
- music-to-video keeps render approval as the one autonomous question,
  per the brief contract
- faceless-explainer and product-launch-video pre-install registry blocks
  before dispatch, like pr-to-video
- talking-head-recut layout files cover landscape and portrait; 4:5 is
  derived
- embedded-captions Cinematic authors cinematic.json; make-cinematic.cjs
  derives plan.json
- audio carve has no static mode; remove the stale static and dynamic text

Left open: the figma usage beacon and the skills-update consent rule.

Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>

* chore(skills): refresh manifest; treat cinematic.json and safe-zones.json as project files

Both are written into the user's project by the embedded-captions workflow,
so the comment-citation check should not look for them in the repo.

Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>

---------

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-10-04 12:14:30 -07:00
WaterrrForever 6a27a34f21 feat(cli,studio): hand off on macOS now, and open the app on Windows and Linux (#4999)
* feat(studio,cli): add an Edit with Framey button and `hyperframes open` for HyperFrames Studio

Point people from the CLI and the Studio preview to HyperFrames Studio, the desktop app.

- Studio header: an "Edit with Framey" button with Framey, the app's cursor mascot (the eye
  follows the pointer, a wiggle on hover, honours reduced motion). Until the app opens
  handed-over projects (HANDOFF_READY), a press shows a "Meet Framey" card with the download.
  The button shows only where the preview server answers GET /api/open-in-desktop on macOS,
  so the desktop app's own embedded Studio never shows it.
- `hyperframes open [dir]`: hands the project folder to the app with `open -b` (released app,
  then Canary) and points to the download when neither is installed. When run by Claude Code
  or Codex it leaves .hyperframes/agent-handoff.json (CLAUDE_CODE_SESSION_ID / CODEX_THREAD_ID)
  so the app's first chat can pick up that conversation.
- render and preview print one line about the app: the download until HANDOFF_READY, then
  `hyperframes open` when the app is installed. Nothing for a draft render, a batch row, or a
  run inside the app (HYPERFRAMES_DESKTOP_PROJECT).

* fix(studio,cli): gate `hyperframes open` too, guard the open-in-desktop POST, keep one gate

Review of #4951 at 58934a34.

- One gate: HANDOFF_READY lives only in the CLI. `GET /api/open-in-desktop` now says
  `handoff`, and Studio's button reads it, so the gate cannot be half-flipped.
- While gated nothing opens and nothing is written: `openInDesktop` returns
  `handoff-unavailable` (so `hyperframes open` prints the download line and the POST route
  answers with it), and no `.hyperframes/agent-handoff.json` can sit waiting for a later release.
- The POST answers only Studio's own same-origin request (loopback Host, matching Origin,
  Sec-Fetch-Site same-origin); a cross-site or rebound-host POST gets 403. Routes moved to
  server/desktopRoutes.ts with their own tests.
- `hyperframes open`: exits 1 when nothing opened, answers in JSON under --json for a bad
  directory, and tells a missing app from one macOS could not open (`open-failed`). A project
  that cannot be written still opens, with nothing handed over.
- Tests drive the live path through `ready`, are path-portable for Windows, and cover the draft
  and batch-row hint skips (`wantsDesktopHint`, `batchRowRenderOptions`).
- The load smoke mocks the route.

* feat(cli,studio): hand off on macos now, and open the app on windows and linux

The released Mac app (b254) takes a handed-over folder and its conversation, so
HANDOFF_READY turns on for macOS. Windows and Linux stay on the download until the
desktop change that reads a folder from the command line ships.

- `hyperframes open` finds the app off macOS: the per-user install on Windows, and
  on Linux the AppImage the app records in launcher.json; it starts it with the folder
- download links follow the machine: the DMG on macOS, the AppImage on Linux, and
  nothing on Windows, which has no public download yet
- the Studio button shows wherever there is a download or an installed app; the card
  no longer says "for macOS"
- every render prints the app line except batch rows: music-to-video delivers a draft
- the success line names the app that took the folder (Canary included)

* fix(studio): the edit-with-framey button asks its route through studioApiFetch

* fix(cli,studio): name the app the hyperframes desktop app, apart from the studio preview

* fix(cli,studio): fewer comment lines, so no file's comment share rises
2026-10-04 12:54:17 +00:00
WaterrrForever bd0dc8c3ae feat(studio,cli): add an Edit with Framey button and hyperframes open for HyperFrames Studio (#4951)
* feat(studio,cli): add an Edit with Framey button and `hyperframes open` for HyperFrames Studio

Point people from the CLI and the Studio preview to HyperFrames Studio, the desktop app.

- Studio header: an "Edit with Framey" button with Framey, the app's cursor mascot (the eye
  follows the pointer, a wiggle on hover, honours reduced motion). Until the app opens
  handed-over projects (HANDOFF_READY), a press shows a "Meet Framey" card with the download.
  The button shows only where the preview server answers GET /api/open-in-desktop on macOS,
  so the desktop app's own embedded Studio never shows it.
- `hyperframes open [dir]`: hands the project folder to the app with `open -b` (released app,
  then Canary) and points to the download when neither is installed. When run by Claude Code
  or Codex it leaves .hyperframes/agent-handoff.json (CLAUDE_CODE_SESSION_ID / CODEX_THREAD_ID)
  so the app's first chat can pick up that conversation.
- render and preview print one line about the app: the download until HANDOFF_READY, then
  `hyperframes open` when the app is installed. Nothing for a draft render, a batch row, or a
  run inside the app (HYPERFRAMES_DESKTOP_PROJECT).

* fix(studio,cli): gate `hyperframes open` too, guard the open-in-desktop POST, keep one gate

Review of #4951 at 58934a34.

- One gate: HANDOFF_READY lives only in the CLI. `GET /api/open-in-desktop` now says
  `handoff`, and Studio's button reads it, so the gate cannot be half-flipped.
- While gated nothing opens and nothing is written: `openInDesktop` returns
  `handoff-unavailable` (so `hyperframes open` prints the download line and the POST route
  answers with it), and no `.hyperframes/agent-handoff.json` can sit waiting for a later release.
- The POST answers only Studio's own same-origin request (loopback Host, matching Origin,
  Sec-Fetch-Site same-origin); a cross-site or rebound-host POST gets 403. Routes moved to
  server/desktopRoutes.ts with their own tests.
- `hyperframes open`: exits 1 when nothing opened, answers in JSON under --json for a bad
  directory, and tells a missing app from one macOS could not open (`open-failed`). A project
  that cannot be written still opens, with nothing handed over.
- Tests drive the live path through `ready`, are path-portable for Windows, and cover the draft
  and batch-row hint skips (`wantsDesktopHint`, `batchRowRenderOptions`).
- The load smoke mocks the route.

* fix(studio): the edit-with-framey button asks its route through studioApiFetch
2026-10-04 10:52:23 +00:00
Miguel Ángel d2398d686a feat(registry): eight ad templates show their video preview in the catalog (#4979)
* feat(registry): eight ad templates show their video preview in the catalog

* refactor(scripts): the preview backfill reuses the shared entrypoint guard
2026-10-04 01:27:10 +00:00
Miguel Ángel 5561b8cb2f ci: a test-only change under studio or player needs no captures (#4956)
* ci: a test-only change under studio or player needs no captures

Tests are not behaviour a user sees, like the markdown the check already skips.
A bench-harness PR (#4953) was asked for Before and After screenshots.

* ci: pin a mixed source and test change, and say tests are not counted
2026-10-03 17:04:44 +00:00
Miguel Ángel 5632356c09 docs: show agents where Studio edits go and how tests build, and let docs skip captures (#4936)
* fix(ci): let markdown-only changes under studio and player skip the captures check

* docs: map the Studio edit path, the edit accuracy bench and test setup for agents

* docs: name the Studio captures rule and the local comment checks

* docs: point the Studio gate notes at the scripts that own the rules

* docs: correct the Studio save paths and add the producer stage map

* docs: name where producer sizes workers and which captures encode inline
2026-10-03 10:15:47 +00:00
Miguel Ángel 7348933111 feat(cli): expose harness subscription usage to agents (#4919)
* feat(cli): plan video creation around remaining harness usage

* feat(cli): read Codex and Grok subscription allowances

* test(cli): load usage fixtures through portable file URLs

* test(cli): keep fixture writes on filesystem paths

* refactor(cli): separate usage window and login decisions

* fix(cli): guard parsed credential array entries

* refactor(cli): report harness usage without workflow policy

* fix(cli): preserve usage when subscription tier is null

* test(cli): separate usage fixture code from inputs

* style(cli): format usage transport fixture

* refactor(cli): isolate usage fixture credential checks
2026-10-03 00:25:43 +00:00
04acbb4d9e feat(registry): add 4 community components (#4906)
* feat(registry): add cinemagraph — a still photograph with one living region

* feat(registry): add ai-answer-rank — a mock AI answer that re-ranks to put your brand in it

A 1080x1080, 9s block for answer-engine and AI-visibility marketing. A prompt
types into a chat card, the assistant answers with a ranked list of three picks,
then re-ranks: the promoted brand slides into its slot (brandRank 1-3), the picks
below step down, the third fades out of the list, and the brand row lands with
the one accent highlight and a drawn check. Monochrome placeholder UI; prompt,
lead-in, pick names, brand, rank, accent, ink and ground are variables.

* feat(registry): add caret swap text effect

* feat(registry): add pendulum-swing component

Add a motion-primitive component that hangs content from a fixed pivot and
lets it settle with a damped oscillation, like a hanging sign coming to rest
on its hook.

The swing rides a single unitless custom property (--hf-swing-angle) that the
timeline eases 1 -> 0, so only rotation animates and it stays sub-pixel under
the seek-by-frame capture engine. The demo drives it with a damped-oscillation
ease (a cosine inside an exponential decay, normalised to land exactly upright)
so the sign passes through center several times, each pass smaller, before it
rests.

Four enum variables: pivot (top-center/left/right), amplitude
(gentle/standard/wide), direction (right/left), tone (ink/paper/accent, each a
bg/fg pair to keep AA contrast on any frame). The snippet is recipe-only and
does not own its timeline; its demo carries the same variable declaration.

Ran npx hyperframes lint/validate on both files, regenerated registry.json via
scripts/generate-registry-items.ts and the docs via
scripts/generate-catalog-pages.ts.

* fix(registry): drop hand-added cinemagraph index entry; catalog-publish adds it

* style(registry): format pendulum-swing with oxfmt

* fix(registry): keep pendulum-swing demo caption clear of the swinging sign

* fix(registry): ship the ad-template editing contract for ai-answer-rank

---------

Co-authored-by: Carlos Miranda Levy <carlosmirandalevy@gmail.com>
Co-authored-by: tugrawork-creator <319556794+tugrawork-creator@users.noreply.github.com>
Co-authored-by: Akshar Patel <aksharcommit@gmail.com>
Co-authored-by: rottenpen <12029924+rottenpen@users.noreply.github.com>
2026-10-02 17:14:52 +00:00
miga-heygenandMiguel Ángel e8ff5ca8c2 fix(lint): fail skill lint on unresolved doc cross-references (#3922)
* fix(lint): fail skill lint on unresolved doc cross-references

scripts/lint-skills.ts now checks every *.md under skills/, .claude/skills/
and .agents/skills/ for cross-references that do not resolve: inline links
[text](target), reference definitions [id]: target, and backticked paths
that are explicitly relative (./ or ../) and end in .md or .html. Targets
resolve against the referencing file's directory and must exist; a #anchor
on a Markdown target must match a GitHub-style heading slug of that file,
and same-file #anchor links are checked against the file's own headings.
Fenced code blocks, URI-scheme targets, absolute paths and placeholder
targets ({ } * $ < >) are skipped. Bare backticked paths without ./ or ../
are a documented blind spot: on the current tree they are mostly skill-root
shorthand, another skill's file, or a runtime artifact, and checking them
produced ~200 hits that are not wrong.

The former top-level main now runs only when the script is the entry point,
so importing the exported checkers from the test file no longer exits the
test runner when the tree is red.

On the current tree the rule reports 34 violations across 16 files, all of
the shape described in #3919 (../ one level too shallow for the referencing
file, or a file that does not exist). The docs are corrected separately.

Refs #3919

Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>

* refactor(lint): trim the cross-reference rule's comments and inline its reporter

* docs(lint): cite placeholders, not invented paths, in cross-reference comments

* refactor(lint): keep the skill lint entry point under the complexity limit

---------

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-10-02 08:20:12 -07:00
Miguel Ángel 84898fbe85 fix(ci): skill docs may name the project files a workflow writes (#4902)
* fix(ci): skill docs may name the project files a workflow writes

* style(ci): wrap the project-file citation test to the format width
2026-10-02 07:06:37 -07:00
renovate[bot]andMiguel Ángel 29ded9b0fa chore(deps): update github-actions (#3934)
* chore(deps): update github-actions

* ci: re-pin producer test job hash after the checkout action bump

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-10-02 03:41:24 -07:00
Wu ShuwenandMiguel Ángel 74e6df4370 fix(release): use portable Git paths in release guards (#3766)
* fix(release): use portable Git paths in release guards

* fix(release): drop the comment on a path git never compares

---------

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-10-02 03:17:16 -07:00
Miguel Ángel d4756f597c fix(studio): edits on GSAP-animated layers keep their keyframes and file positions (#4869)
* fix(studio): resizing an element GSAP only positions saves its CSS width and height

* fix(studio): a resize never saves GSAP's scale mask as the element's CSS

* fix(studio): keep the resize size writer inside the file and comment budgets

* fix(studio): a GSAP-positioned resize saves its CSS size before the GSAP write renders

* fix(studio): undoing a crop leaves a GSAP-animated layer where its file puts it

* test(studio): an undo on a GSAP-parsed layer in a two-script file reloads the preview

* fix(studio): a move keeps an authored xPercent centring set in the saved file

* fix(studio): a drag on a centred element no longer flashes off-centre on release

* fix(studio): a font pick saves the edit and its @font-face in one write

* fix(studio): a focused combobox, switch or plain-text editor counts as an unsaved edit

* test(scripts): pin that a slash after i-- before a quoted slash is division

* fix(studio): an inspector edit saved while a preview reload loads stays on screen

* fix(studio-server): a font save accepts exactly one @font-face rule and nothing after it

* fix(studio-server): a font name or path with braces in quotes still saves its @font-face

* fix(studio-server): a CR or form feed in a quoted font name cannot end the @font-face rule

* test(studio): bank the 99 edit accuracy cases the GSAP fixes now pass
2026-10-01 17:24:35 -07:00
37f30b1d06 feat: keep a video's sound on the clip by default (1/8) (#4813)
* feat(cli): one owner for init media placeholders, audio stays on the video

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(cli): init keeps a video's sound on the clip; silent files scaffold muted

Supersedes the patchVideoSrc changes in #3977 and #3942 and carries their tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: miga-heygen <miga-heygen@users.noreply.github.com>
Co-Authored-By: kapelame <kapelame@users.noreply.github.com>

* feat(registry): example templates keep the a-roll's sound on the video

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(lint): video audio rules recommend keeping sound on the video

Update video_missing_muted and video_audio_double_source message and
fixHint text to recommend the new default: keep audio on the video with
data-has-audio="true" (not muted). Tests verify the new wording is in place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(parsers): a video is either audible (data-has-audio) or muted, never both

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(studio): follow the compiler's audio rule in the audio-track toggle

Confirm before background removal drops a video's sound.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(skills): hyperframes-core teaches sound on the video by default

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(skills): workflows and scaffold guidance keep a video's sound on the clip

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: footage with sound keeps it on the video clip

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cli): init from-file a-roll is timed, muted strip is attribute-exact

The from-file a-roll video gets data-start="0" so a scaffold lints clean, and
the orphaned audio-only slot comment is dropped. setVideoAudio now walks the
tag attribute by attribute, so a class value containing the word muted
survives. The ffprobe-missing warning says the video is assumed to have sound
and how to switch to muted, and DEFAULT_META notes the failure surfaces at
render until lint support lands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(studio): keep has-audio in step with the muted toggle, test cutout confirm

Muting a video now clears has-audio and unmuting sets it to true, in both the
Design and flat media panels, matching the Has audio track toggle. Audio
elements are unchanged. Adds coverage for both panels plus the cutout
window.confirm guard: declined leaves src alone, accepted swaps src and mutes,
a silent video never prompts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(parsers): updateElementInHtml hasAudio only applies to videos, true or false

An audio element or an undefined hasAudio no longer picks up a muted attribute;
only an explicit false on a video writes muted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: video_missing_muted rule text, silent-footage notes, consistent audible examples

lint.mdx and cli.mdx describe the current video_missing_muted rule instead of
the retired unmuted-video wording. The editing recipes say how to adapt for
silent footage and require the outgoing clip's sound to end at a J-cut. The
Remotion media example shows the audible mapping consistently, data-attributes
states that has-audio is required unless muted, and the hairline frame note no
longer claims the video is muted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cli): encode init media filenames and probe --audio duration

Function replacers stop dollar-sign pattern expansion in filenames; src is percent-encoded.
--audio now passes the probed track length as the composition duration in both init paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(lint): audio-state-aware media rules and time-aware double-source check

video_missing_muted honors any explicit data-has-audio.
video_audio_double_source only considers audible videos and only fires
when the same-src clips overlap in time.
duplicate_audio_track now covers audible videos.
audio_file_without_element no longer claims silence when an audible video
carries the sound.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(skills): audio-stream check for remotion ports, followable J-cut recipe

Remotion Video maps to data-has-audio only when the file has an audio stream.
The J/L-cut recipe now shows both shots with muted pictures and separate audio.
embedded-captions removes the audio.mp3 scratch file it extracts.
skills-manifest.json regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(lint): trim comments to satisfy the comment ratchet

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(studio): atomic muted edits, cutout keeps sound on a linked audio

Muted and Has audio track commit one batched write (one persist, one
undo entry) and remove data-has-audio instead of writing an empty
marker. Unmuting a video probes its src: audible -> true, silent ->
false, probe failed -> absent. Remove background on an audible video
inserts a linked sibling <audio> with the original sound in the same
write, replacing the drop-the-sound confirm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(producer): skip hidden videos when auto-adding video audio

A video hidden in Studio is dropped by the mixer, so its src never reached
existingAudioSrcs and the render re-added it at full volume. The video parser
now records the shared hidden predicate and the auto-detect step skips hidden
videos.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(studio): keep useDomEditSession under the 600-line cap

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: miga-heygen <miga-heygen@users.noreply.github.com>
Co-authored-by: kapelame <kapelame@users.noreply.github.com>
2026-10-01 23:56:36 +00:00
Miguel Ángel d218cec091 ci(studio): gates read the base the run measured, and drag frames on a quiet runner (#4872)
* ci(studio): measure drag smoothness on a quiet runner, with a blank-page control

* ci(studio): gates read the base the run measured, not the base branch tip

* style(ci): format the base-file test
2026-10-01 16:35:49 -07:00
Miguel Ángel fd105807c4 fix(scripts): count comments after regex literals in the comment ratchet (#4879)
* fix(scripts): count comments after regex literals in the comment ratchet

* fix(scripts): keep comments after JSX tags, ++/-- and wrapped division in the ratchet
2026-10-01 14:09:44 -07:00
Miguel Ángel e666a8a2a3 feat(studio): the code editor follows studio's light and dark theme (#4862)
* feat(studio): the code editor follows studio's light and dark theme

Replaces One Dark with a Studio editor theme whose colours are theme.css variables: eight syntax role tokens (One Light and One Dark hues held at 4.5:1), editor surfaces from Studio's roles, all measured by the contrast ratchet.

* test(studio): the code editor theme takes every colour from a theme.css token

* fix(studio): code stays at 4.5:1 when a selection or search match sits on the active line

Syntax tokens are tuned for the selection over the active line, which the ratchet now measures; matches are outlined rather than stacking another wash; the search panel's pressed button and special characters follow the theme; unused tooltip rules go.

* fix(studio): code matches carry no background, and types read apart from constants

Search and selection matches are outlined only, so CodeMirror's lime match wash and stacked washes are gone; type and constant tokens split by hue in both themes.

* fix(studio): the current search match reads apart by width, not colour alone

A 2px accent outline on the current match and a muted 1px outline on other copies of the selected word.
2026-10-01 10:13:48 -07:00
Miguel Ángel 658f22bb7f feat(studio): graphite colour system with a light theme that passes contrast (#4836)
* feat(studio): graphite colour system with a light theme that passes contrast

Studio's tokens take the Graphite values: a lifted dark, a light set behind
data-theme="paper", the player dark in both. Every text role clears 4.5:1 and
every icon and ring 3:1 on the fills it sits on, held by the contrast ratchet.

* fix(studio): every studio surface follows the light theme and passes contrast

The dock no longer inherits dockview's forced dark scheme, popovers sit on the
raised surface, stock white, red and amber text map to roles, the keyframe
diamonds and track headers use roles, and the contrast ratchet measures every
pair in both themes (150 pairs).

* fix(studio): stock white, red and amber stay colours; studio text and fills use roles

Round-two review: white, red and amber are colour content as well as text, so
they keep Tailwind's values and hosts keep their palettes. Text, toasts, the
feedback card, the FX popover, destructive buttons, category labels and the dock
take Graphite roles instead; the contrast gate rejects an unparseable colour.

* fix(studio): every light-theme ink, icon, dot and focus ring holds its contrast

Stock white, red, amber and file-type hex left in Studio chrome now use roles or a light-dark pair; the file tree icons carry a contrast test.

* chore(studio): drop two comments the names already carry

* fix(studio): red marks, teal outlines and colour curves hold contrast in both themes

Danger graphics use the danger ink, file tree outlines use the accent ink, the colour-curve well stays dark like the scopes, and six timeline token pins come back.

* fix(studio): invalid readouts, the curve focus ring and editor dots stay distinct in both themes

The invalid readout border is dashed as well as red, the curve graph's ring sits inside its dark well, the radius dots and ease handle centre follow the theme.

* refactor(studio): ease and radius editors use colour classes; the dashed invalid border has a test

Simplify pass: the ease editor's inline accent styles become stroke and fill classes, the radius dot class is computed once, the ratio check is one expression, and the alias table is named for what it holds.
2026-10-01 07:21:05 -07:00
Miguel Ángel 0b417cb57a refactor(studio): delete the move writer no Studio path calls (#4837) 2026-10-01 05:46:28 -07:00
Miguel Ángel 567525a8c2 fix: readers never see an empty or half-written project file (#4777)
* fix: readers never see an empty or half-written project file

* test: pin the create routes and block install to whole-file writes

* fix(sdk): a write through a link to a missing file creates that file

* fix: saves through a linked folder reach the real file, atomic writes handle long names

* test(sdk): check for the stray file, not the folder listing

* fix(core): creates work on Windows FAT drives, and links through folders resolve as the system does

* fix: agree the helper move in the deletion check, and match temp paths without a path regex

* test: check POSIX file modes only off Windows
2026-09-30 19:17:59 +00:00
Miguel Ángel 590c192498 feat(catalog): add 3D objects, cursors, snow, starfield and paper transitions (#4349)
* feat(catalog): shelve 3D objects and cursors in their own catalog sections

Carries the generator grouping rules from #4310 and #4308.

* feat(registry): add a starfield ambient overlay

From #4313.

* feat(registry): add a falling snow ambient overlay

From #4331.

* feat(registry): add a torn-paper-rip transition

From #4321.

* feat(registry): add a paper-collage-reveal transition

From #4322.

* feat(registry): add a curtain-reveal transition

From #4323.

* feat(registry): add 3D objects: product turntable, glass hero, exploded stack, extruded logo

From #4310.

* feat(registry): add eight cursor animations and the dart cursor

From #4308.

* fix(catalog): find items by everyday editor words and accept a bare search word

From #4311.

* chore(registry): remove the simulated-cursor component

The Cursors section's new pointer animations replace it (from #4308).

* fix(registry): take cursor-zoom-follow's screenshot from runtime variables only

The declared default is empty, so page markup never becomes an image URL.

Clears the code-scanning alert raised on #4308.

* docs(cli): show a catalog search example that finds items

"smoke" matches nothing in the catalog; "crossfade" finds fade-through.

* chore(ci): allow the simulated-cursor removal in the deletion guard

* fix(ci): skip tracked files missing from disk in the player CDN pin check

Catalog regeneration removes a deleted item's pages before the publish PR untracks them,

so the check crashed on every catalog deletion PR.

* chore(cli): drop comments that narrate catalog search history

* fix(registry): drag-drop keeps the family arrow and shows the drop slot only while hovering

* fix(registry): agent cursor labels its steps in an accent tag, no glow or sparkles, in 4.8 s

* fix(registry): annotate cursor draws a tapered marker stroke and clears the circled value

* fix(registry): show real file thumbnails and end the context menu demo sooner

* fix(registry): give marquee select real photos, a Finder-blue marquee and a tighter loop

* fix(registry): centre the text-select toolbar on the selection and tighten its timing

* fix(registry): selection cursors default to the macOS system blue

* style(registry): format the agent and annotate cursor sources

* fix(registry): dart-cursor stops covering the choice it makes and shows the export finishing

* fix(registry): frame the product turntable fully and light it with two studio softboxes

* fix(registry): touch-indicator taps like a real phone and the post it taps gets liked

* fix(registry): press-ripple confirms with a check and small click rings at the cursor

* fix(registry): exploded stack separates at once and marks each layer in blue as it is labelled

* fix(registry): extruded logo settles facing the camera, set in Geist Bold with a neutral rim

* fix(registry): magnetic cursor docks its toolbar on the selection and ends after the press

* style(registry): format the exploded stack source

* fix(registry): glyph trail becomes a tutorial cursor path with numbered steps on a settings screen

* fix(registry): make glass-hero a clear lens that shows the headline through it

* fix(registry): snow reads as real snowfall with bokeh flakes over a winter title card

* fix(registry): oversized cursor is a 3.5x arrow that clicks a real editor toggle on

* fix(registry): starfield reads as a drifting night sky over a real title card

* style(registry): format the glass hero source

* fix(registry): make the paper tear show a white fibrous edge and lift the torn sheet away

* fix(registry): curtain reveal draws velvet folds that gather onto a title card

* fix(registry): make every zoom-follow click change the screen and read at zoom

* fix(registry): paper collage pieces stay opaque, tear with deckled grain and lift away on an arc

* refactor(registry): drop dead code and correct stale comments in reworked items

* fix(registry): agent cursor scrubs cleanly, docs match press-ripple, fictional demo names

* docs(registry): catalog notes say oversized-cursor exits and how it differs from press-ripple

* docs(registry): plain lowercase comments in oversized-cursor and touch-indicator

* docs(registry): oversized-cursor names CLICK_AT as the click cue
2026-09-28 22:03:59 +00:00
Miguel Ángel f7ef18dca8 fix(producer): renders leave no captured frames behind when interrupted (#4694)
Ctrl+C during a render left its whole work dir of captured frames (and a
staging dir) in the renders folder. Puppeteer's default SIGINT handler kills
Chrome and exits the process before the render's own cancel path can remove
them. The engine keeps that default unless the host owns Ctrl+C; the CLI's
render cancellation scope claims it while a render runs, so the render aborts
and cleans up after itself.

A render killed outright (SIGKILL, crash) cannot clean up, so each work dir and
staging dir now records its owner process (pid, hostname, boot, and on Linux
the pid namespace), and the next render into the same folder removes
the ones whose owner has exited.

Cancelling a render while its browsers start no longer leaves anything
running that kept a batch render alive after Ctrl+C: the BeginFrame warm-up
loop, the probe browser and file server, or a capture browser whose launch
finished after the cancel (now closed before the worker settles, since
Puppeteer's exit hook skips every other live browser).
2026-09-28 21:58:06 +00:00
Miguel Ángel a31738f115 fix(docs): restore examples with the HyperFrames Player (#4675)
* fix(docs): restore visible example video cards

* fix(docs): use catalog player for example films

* fix(docs): fit player posters before playback

* fix(docs): preserve the original examples grid
2026-09-28 11:33:54 -07:00
Miguel Ángel 5383e486bb fix(producer): gif renders survive rgb frames among transparent ones (#4610)
* fix(producer): gif renders survive rgb frames among transparent ones

* test(producer): run the gif mixed-frame encode test in the ffmpeg lane

* fix(producer): gif encode test passes the argv, reachability and comment checks

* fix(producer): transparent gifs clear every frame so an opaque frame never lingers

* fix(producer): only gifs that turn translucent after an opaque frame get whole cleared frames

* fix(core): plain text keeps its own gif control block; gif encode failures share one shape
2026-09-28 06:48:49 +00:00
Miguel Ángel b41ef425cc fix(release): name Release once in a changelog entry's tags (#4567)
* fix(release): name Release once in a changelog entry's tags

* fix(release): the release tag appears once in any case; four past entries lose the duplicate
2026-09-28 04:04:00 +00:00
Miguel Ángel 04d56d06aa fix(producer): renders use the font named in a var() fallback (#4619)
* fix(producer): renders use the font named in a var() fallback

When a font-family starts with var(--x, A, B) and --x is not defined, the
render now reads A, B the way the browser does. A system font at the head of
that fallback gets Inter placed in front of it, the same rule a direct
system-font stack already follows. The rewrite stays inside the var()
fallback, so a defined --x still wins. A defined property whose value is
itself a var() is followed too. The distributed plan check reads the same
answer.

Fonts named only in such a fallback are best-effort: they are embedded when
they resolve, and one that does not resolve is logged instead of failing a
distributed render. Fonts named directly, or through a defined property,
still fail closed as before.

CSS-wide keywords such as inherit are no longer treated as font names to
fetch.

Compiled the way a distributed render compiles them, 28 registry items now
embed a font they name this way, and none newly fails.

* refactor(producer): fold var() fallback resolution into one branch

* fix(producer): fonts behind a var() chain or empty fallback add no new render failures

A family reached through a chain of defined var()s is optional like a
fallback-only one: fetched fail-closed, skipped only when no source serves
it. An empty var() fallback makes the declaration invalid, so the browser
inherits and the plan check no longer rejects the families after it.

* test(producer): a family named after a leading var() stays required

* fix(producer): a transient error on an optional font warns and renders the fallback

* chore(catalog): record the font requests var() fallbacks now make

* fix(producer): required fonts fetch before optional ones can spend the time budget
2026-09-28 03:30:21 +00:00
Miguel Ángel 693c385508 fix(cli): commands exit right away even when the network or DNS is broken (#4604)
* fix(cli): commands exit without waiting on update checks or telemetry

The update and skills checks run in a detached child that refreshes the
caches for the next run; the parent only reads the caches. finalizeCli no
longer awaits the telemetry flush: the exit handler flushSync() delivers
what is queued. The events beacon, feedback and figma error paths stop
awaiting flush() too.

* fix(cli): retry a failed background check at most once an hour

Before starting the background worker, the CLI stamps a per-check attempt
time and skips a check it attempted within the hour, so a dead network no
longer starts a worker on every command. The stale-pin notice writes onto a
fresh read, detached spawns set windowsHide, and a test ties the worker path
to its build entry.

* fix(cli): a failed-check stamp dated in the future no longer silences update checks
2026-09-28 02:22:12 +00:00
James Russo 0a5e3c7fc4 feat(plugins): ship portable agent bundles and installation guides (#4608)
* feat(plugins): ship portable agent bundles and installation guides

* fix(plugins): align new manifests with current release

* test(plugins): split caption fixture setup and cleanup

* test(plugins): pass render fixture path as an argument
2026-09-27 21:58:51 +00:00
Miguel Ángel 2b1bb8b731 test(cli): CLI tests run with a home folder of their own, never the user's (#4587)
* test(cli): CLI tests run with a home folder of their own, never the user's

* style(cli): format the test home check

* test(cli): the skills test lane also runs with a home folder of its own

* style(cli): format the test home check

* ci: the test reachability map follows the skills lane's new command
2026-09-27 12:30:03 +00:00
Miguel Ángel e00baefb5c fix(studio): reload the preview only when a file it loaded changes (#4570)
* fix(studio): reload the preview only when a file it loaded changes

* test(studio): read the owner id from the new change-owner shape

* refactor(studio): drop a comment the parser name already says

* fix(studio): keep reloading until the preview is built, and count what its document names

* style(studio): format the reference pattern and name the kept reload

* fix(studio-server): scan preview references in linear time and refresh all thumbnails on a merged reload

* fix(studio): keep a waiting film change when a file the preview never loaded arrives behind it

* test(studio): name the preview ownership test for what it checks

* chore(ci): allow the watchIgnore removals and trim the new comments

* fix(studio-server): reload the preview when a folder holding a file it loaded moves in or out

* perf(studio-server): check a changed folder in one lookup and cover every reference form

* fix(studio-server): record read folders for a project at the filesystem root

* fix(studio-server): reload the preview when the file a symlinked asset points at changes

* fix(studio-server): resolve symlinks on every preview read and key projects by their real folder

* fix(studio-server): record every link a preview read passes through, resolved as the system does
2026-09-27 10:05:53 +00:00
Miguel Ángel 1a53681de1 fix(studio): an automation lane edit is refused on a locked clip and reports how it saved (#4533)
* fix(studio): an automation lane edit is refused on a locked clip and reports how it saved

Lane drags, Delete and paste save through the timeline's gated clip or group
save instead of the property panel's commit, so the host's canEdit applies and
the commit resolves saved, refused or failed. A landed save refreshes the lane
store and the panel's selection, as the panel's commit did.

* fix(studio): a refused automation save puts the preview and the lane back

A save that does not land (recording, no id, a failed write) reverts the live
preview it was given, the lane drops a draft whose save came back refused or
failed, the no-id and no-project failures toast like the others, and the panel
refresh reads the current selection. The panel commit's unused coalesce option
goes, and the lane value reuses automationAttrValue.

* fix(studio): a failed save restores the preview it was given, not an older one

Each timeline save claims the preview's before-value when it starts, so a
gesture begun while it is in flight records its own and a later failed save
cannot put back a value from before an earlier save landed.

* chore(repo): allow the automation gesture key files this branch removes

* fix(studio): a refused automation save leaves the lane store and paste mark as saved

Reverting a live value re-syncs the lane store from the restored preview, so
an overlapping save that landed first cannot leave the store holding a later
value that failed. A paste whose save is refused or fails gives back the
selection and paste chain it replaced.

* fix(studio): a failed save settles on what the file holds and a refused paste marks nothing

A save that does not land settles the preview and store on the file's value
(the claimed before-value only when the file cannot be read), and leaves a
gesture begun meanwhile alone apart from correcting its before-value, so two
overlapping failed saves cannot leave either on a value that never landed. A
paste selects and marks its span only once it lands and only over the
selection it was pasted at.

* fix(studio): a failed save reads the file only after writes queued to it land

The settle read goes through the same per-path queue as the writes, so a save
that fails while another is queued settles the preview, the store and any
newer gesture's before-value on what that queued save wrote.

* fix(studio): an older failed save never settles over a newer edit on the same lane

Live-preview bookkeeping moves to createLiveLanes, one owner for both timeline
saves: each lane key carries a generation that every live write and save start
advances, and a save settles the preview and store (on its landed value, or on
the file after a failure) only while it is still the newest. A paste marks its
span only if it is still the latest paste.

* test(studio): a newer live drag keeps its preview when an older save recovers

* fix(studio): every finished save records what the file holds, even under a newer edit

A save's settle now always writes the file's value (landed, or read back after
a failure) into the lane store and a live gesture's before-value, and moves the
preview only while nothing newer has touched the lane. The clip store is set
from that value rather than copied from the preview. A paste marks its span
only while its clip is still the selected one.

* fix(studio): an older automation save never overrides a newer gesture on its lane

A failed save's recovery read could settle after a newer save started, and the
save's own optimistic preview patch and failure unwind ran over a newer drag.
Every preview write a save makes now goes through the lane's ownership check,
only the newest save records its value, and a refused drag hands the lane back
to the newest pending save.

* fix(studio): timeline attribute saves land in the order they start

An attribute save read the file before joining its write queue, so an older
save with a slow read could land after a newer one, overwrite it on disk and
leave the lane showing the newer value. The save now joins the queue first and
reads, patches and writes inside it. A save moves the preview only while it is
the newest save and no drag is live, which replaces the per-lane generation.

* refactor(studio): name the in-queue history write for the queue it needs

* test(studio): pin save start order and a newer release's preview mid-flight

* fix(studio): a drag keeps its points when an older save lands under it

An older save that landed mid-drag wrote the lane store, the lane dropped its
draft, and the release committed the older points. The lane now keeps its draft
while a gesture is live. A save that cannot read the file back settles on the
last value read or saved on its lane, never on an unsaved one, and an older save
that lands records its value there even when a newer save is pending.

* fix(studio): a lane's first failed save still puts its drag back

With the claimed-value fallback gone, a first save on a lane that failed and
could not read the file back settled on nothing, leaving the drag playing. A
save begun with no other save pending now records the value under its drag as
verified, since that is what the file last settled on.

* fix(studio): only a before-value read with no save pending counts as saved

The value under a drag was trusted when its save started with nothing pending,
but it was read when the drag began, possibly over a paste that later failed.
A lane now marks a before-value clean only when no save was pending as the drag
began. A group lane's preview carries its store mirror, as its live writes do.

* fix(studio): a lane's saved value never crosses into another project

The lane bookkeeping outlives a project switch and its keys had no project, so
a value saved in one project could settle into another project's clip with the
same key when a save there failed and could not read the file back. Lane keys
now carry the open project, and a save moves the preview or store only while
the project it was made in is still open.

* fix(studio): a failed save reads its file back from its own project

A save that failed after a project switch read its recovery from the newly
opened project and recorded that project's value as its own. Each save now
captures its project when it starts and reads back from it.

* fix(studio): a failed save never brings back a value an undo removed

A lane kept its last verified value after its saves settled, so after an undo a
save that failed and could not read the file back settled on the removed value.
The value now lives only while a save on the lane is in flight, and each save
records what its own in-queue file read found. The group preview's store mirror
is no longer needed and is gone.

* test(studio): pass the file-read callback in the direct attribute-save tests

* fix(studio): a drag whose before-value a save settled counts as clean

When a settle rewrites a live drag's before-value to a verified value, that
value is known saved, so the drag's own save can fall back on it even when its
read and read-back both fail. Adds a test for a failed save reading back from
its own project while another save is in flight across a switch.

* fix(studio): a save started in one composition never lands in another of the same project

Live lanes were scoped to the project only, so a save held across a switch
to another composition of that project settled its value into the new
composition's preview (clip and group lanes) and store (group lane).
Lanes are now scoped to the project and the composition open when the save
starts. createLiveLanes moves to its own module for the 600-line cap.

* fix(studio): an undo during or after a lane gesture stays undone when the next save fails

A live gesture records the lane's value before it starts, and a later save
that cannot read the file settles on that value. An undo mid-drag, or after a
gesture that ended without saving, left that value stale, so the save brought
back what the undo removed. Every undo and redo now re-reads each open lane's
value from the files it restored.

* fix(studio): undoing group automation clears it from the members' lanes at once

The group-info cache only noticed a change when its MutationObserver callback
ran, so the soft restore's store sync, which reads it synchronously, mirrored
the pre-undo group automation onto the members. A read now counts mutations
the observer has not delivered yet.

* fix(studio): an undo reaches every lane and pending save it rewrote, in any composition

The undo re-read only covered lanes in the open composition and live gestures.
History is project-wide, so undoing from another composition left the
rewritten composition's lanes on the pre-undo value, and a save released while
the undo was in flight kept that value as its fallback. The re-read now covers
every lane of the project on a restored path, including saves still pending,
and an undone file it cannot read makes the value untrusted instead of dropped.
A missing element reads as unreadable, the same as a save's read-back.

* fix(studio): a cancelled fade, stretch or preset audition leaves no stale lane value behind

A cancel put the gesture's start back through a live write, which left the
lane's before-value in place until the next save trusted it, even after the
property panel, the source editor or an agent had changed the file. The fade
cancel, the edge-stretch cancel and leaving a preset audition now end through
the lanes' revert, so a before-value exists only during a live gesture.

* test(studio): pin that a group write is read back fresh in the same task

* style(studio): drop two comments the names already carry

* fix(studio): a failed group save reads its own file back; fade cancels work without revert

- The group save's failure read-back used the file of whatever composition was
  loaded when the write failed. It now reads the file captured when the save
  started, as the clip save does.
- A fade cancel writes its start back live before it reverts, like the other
  cancel sites, so a host without the optional revert callback still puts the
  fade back.
- The edge-stretch cancel comment says what the code now does.
- The save test harness passes the revert callbacks, as the app does; without
  them the cancelled-gesture tests exercised a host that lacks them.

* refactor(studio): trim three long comments on live automation saves
2026-09-27 09:05:54 +00:00
Miguel Ángel d290c05b09 fix(cli): --yes in an unattended run no longer overrides a no saved by another process (#4547)
* fix(cli): --yes in an unattended run no longer overrides a no saved by another process

* fix(cli): every settings writer takes the lock, and --yes needs a real terminal to reverse a no

* fix(cli): the thin-results download offer also needs a real terminal

* fix(cli): one owner-checked settings lock, and an unreadable settings file never counts as a yes

* fix(cli): settings lock removals go through a reaper, and a non-boolean answer counts as a no

* fix(cli): a settings lock that cannot be released no longer fails a write that landed

* fix(cli): a leftover settings lock is never taken over, and doctor names it

* fix(cli): a background update that cannot be recorded is not launched again every run

* fix(cli): every on-device install re-reads the saved answer first, and an unsaved yes installs nothing

* fix(cli): the thin-results offer stops when a yes cannot be saved

* fix(cli): only the consent-checked step installs the on-device runtime, and a leftover lock warns once

* fix(cli): the leftover-lock warning follows the attended-terminal rule
2026-09-27 01:39:17 +00:00
Miguel Ángel 8798e40da8 fix(registry): carousel cards fall back to their sample image inside the assets folder (#4543)
* fix(registry): carousel cards fall back to their sample image inside the assets folder

When an image variable resolves to nothing, the 25 carousel blocks loaded the
sample card by bare file name, which 404s beside the block and draws a white
card. The fallback now keeps assets/carousel-images/, the folder every card
image is installed into. A guard test fails on any block with a bare fallback.

* test(registry): the carousel fallback guard covers every block that uses it

The guard now picks each block that reads an image variable and fails when its
fallback no longer matches, instead of silently skipping it, and ignores stray
files in the blocks folder.
2026-09-26 15:26:19 +00:00
Miguel Ángel c238abfe6f fix(media-use): tests never write into the person's media library (#4459)
* fix(media-use): tests never write into the person's media library

* fix(media-use): guard the media home only in this repo's test runs

* test(media-use): probe the media home from a plain child process

* fix(media-use): keep miss logging best-effort when home is unresolvable

* test(media-use): miss logging survives an unresolvable media home

* docs(media-use): describe the media library without naming an app

* chore(media-use): refresh the skills manifest hash
2026-09-26 14:13:56 +00:00
Miguel Ángel 81d8a03d2e fix(repo): keep one fallow audit base cache per checkout instead of one per main commit (#4525) 2026-09-26 07:36:06 +00:00
Miguel Ángel fbaa9f9205 ci: stop running CodeQL on merge queue groups, where its upload races the merge (#4492)
* ci: stop running CodeQL on merge queue groups, where its upload races the merge

The code scanning rule does not apply to merge queue groups, and the queue deletes
a group ref when it merges, so the upload failed on about half the groups.
Pull requests and pushes to main keep scanning.

* ci: catch merge_group in any trigger form and say the upload race is intermittent
2026-09-25 16:50:55 +00:00
Miguel Ángel 828c434f76 refactor(studio): drop recordEdit's kind, which the project history never read (#4474) 2026-09-25 10:48:46 +00:00
Miguel Ángel 36f8bd0e91 feat(studio): undo and redo step through the project history, whoever made the change (#4456)
* feat(studio-server): an undo taken from Studio is not mistaken for an outside edit

* feat(studio): undo and redo step through the project history, whoever made the change

* feat(studio): the dev server keeps each project history, in the same folder as preview

* fix(studio-server): an undo that deletes a file reads as Studio's, and dev histories close

* fix(studio-server): a claim keeps an agent's earlier write the agent's and takes back Studio's own

* chore: allow the removal of Studio's browser edit history files

* test(studio): mock the project history in the runtime smoke

* fix(studio): step past a jammed undo and say why a step fails

* fix(studio-server): an agent turn cut by a claim commits its part before the claim

* fix(studio-server): a claim's cut ends a held drag and takes only the cut files out of a turn

* fix(studio-server): a held drag ends when someone else changed a file it already holds
2026-09-25 08:43:04 +00:00
Miguel Ángel fc8cb86ef6 ci: check TODO owners, public URLs and source links in comments (#4453)
* ci: check TODO owners, public URLs and source links in comments

* fix(ci): a TODO's issue link must be a github.com issue URL
2026-09-25 04:43:59 +00:00
Miguel Ángel 7c634426fd docs(cli): explain how catalog search works and which model it downloads (#4462)
* docs(cli): explain how catalog search works and which model it downloads

* docs(cli): name the model catalog search was chosen against

* docs(cli): keep only live rules in the word-search header comment

* docs(cli): correct the hosted vector size in the local vector build comment
2026-09-25 04:43:54 +00:00
Miguel Ángel 53e2e20a15 fix(registry): six blocks load their own files and variables when mounted (#4452)
* feat(core): a composition's script can load its own files with __hyperframes.assetUrl

* test(core): a template composition inside a mounted file resolves assets from that file

* docs: assetUrl is for paths beside the composition, not project-root paths

* docs: say assetUrl misses a project-root path

* fix(core): a mounted composition's import map and module scripts run as such

* refactor(core): scope a mounted module script under the same condition as a classic one

* fix(core): a mounted module script loaded by src stays a module

* fix(core): a mounted import map keeps the trailing slash of a prefix address

* fix(registry): six blocks load their own files and variables when mounted

* fix(registry): keep frost's variable declarations on its html element

* fix(registry): orbit-card uses its own variables; frost font and catalog maps follow

* chore(core): keep the module-script helpers' comment share at the base

* fix(catalog): frost's payload runs its driving script, not the asset-base one

* test(catalog): check import-map keys by membership, not a built regex
2026-09-25 04:43:43 +00:00
Miguel Ángel 0e34d69641 ci: comment checks grade branches cut before their scripts existed (#4466) 2026-09-24 23:28:36 -04:00
Miguel Ángel 3520c8225c ci: a package file's comment share may not rise (#4445) 2026-09-24 22:06:50 -04:00
Miguel Ángel 3194c2ae87 ci: grade the comments each PR touches (#4444) 2026-09-24 20:26:15 -04:00
Miguel Ángel 8dabc8af63 fix(registry): glass-shard-title renders when mounted, and 4 skipped blocks install again (#4423)
* fix(registry): glass-shard-title renders when mounted, and 4 skipped blocks install again

* fix(registry): glass-shard-title loads in Studio and ?play, and its docs payload fails loudly

* test(scripts): pin the glass payload's hdr guard for any quote style
2026-09-24 13:08:15 -04:00