Carry the runtime's blur through the render pipeline and the Studio render
panel.
- engine: installPageMotionBlur sets the page-side off switch and the render's
device scale before any author script runs; CaptureOptions.pageMotionBlur
lets a caller stand the page blur down.
- producer: a composition that asks for data-motion-blur on the root the
runtime would pick forces screenshot capture, because html-in-canvas needs
a real paint; the probe session carries the request's pageMotionBlur like
every other session.
- studio-server and Studio: the render panel sends previewMotionBlur, the
render route turns "off" into pageMotionBlur once, and the Studio dev server
and the CLI server both receive the final value.
- mover scan: a large element that is see-through while it moves (a sweep, a
light leak) is no mover, including a large image or canvas with alpha; the
scan skips elements moved by a 3D individual transform property.
- cli snapshot injects video frames before the seek settles, matching the
engine's order.
Folded review fixes: R1, R2, R3 (scan half), R7, R16, R18, T3 (render
adapters).
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* feat(studio-server): accept a crf override on the render route
The Studio render route only forwarded draft/standard/high, so a Studio or
desktop export could not ask for a specific CRF the way `hyperframes render
--crf` can. Film-grain finishing passes need CRF 12 to keep the grain.
The route now takes an optional integer `crf` (0-51 for mp4, 0-63 for webm),
rejects an out-of-range value with 400 instead of dropping it, ignores it for
mov, and both Studio adapters pass it to the render job.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore(studio-server): keep the crf change free of new comment lines
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(studio-server): accept videoBitrate, videoFrameFormat and hdrMode on the render route
Same options `hyperframes render` already takes (--video-bitrate,
--video-frame-format, --sdr/--hdr), so Studio callers are not limited to
the quality presets. crf and videoBitrate are an either/or, matching the CLI;
an invalid value is a 400. Parsing moves to helpers/encoderOptions.ts.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(studio): export can encode on the GPU, like render --gpu
* test(studio): the 4K export assertion carries the GPU flag
* refactor(studio): the GPU flag needs no comment beyond its name
* fix(studio-server): only MP4 exports encode on the GPU, and render errors report it
* fix(studio): the disabled GPU option dims like the other export controls
* fix(studio-server): keep the desktop app's hand-off and chat history out of the file routes
The file and preview routes reach any path inside the project. The desktop
app keeps two files there that hold the person's agent session and their
chat: .hyperframes/agent-handoff.json and .hyperframes/app-history.jsonl.
No route now reads, writes, renames onto, duplicates or uploads over them,
by any spelling or through a link. The rest of .hyperframes/ (Studio's
motion file, prepared GIF assets) is served as before.
* fix(studio-server): keep the desktop app's run log out of the file routes too
The app records every chat run, the person's words included, in
.hyperframes/agent-runs.jsonl. Only the app's main process reads it.
* fix(studio-server): refuse everything under .hyperframes/ but Studio's own files
Review (Jerrai, Rames): a list of three names missed the read marks the
desktop app and the CLI also keep there, and renaming the folder reached
them under a new name. The check is now an allow-list: no route reaches
.hyperframes/ or anything in it, by any spelling or through a link, except
Studio's studio-motion.json, studio-manual-edits.json and prepared-assets/.
Studio's own internal files (backups, preview documents, history id) are
read by path inside the server, not through a request path.
The project's file tree leaves out the same paths. The upload test now
sends a file uploads accept elsewhere, so the folder is the only reason
it is refused.
* fix(studio-server): serve the app's request pictures, close the renders and rename paths
Review (Rames, Jerrai):
- Desktop loads each request's picture from .hyperframes/requests/ through
the preview route, so that folder joins the allow-list. An allowed
folder's own path is allowed too.
- The render-file route resolves against renders/, so a renders/ linked
into .hyperframes/ reached the records. It now also checks the file
against the project.
- A rename's reference update walked every JSON/MD file, records
included, and could rewrite them. It now skips private paths.
- The allow-list reuses STUDIO_MOTION_PATH and STUDIO_MANUAL_EDITS_PATH.
* chore(studio-server): keep render.ts's comment share at the base
* fix(studio-server): list, view and download only renders the render-file route would serve
Review (Jerrai, Rames): the render list found files in renders/ with join
and statSync, which follow links, and registered them for /render/:id/view
and /download, which read them with no check. A project shipping
renders/leak.mp4 -> ../.hyperframes/app-history.jsonl had its chat served.
The list now keeps an entry only when resolveWithinProject accepts it and
it isn't private, and registers the resolved path. View and download serve
only a plain file, so a link swapped in after the list is refused. This
predates the PR; it's the same shipped-link case.
* feat(studio-server): share loudness measurement with an absolute target and video support
normalize-audio moves its measure/plan code into @hyperframes/studio-server/loudness,
gains an absolute mode (--lufs, default -16, clamped at +12 dB and -1.5 dBTP) and
accepts <video> clips with sound. Adds the @hyperframes/core/audible-video subpath.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(studio-server): add a measure-only loudness normalize endpoint
POST /projects/:id/loudness/normalize measures a clip's played window (audio or
video) and answers the -16 LUFS data-volume plan; Studio writes the attribute.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: register the loudness and audible-video subpaths in their descriptors
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* style: format the loudness and audible-video subpath descriptors
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(studio): add Normalize loudness and Duck under voice to the clip menu
Normalize measures the clip on studio-server and writes data-volume as one undoable
edit; Duck toggles a carve against every overlapping voice (audio or video with
sound) at the default strength. Both live in clipMenuAudioItems with one hook-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(studio): tighten carveOutput docs to the package comment share
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(producer): report how far the true-peak limiter lowered the export
The AAC true-peak pass now returns its attenuation; the render logs "Audio lowered
by X.X dB to stay under -1 dBTP" and sets RenderJob.audioLoweredDb, which Studio
servers carry in the complete SSE event and the /renders record. Also exports
useAudioMetersVisible from @hyperframes/studio for hosts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(studio-server): scan HTML start tags without a backtracking regex
The start-tag regex backtracked quadratically on '<' followed by many spaces
(CodeQL js/polynomial-redos). A linear character scan reads the same name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(studio): refuse to normalize a volume-automated clip and honour save outcomes
A volume lane owns the clip's gain in preview and render, so writing
data-volume changed nothing while the toast claimed success; Normalize now
refuses with a clear message. Normalize and Duck also check each quiet-save
outcome, stop at the first failed or refused write and report it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(core): drop the duplicate audible-video subpath descriptor
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(studio-server): a save never recreates a project folder renamed away
* style(cli): wrap imports after merging main
* fix(studio-server): proxy and upload answer that the project folder is gone instead of 502 or an empty 201
* test(studio-server): rename the project folder while an upload is being read
* fix(studio-server): a proxy or backup answers that a vanished project folder is gone
* test(studio-server): clean up the renamed folder after the proxy test
* refactor(studio-server): drop an unused re-export and share the folder-gone test setup
* fix(studio-server): any request failing after its project folder vanished says the folder is gone
* fix(studio-server): the folder-gone answer works on every host and drops the failed route's headers
* fix(studio-server): the folder-gone 404 keeps the host's own headers
* fix(core): no folder outside a project is created once the project folder is gone
* test(studio-server): a rename that makes the codec probe fail answers that the folder is gone
* test(studio-server): a refused render leaves no outside renders folder behind
* docs(core): wrap the mkdirWithinProject comment under the line limit
* refactor(studio-server): let the folder-gone middleware's names carry what its comments said
* fix(studio-server): say the folder is gone when a render names a composition
* fix(studio-server): check renders against the folder path and share one folder-gone answer
* test(studio-server): pass the project folder to the preview document store
* fix(studio-server): a save never recreates a project folder renamed away
* style(cli): wrap imports after merging main
* fix(studio-server): proxy and upload answer that the project folder is gone instead of 502 or an empty 201
* test(studio-server): rename the project folder while an upload is being read
* fix(studio-server): a proxy or backup answers that a vanished project folder is gone
* test(studio-server): clean up the renamed folder after the proxy test
* refactor(studio-server): drop an unused re-export and share the folder-gone test setup
* fix(core): no folder outside a project is created once the project folder is gone
* test(studio-server): a refused render leaves no outside renders folder behind
* docs(core): wrap the mkdirWithinProject comment under the line limit
* test(studio-server): pass the project folder to the preview document store
Build RFC 6266 Content-Disposition values with an ASCII filename fallback and UTF-8 filename* parameter for each render file response. This preserves inline and attachment disposition semantics while preventing non-Latin-1 filenames from crossing the HTTP ByteString boundary.
Co-authored-by: heygengenesis[bot] <262951085+heygengenesis[bot]@users.noreply.github.com>
Co-authored-by: miguel.sierra <229591595+miguel-heygen@users.noreply.github.com>
- A long-lived preview cached its telemetry posture in two places
(readConfig and shouldTrack). Running `telemetry disable` in another
terminal left it resolving canaries and injecting the CLI id for hours.
Both caches are now dropped together at a request boundary.
- Studio minted and shipped a telemetry id for every render regardless of
the browser profile's opt-out, and the server emitted the outcome under
CLI policy, which cannot see localStorage or DNT. The browser now sends
an explicit telemetryOptOut, distinct from an old client's omission.
- Any non-empty HYPERFRAMES_PREVIEW_HOST disabled the DNS-rebinding guard,
so even a loopback bind accepted a hostile Host. The guard now holds for
loopback binds and, on a LAN bind, admits only names this machine
answers on.
- sunsetAfter had no reader of the current date. A scheduled workflow runs
scripts/check-canary-sunset.ts weekly, so a failure lands on the
rollout's owner rather than on an unrelated PR author.
- The install-state seed memo outlived `rm -rf ~/.hyperframes`,
resurrecting a cleared cohort. Removed; it only saved a read on a
readConfig cache miss.
Docs updated for the Host rule and the 100% exclusion carve-out.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fourth PR of the template-variables Studio stack — the HTTP plumbing.
- preview routes (/preview and /preview/comp/*) accept
?variables=<url-encoded json> and inject
`window.__hfVariables = {...}` into <head>, before the runtime and any
composition script — the exact global the engine sets via
evaluateOnNewDocument at render time, so preview-with-values cannot
diverge from render output. Values are escaped against </script>
breakout, malformed payloads 400 instead of silently previewing
defaults, and the ETag is salted with a hash of the payload so cached
previews revalidate when values change.
- POST /projects/:id/render accepts variables ({variableId: value}) and
forwards them through StudioApiAdapter.startRender into the producer's
RenderConfig.variables — the same channel `hyperframes render
--variables` uses. Wired in both adapters (CLI embedded server + vite
dev adapter).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor: extract @hyperframes/studio-server package from core
Moves all studio-api routes, helpers, and Hono server wiring from
packages/core/src/studio-api/ into a new standalone packages/studio-server
package (@hyperframes/studio-server).
Core keeps thin re-export stubs at @hyperframes/core/studio-api and the
subpath helpers (screenshot-clip, draft-markers, etc.) for backward
compatibility. Consumer imports (cli studioServer, vite adapter/config,
producer htmlCompiler, studio manualEditsTypes) are updated to import from
@hyperframes/studio-server directly.
Also exports rewriteInlineStyleAssetUrls from @hyperframes/core root (was
in compiler/rewriteSubCompPaths.ts but not re-exported), required by
@hyperframes/studio-server/helpers/subComposition.
Removes postcss-selector-parser from @hyperframes/core dependencies (moved
to @hyperframes/studio-server which owns the routes that used it).
Depends on @hyperframes/parsers (PR #1755).
* fix(ci): add parsers+studio-server to Dockerfile and build before preview tests
* fix(ci): build @hyperframes/studio-server before Test and studio load smoke
Studio's vite.config.ts imports @hyperframes/studio-server, which resolves
via its "node" export condition to built dist. The Test and studio-load-smoke
jobs only built parsers + core, so esbuild's config load failed to resolve the
package entry. Build studio-server too.
* fix(studio): repoint sdkCutoverParity test import to studio-server
sourceMutation moved from core's studio-api to @hyperframes/studio-server;
the test still imported the deleted core path. This was masked while studio's
vite.config failed to load (couldn't resolve studio-server); now that the
config loads, the test runs and the stale import surfaced.