Commit Graph
100 Commits
Author SHA1 Message Date
ukimsanov aa928f399a style(scripts): oxfmt the nav-suppression assertion
The long assert.match line tripped oxfmt --check in CI (Preflight/Format red,
which cascaded regression/player-perf/preview-regression into skip-then-fail). The
pre-commit format hook doesn't cover scripts/*.mjs, so it slipped through. Wrapped
per the formatter. Flagged by Magi and Rames.
2026-08-05 11:47:59 -07:00
ukimsanov fed2baf278 docs: correct composition offset rationale; pin nav fix; label-in-name
- html-schema: the previous clause claimed a composition host's data-media-start is
  never read. It is: readElementPlaybackStart (media.ts:16) resolves
  data-playback-start ?? data-media-start and timeline.ts calls it on composition
  clips. Rewrote to the accurate reason both reviewers gave — composition hosts are
  only inspected by the playback-start-first readers, media-start works as a
  fallback, but playback-start is what Studio writes/normalises to.
- motion test: pin the HoverVideo click-suppression (preventDefault +
  stopPropagation). Removing it left the gate 12/12; now it fails. This is the bug
  that escaped static review and only surfaced by driving the live preview.
- replica-compare: fold the visible 'Sound off/on' text into the aria-label so the
  accessible name contains it (WCAG 2.5.3, Rames).

Round-5 findings from Magi and Rames.
2026-08-05 11:32:21 -07:00
ukimsanov 83db94e75e docs(examples): stop the sound control from following the card's link
Grid cards with a public source are wrapped in an <a>; the sound/play button
lives inside it, so a click — mouse or keyboard — bubbled up and navigated to
GitHub instead of toggling sound. preventDefault + stopPropagation on the control.
Found by driving the live preview (self-review).
2026-08-05 11:12:55 -07:00
ukimsanov f3d1d531d1 docs(examples): reset control state on offscreen release; precise a11y labels
- replica-compare: the offscreen teardown now resets muted (element + React state),
  matching HoverVideo — a pair unmuted before it scrolled away no longer returns
  reading 'Sound on' over a paused, sourceless pair (Magi blocker).
- Both controls' aria-labels now follow the mode: under reduced motion the button
  plays/pauses the whole comparison, and when a preview is already autoplaying
  muted the action is 'unmute', not 'play with sound' (self-review + Rames).
- Hardened the motion-suite assertion to scope 'startBoth' to toggleSound's body
  (a defined-but-unused helper no longer satisfies it) and pin the offscreen
  muted-reset transition (Rames mutation-test gap).
- html-schema: 'hyperframes validate' inspects <audio> only; note that no
  media-start-only reader inspects a composition host, so the kind rule strands
  nobody (Rames).
2026-08-05 10:59:29 -07:00
ukimsanov c49fea2e0c docs: complete the media-start layer map + fix the third page
Per Rames: add the engine audio mixer (audioMixer.ts:350 -> ffmpeg -ss) to the
media-start-only readers — it's the live path that makes a lone data-playback-start
a shipped output bug (trimmed picture over untrimmed audio). 'Set one' isn't enough;
give the element-kind rule: <video>/<audio> use data-media-start, nested composition
uses data-playback-start (Studio writes it; it's the child-timeline offset). Also fix
editing-existing-videos.mdx, which offered both names as interchangeable for front
trim of a clip — the exact kind where they aren't.
2026-08-05 10:45:32 -07:00
ukimsanov 630c8900f5 docs(examples): reduced-motion play starts both films; precise media-start layers
- replica-compare: the voluntary control now starts and pauses BOTH films (not
  just the reference), and the replica-sync effect attaches in view regardless of
  the preference, so a reduced-motion visitor who presses play sees the whole
  synchronized pair. Added a focused source-level assertion to the motion-check
  suite (the repo has no React runtime harness for snippets).
- html-schema: describe each layer precisely instead of grouping the CLI —
  timing compiler, HTML parser, producer audio, and 'hyperframes validate' read
  only data-media-start; runtime, Studio and 'hyperframes snapshot' read
  data-playback-start first (Studio also writes it).

Round-3 findings from Magi.
2026-08-05 10:41:34 -07:00
ukimsanov b541245f1a docs(studio): note the third arrow-key meaning after merging Timeline navigation
The merged '## Timeline navigation' section documents a third arrow-key behaviour;
the 'selected area decides' paragraph read as exhaustive with only two. Fold in the
timeline-focus case and link the section. Coherence note from Rames.
2026-08-05 10:25:06 -07:00
ukimsanov d373c3f4a0 fix(catalog): explicit section ownership, no body-sniffing heuristic
carriedSectionsFrom() decided whether a ## Usage section was generated by matching
its first line against a list of historical opener phrases — so a hand-written
Usage section that happened to open that way was classified as generated and
silently deleted on regeneration. Ownership is now purely set membership: a
section is generated iff its heading is one the template emits, and ambiguous
'usage' is no longer in that set (the template never emits it), so any ## Usage is
carried. Exported carriedSectionsFrom behind an entrypoint guard and added two
executable preservation fixtures. Flagged by Magi (#5).
2026-08-05 10:23:37 -07:00
ukimsanov c9fd7465c9 docs(examples): allow voluntary playback under reduced motion; a11y parity
Both snippet players now keep a source assigned whenever in view (preload='none'
so nothing downloads until asked), gating only autoplay on reduced motion — so a
reduced-motion visitor can press play/unmute instead of being left with an inert
poster. HoverVideo renders a control on hasAudio={false} cards too (a play/pause
toggle), and ReplicaCompare no longer calls play() on a source it just removed.
Gave ReplicaCompare's button the same aria-pressed + focus-visible as HoverVideo.
Round-2 finding from Magi (#1) and Rames.
2026-08-05 10:17:40 -07:00
ukimsanov 8e658bd6c9 docs(sdk): fix dispatch/can contract, second stale GSAP site, media-start layers
- edit-operations: dispatch() does not consult can() (session.ts:608 -> applyOp
  with no validation; a no-timeline addGsapTween/addLabel is a no-op but a missing
  target still writes via selector fallback). Use Rames's wording: call can() first
  and skip on failure — no false 'applies nothing' guarantee.
- timing-and-animation: the second E_NO_GSAP_TIMELINE site — gated setGsapTween on
  an error it cannot return and called shipped parser code 'a later phase'. Rewrote
  to gate addGsapTween (which can return it); dropped the stale can() comment in
  types.ts:595.
- html-schema: data-playback-start is read by runtime, Studio and CLI (Studio also
  writes it, timelineEditingHelpers.ts); only the compile path is media-start only.
  Document the layered precedence instead of calling it runtime-only.

Round-2 findings from Magi (#2/#3/#4) and Rames.
2026-08-05 10:13:17 -07:00
ukimsanov aa49f7d924 Merge remote-tracking branch 'origin/main' into docs/pages-show-not-tell 2026-08-05 05:56:59 -07:00
ukimsanov aa077e000d docs(examples): intersection-gate the recreate pair too
ReplicaCompare now assigns/decodes its reference+replica only near the viewport
and releases both (pause + removeAttribute + load) on exit, closing the last
eager-load surface from Magi's #5. Sync + reduced-motion guard preserved.
2026-08-05 05:54:49 -07:00
ukimsanov 8cadefa5d8 fix(scripts): import the changelog style note instead of duplicating it
changelog-weekly.ts hard-copied the CHANGELOG_STYLE_NOTE literal that set-version.ts
exports; both feed the release gate, so a drift silently weakened it. Import the
constant so the two markers cannot diverge. Flagged by Copilot and Rames.
2026-08-05 05:51:16 -07:00
ukimsanov 0467bc8129 docs(examples): make hover-audio accessible, lazy-loaded, and guard-complete
HoverVideo now: (1) exposes a focusable button that toggles/plays sound by
keyboard, touch and pointer — hover is an enhancement, not the only path;
(2) gates the source behind an IntersectionObserver so offscreen cards no longer
download/decode, releasing the buffer on exit; (3) still blocks autoplay under
reduced motion while allowing voluntary playback via the button. Routed all 30
Thirty-Days cards through it (hasAudio={false} for the 12 silent ones) so no raw
<video autoPlay> bypasses the motion guard. Flagged by Magi (P1 #4/#5/#6).
2026-08-05 05:48:23 -07:00
ukimsanov 0f6259d461 docs: correct three source-contradiction findings from review
- edit-operations: E_NO_GSAP_TIMELINE is raised only by addGsapTween and addLabel
  (mutate.ts:1747), not the set/remove tween ops; drop the false 'dispatch() still
  applies structurally' claim (mutate.gsap.test.ts verifies zero patches); add the
  requirement to the Labels section where addLabel lives.
- html-schema: data-media-start is read by parser + timing compiler + runtime;
  data-playback-start is a runtime-only alias, so a lone playback-start doesn't
  shift trim/split. Document the surfaces instead of claiming one universal value.
- testing-local-changes: bun unlink cleanup used a relative cd that resolves under
  the video project; use an absolute checkout path.

Flagged by Magi (P1 #2/#3, P2 #7) and Rames.
2026-08-05 05:43:35 -07:00
ukimsanov 6ff6601dd0 fix(catalog): restore the required Related topics section on generated pages
My generator rebuild replaced the '## Related topics' section (still required by
docs/AGENTS.md) with the provenance footer, dropping it from all 168 generated
Catalog pages. Emit it again as the final section so pages end with it, and stop
carriedSectionsFrom() breaking at the footer marker so a human section appended
below the generated tail survives a regeneration. Adds a per-page regeneration
assertion so a future drop fails CI. Flagged by Magi (P1) and Rames.
2026-08-05 05:38:58 -07:00
ukimsanov b62c3fd656 docs(theme): brand the Playground CTA + align the navbar logo
Repaint the Playground pill with the HyperFrames mark's own cyan-to-green gradient
(#06E3FA -> #4FDB5E) with dark text, instead of the muted primary-dark. Left the
site-wide primary untouched so link contrast stays AA. Also nudge the logo 16px so
its left edge lines up with the sidebar text instead of sitting at the navbar padding.
2026-08-05 04:02:41 -07:00
ukimsanov c53c7efd1e docs(nav): lift 30 Days above the update feeds in Explore 2026-08-05 03:54:45 -07:00
ukimsanov 4b1a526fac docs(examples): fix silent recreate references (stale keys -> new audio keys)
The reverse-*-reference.mp4 keys on the CDN were serving the original silent cut
(immutable cache never took the audio re-upload). Re-encoded from the audio sources
to reverse-*-reference-snd.mp4 and pointed ReplicaCompare at them, so the reference
now actually plays sound on click.
2026-08-05 03:40:27 -07:00
ukimsanov 821189586f docs(examples): k3 promo now hover-to-hear with its audio version from X 2026-08-05 03:29:39 -07:00
ukimsanov 630c6c880d docs(examples): replace two padded launch films + wire figma audio
The claude-design (54s) and music-to-video (181s) docs films were the real ~40s/~45s
films padded with black / a held end-logo. Swapped both for their correct X launch
versions (40s and 90s) which also carry audio, and wired figma to its X source. All
three now hover-to-hear.
2026-08-05 03:21:46 -07:00
ukimsanov 59a2ee31c9 docs(examples): more hover-audio + a 'Where HyperFrames plugs in' strip
Swapped five more grid films (launch, keyframes, pr-to-video, skills, sound) to
their content-matched X launch versions with audio. Added an integrations strip
(Codex, Vercel, Ollama, hyperframes.dev community) from the same archive. Skipped
the square/different-cut sources (figma, claude-design, music-to-video) to avoid
cropping or swapping in a different edit.
2026-08-05 03:09:14 -07:00
ukimsanov e6bc8f139c docs(examples): hover-audio on six more grid films from the X launch archive
variables, texture, website-to-video, vfx, frame.md and inspector each have a
content-matched launch film on @HeyGen's X with audio; re-encoded those to -s.mp4
and wired HoverVideo. Launch grid now has 10 cards with hover-to-hear.
2026-08-05 02:58:25 -07:00
ukimsanov fb6a82c505 docs(examples): hover-to-hear on the four launch films that have audio masters
HF x Stripe, SpaceX, Cloud rendering and Timeline had audio-baked render masters
in hyperframes-launches (via LFS); re-encoded those with sound to -s.mp4 keys and
wrapped their cards in HoverVideo. The rest of the grid has no ready audio master
(silent renders; audio only as unmixed assets) and stays muted.
2026-08-05 00:53:28 -07:00
ukimsanov 5361b9e10c docs(30-days): hover a card to hear the film's audio
Adds a HoverVideo snippet (autoplay muted, unmute + play on hover, re-mute on
leave) and points the 18 days whose source has real audio at new -s.mp4 keys
re-encoded with sound (CDN is immutable, so new keys not overwrites). The other
12 days have genuinely silent sources and stay muted. Honours prefers-reduced-motion.
2026-08-05 00:36:07 -07:00
ukimsanov 8298bd4390 docs(examples): drop three dead source links (no such launch folder)
skills-launch-video, keyframes-launch and music-to-video-launch have no folder
in hyperframes-launches. The films are real but their source was never published,
so these cards were linking to 404s and falsely claiming 'source included'. Made
them non-linked cards labelled '100% HyperFrames', like the other sourceless ones.
2026-08-05 00:16:29 -07:00
ukimsanov 407a5fd513 docs(examples): bind reference video muted to state so unmute sticks
React was re-applying the static muted={true} on every re-render, instantly
re-muting the reference after a click. Bind muted to state instead.
2026-08-05 00:12:12 -07:00
ukimsanov bde03484ae docs(examples): sync the recreate pairs with a tap-to-unmute button
The reference and replica play on one shared clock; a single Instagram-style
button unmutes the reference (its original audio), the replica stays silent.
Reference videos re-encoded with audio on the CDN. The component honours
prefers-reduced-motion on both edges, per the docs-snippet-motion guard.
2026-08-05 00:04:26 -07:00
ukimsanov 98ae45e066 docs(examples): add 1:1 recreate-any-video comparison block
Two reverse-engineering campaigns (THE OBLIST, Srinika x Mercury), each
showing the reference original beside its frame-matched HyperFrames replica.
Assets on the docs CDN; kept separate from the '100% HyperFrames' grid since
the reference films are the originals.
2026-08-04 23:42:39 -07:00
ukimsanov 31366d724d docs: draw the project model instead of describing it twice
The page that explains how the whole thing works had one Studio screenshot, 13
table rows, and two ASCII trees doing a diagram's job.

Two mermaid diagrams now. The first is the page's central claim made visible —
agent, Studio, CLI, SDK and Player all reading and writing one project folder,
with the render falling out of it. The second replaces the ASCII composition
tree with the same structure carrying real times and the file each scene lives
in, which the ASCII version could not show.

Verified rendering in the browser, not assumed.
2026-08-04 03:30:16 -07:00
ukimsanov f595a7d683 docs: the last five blind guides now show the thing
Rendering, Performance, Deploy, Figma and Bring in a design.

**Figma is the one worth reading about.** The first attempt failed honestly — no
token, no file key, so it refused to fake an import and built a generic design
handoff instead, where both panels looked identical and nothing was demonstrated.
A real token and file key were sitting in bug-bash-figma-hf. With genuine access
it makes the actual point: the same cursor and the same clicks land on a PNG export
and on the imported HTML, and only the HTML responds — the field takes text, the
button runs through its states. One side is pixels, the other is a component. It
replaces the placeholder comment on Bring in a design too.

**Performance is measured, not asserted.** 25.0s against 9.8s as the median of
three runs at 1920x1080 over 300 frames, one CSS declaration responsible for the
whole gap, and a pixel comparison at 43.7 dB PSNR confirming the picture did not
change. Real numbers from real runs on this machine.

**Rendering** shows the loop end to end. **Deploy** shows the four places a render
can run and labels anything not executed live.

All four carry their own audio at about -19 dB. Each was checked on a contact
sheet and for dead frames before publishing.

Guides with nothing to look at: 15 at the start of this work, 5 now. The five
left are pages where a video would be decoration — authentication, feedback, a
decision table, a comparison that carries code, and 4K, which is a paragraph
about a flag.
2026-08-04 03:14:43 -07:00
ukimsanov d16f6e69d7 docs: move 19 anchor links onto the headings that now exist
The capstone and determinism rewrites renamed headings that other pages point at.
Eighteen prompting pages linked to capstone#the-full-prompt-verbatim, which is now
'The prompt, word for word', and the rewritten determinism page invented an anchor
on frame-adapters that was never there.

Moved the links rather than restoring the old headings — 'the full prompt,
verbatim' is exactly the register the rewrite was cleaning up.

Broken anchors site-wide: 0. mint broken-links does not check anchors, so none of
this would have failed CI.
2026-08-04 03:09:00 -07:00
ukimsanov eb199bd159 docs: make the two hardest-reading pages readable, and draw the two core concepts
**Prompting.** These were the only two pages that stayed hard to read once the
sentence counter was corrected for bullet lists. Capstone went from 46% of
sentences over 28 words to 20%, and 53 semicolons to 42. Motion went from 37% to
15%, 26 semicolons to 4, and lost six table rows that were prose in a grid. Both
keep every technique and every worked example — the length barely moved, the
sentences did.

**Concepts.** Compositions and Determinism explain how the whole thing works and
showed the reader nothing. Each has one mermaid diagram now: how an HTML file
with data-* timing becomes a numbered sequence of frames, and why frame 90 always
comes out the same — what is locked before frame 0, and the three things that
break it.

Mermaid renders natively in Mintlify, so these cost no asset, no CDN upload and no
regeneration. Verified in the browser rather than assumed; the pattern is the one
proven on concepts/frame-adapters.
2026-08-04 03:08:04 -07:00
ukimsanov 8762887a5b docs: four more guides show the thing instead of describing it
HTML in Canvas, MCP, Skills, and Images and video all had nothing to look at.

**HTML in Canvas** puts a real HTML dashboard beside the same pixels running as a
live WebGL texture, bent by a shader while the DOM underneath keeps updating and
the texture re-captures every frame. That is the whole feature in one shot.

**MCP** is an honest reconstruction and says so on screen for its full length —
"Illustration of the flow". It could not reach a live connector, so it does not
pretend to have one. Describe the video, revise it in plain language, ask for a
format, get the render.

**Skills** shows a real run of the install command, then the router reading two
different requests and picking a different workflow for each.

**Images and video** walks the five basic moves — import, place, crop, trim, swap
the source — adding one attribute at a time on the left with the result on the
right. It serves both /guides/media and /guides/video-components.

All four carry a music bed from HeyGen's own catalogue at about -22 dB. Each was
checked on a contact sheet before publishing, not taken on the session's word.

Blind guides: 15 down to 9.
2026-08-04 03:08:04 -07:00
ukimsanov 951c6ea45c docs: point two prompting links at headings that exist
Both went to /guides/color-grading anchors that the guide rewrite removed. The
content did not disappear — it lives on /reference/color-grading as 'Animate a
supported property' and 'Limit a grade to part of the frame'. A reader clicking
either landed on the right page and scrolled nowhere.

mint broken-links does not check anchors, so CI would never have caught this.
2026-08-04 03:08:04 -07:00
ukimsanov 60f09dfcfd docs: give every click-to-play video an audio track
Ten videos on the docs site played in silence. The player is click-to-play, so a
reader deliberately starts them and gets nothing — which reads as broken rather
than as a stylistic choice.

The music is from HeyGen's own catalogue, pulled through the media-use skill's
resolve path with the account already authenticated on this machine. That matters
more than convenience: the launch projects on disk carry a `launch-music-trimmed.mp3`
with no licence, no attribution and no metadata, and three Pixabay tracks. Neither
belongs on the official docs CDN, where objects cache immutably for a year.

Mixed at roughly -22 to -24 dB mean so it sits under the picture. Getting there
took two corrections worth recording: proportional fades, because a 1.2s fade-out
on a 5s clip eats half the audio; and seeking 11s into the bed, because its first
ten seconds are a quiet intro that dragged every short clip down to -27 dB.

The three Examples tiles are left silent on purpose — they are muted autoplay
loops, so an audio track would only be weight.

Verified: every video referenced anywhere under docs/ that is not a muted
autoplay tile now has an AAC stream, checked against the live CDN URL rather than
the local file.
2026-08-04 03:08:04 -07:00
ukimsanov 61cd3f1da2 docs: keep the audio on the Huly launch film
I stripped the audio track with -an when encoding for the web. The source has a
real mix at -19.2 dB mean / -3.1 dB peak, and the docs player is click-to-play,
so there is no reason for it to be silent. Re-encoded with AAC at 128k; levels
match the source exactly.

The other published films have no audio track at all to preserve — they were
rendered silent at source.
2026-08-04 03:08:04 -07:00
ukimsanov ef99809482 docs: show GSAP keyframes moving, and drop the weakest video on Examples
The animation guide had nothing to look at, which is a strange state for a page
about motion. It now opens with the keyframes launch film — real `gsap.to()`
source with a `keyframes` array on screen, and the same motion being reshaped by
dragging an ease curve in Studio instead of guessing numbers.

Examples loses the ten-second reference-project render. It sat directly beneath
four finished films and lost the comparison every time; the four cards above it
already carry the point that the project is open end to end. The cards stay, the
render goes.
2026-08-04 03:08:04 -07:00
ukimsanov 2a7d9bd0ad docs: rebuild the catalog generator, and show what background removal and HDR do
**The catalog generator destroys hand-written documentation on every run.** It
does `rmSync(dir, { recursive: true })` on both catalog folders before
regenerating, so any section a human added to a generated page is deleted the
next time anyone runs it. There is now a carry-forward pass that reads those
sections off the existing pages first and re-emits any heading the template does
not own. Note this prevents future loss only — the earlier regeneration in this
stack already removed what was there, and it is recoverable from git if wanted.

It was also clobbering `docs.json`: rebuilding the Catalog tab dropped its icon
and unlinked `catalog/index.mdx` from the sidebar entirely.

And 27 of 36 component pages told the reader to "see the comment header in the
file" when no such header exists. The generator reads the file now and only says
it when true.

On presentation: 1,365 table rows across 168 pages became 0. `## Details` was a
table whose rows were "Type: Block" and the duration; `## Files` was three
columns where most items have exactly one file. Both are one sentence now. The
preview leads the page instead of sitting under title, description, tags, a
warning and a credit — four of which Mintlify already renders from frontmatter.

**Two guides that showed nothing now show the thing.** Background removal plays a
real cutout with the matte magnified at the hair edge and at a shoulder a plant
overlapped, because those are where matting fails. HDR is the honest one: you
cannot show HDR brightness on an SDR page, so it shows the `ffprobe` verdict and
a measured round-trip — 4000 nits authored, 4012 back out — and says on screen
for all 26 seconds that it is not simulating anything.

Both films were checked before publishing: no black or frozen stretches, and read
at the real 590px docs column width rather than full size. Nav after regeneration:
307 pages, 0 dangling, 0 redirect collisions.
2026-08-04 03:08:04 -07:00
ukimsanov 72d4016f5f docs: rewrite four pages that told instead of showed
**Color grading** was the worst page in the docs by the measure that matters:
33 table rows against one image, for the most visual feature in the product.
Four of the five tables were restating jargon in a grid — "Waveform | Brightness
by horizontal image position" tells you nothing you could act on. Those are prose
now, and the scopes section explains what each one is *for* (skin tones land on
one known line of a vectorscope, so faces are quick to check). The control lookup
stays a table, because that one is a genuine lookup.

Four factual errors in the old page, each verified at source before changing:
Studio's group is **Grade** in the flat Inspector, not "Color grading" in Design;
the controls are `whites`/`blacks`, not "white point/black point"; LOG and HDR
were conflated into one sentence when they are different cases; and the
project-wide LUT copy refuses a relative path in favour of a URL or data URL, not
a "project-root path".

Two things the page never mentioned and should have: `hyperframes grade-compare`,
which renders every candidate look onto one reference frame, and the fact that
`data-color-grading` nests under adjust/details/effects — a flat object renders
nothing at all.

**vs Remotion** argued for 851 words with nothing to look at and no code. It now
carries the same three-second title card written both ways. The HyperFrames one
is a real composition, not illustrative markup: extracted and run through lint
(0/0) and the full browser gate — runtime, layout, motion and contrast all pass.
It also concedes what Remotion genuinely does better, because a comparison page
that only flatters us is not worth reading.

**Choose how to create** never said what the four options were before comparing
them. It opens with the actual decision now — they all give you a video, the
difference is what you keep — and a four-row table of what you walk away with.

**Bring in a design** loses the 7-row keep-vs-rebuild table for one plain rule,
and the bullet list that used semicolons.

Every internal link on all four checked against the real page set. Where a visual
belongs but no approved asset exists, there is a JSX comment marking the spot
rather than a URL that would 404.
2026-08-04 03:08:03 -07:00
ukimsanov 84b5160b66 docs: show the work on Media effects, Product launch, and Examples
Three pages that described capability instead of showing it.

**Media effects** named eighteen effects as a bulleted list of words and showed
four of them in one small PNG. A reader could not see what Kuwahara Paint,
Ordered Dither or CRT Curvature actually look like. It now leads with the
finished media-treatments showcase film — every effect family on real footage,
with labelled before/afters — and the "Choose the effect" table is gone, because
six rows were saying what four sentences say better.

**Product launch** embedded a 44-second cut of small UI panels on black. Replaced
with the film this workflow's own skill produced from huly.io: the product's real
screens, its type and colour, bold editorial layout. Verified before publishing —
no black stretches over 0.4s, no freezes over 1s, captions clean at full
resolution.

**Examples** gets its four project cards back. Cutting them to one link was my
misreading of the actual complaint, which was about the reference-project video
sitting underneath four finished films and losing to them.

Both new assets are published to the docs CDN under versioned filenames and
return 200.
2026-08-04 03:08:03 -07:00
ukimsanov da35679bf9 docs: widen the accordion exception to the pattern it actually covers
Rames' first item on #2976. The clause named one page while the tree has 48
accordions across eight files: 20 in the verified-example gallery it means, and
28 under per-page `## Variants` headings — which the component table two lines
above routes to `Tabs`.

That contradiction matters more than usual because this file instructs agents.
The next one asked to bring the docs in line reads 28 compliant blocks as
violations and converts them.

Named by pattern now, with the reason the Tabs row does not reach them: Variants
blocks hold long alternative prompts a reader picks one of to read in full, not
parallel ways to perform the same step. Pre-existing and untouched by this PR —
the fix is the sentence, not the tree.
2026-08-04 03:06:59 -07:00
ukimsanov e94a458fed docs: correct two contributor instructions that the source contradicts
Both P1s from Miguel's review on #2976.

**The weekly changelog command has a fourth output, and it is public.**
`changelog-process.mdx` listed three internal drafts. `outputWeeklyDraft` also
calls `prependDocsUpdate` on `--write` (`scripts/changelog-weekly.ts:236`),
which writes straight into `docs/weekly-updates.mdx`. A contributor following
the page would review three files and push a fourth unread. Now called out as a
warning, with the re-run behaviour, since that is what makes editing the entry
afterwards safe.

The same paragraph also said Weekly updates is "unlisted in the sidebar". That
was true when written and I made it false myself, putting the page back under
Explore in #2978 after Rames found it orphaned. Corrected rather than reverted —
the page belongs in the sidebar.

**Bare `hyperframes lint` cannot validate a registry item.** Both
`contributing.mdx` and `contributing/catalog.mdx` told contributors to run it.
The CLI resolves a project by looking for `index.html`
(`packages/cli/src/utils/project.ts`), and registry items ship as `<name>.html`
or `demo.html`, so it fails with "No composition found". This is not
theoretical: `scripts/lint-registry-items.mjs` exists precisely for this shape
and its header records two `gsap_non_transform_motion` errors that reached main
unlinted because of it.

Both pages now document `bun run lint:registry-items`, and the Catalog page
shows the install-into-a-scratch-project route for the full `check` gate rather
than claiming a bare `check` covers a registry item.

Verified by running the documented command, not just by reading the script.
2026-08-04 03:06:59 -07:00
ukimsanov fd90fd3e72 docs: correct the docs rules and label the Prompt Guide previews
Three things the quality layer asserted but the pages did not support.

changelog-process said "do not recreate docs/weekly-updates.mdx; that empty
public page was retired" while the page exists with a real curated feed. It
describes the page as it is: unlisted, reachable by URL and RSS, with versioned
notes still in the Changelog.

The Related-topics rule read as universal, but three different endings are in
use and each is right for its shape — Related topics on task pages, a single
Next line through the Prompt Guide's numbered sequence, and nothing on
reference and concept pages. The rule now says that, and every section matches
it; color-grading gained the Next line it was missing and no longer points at a
page this stack retired.

The vocabulary previews were 18 DocsVideo players in three unlabelled grids, so
a reader saw several near-identical clips with no way to tell smooth from
snappy. They are now native muted loops with visible captions, which is what
AGENTS.md asks for small preview loops in the first place.
2026-08-04 03:06:58 -07:00
ukimsanov 174bd4e2dc docs: add documentation quality gates 2026-08-04 03:06:58 -07:00
ukimsanov 4ef1511b19 ci: re-run catalog previews when the containment module changes
Rames' non-blocking note on #2975. The paths filter listed the renderer but not
scripts/registry-target-paths.mjs, which it imports — so a future change to the
path-traversal defence alone would never re-run the only job that exercises it.

That is the same shape as the bug the module exists to prevent: the check is
present, the thing that would catch a regression in it is not wired to run.
2026-08-04 03:06:32 -07:00
ukimsanov dc2d1697fe fix(scripts): make registry path containment filesystem-aware
Miguel's second P1 on #2975, and he is right that my first fix only closed half
of it. `resolve()` and `relative()` are string operations and do not follow
links. Registry items are copied in recursively with symlinks preserved, so a
PR shipping `escape -> /tmp/outside` and declaring `target: "escape/pwned.txt"`
passed the lexical check, `mkdirSync` followed the link, and `cpSync` wrote
outside the project.

Reproduced before fixing: the old predicate returned one allowed copy and the
file appeared outside the project. Both directions were exposed — a symlinked
`path` reads a runner file in just as readily.

Containment is now filesystem-aware. No existing component of a candidate may
be a symlink, and the candidate's real location — resolved through its deepest
existing ancestor — has to sit under the project's own real path. A symlink is
refused rather than followed, even one pointing back inside the project:
nothing in the registry needs one, and following it would mean trusting the
target not to change between the check and the copy.

The tests are real fixtures now instead of string cases, because a purely
lexical suite is exactly what stayed green through the bypass. Twelve of them,
covering a symlinked target directory, a symlinked source file, a deeper path
through a symlinked component, an inward-pointing symlink, plus the lexical and
absolute cases from before.
2026-08-04 03:06:32 -07:00
ukimsanov bd7ea5d5ce fix(scripts): contain registry manifest paths in the preview renderer
Miguel's P1 on #2975, and it is real. `catalog-previews.yml` triggers on
`pull_request` for anything under `registry/blocks/**` or
`registry/components/**`, so `registry-item.json` arrives from the pull request
and is untrusted. `mirrorRegistryTargets` joined `files[].path` and
`files[].target` under the temp project and called `cpSync` on the result, and
`join()` walks out of its first argument. A `path` of `../../../../etc/passwd`
reads an arbitrary runner file into the project — which the job then uploads as
an artifact — and a `target` of the same shape writes an arbitrary runner path.

Both sides are now resolved and rejected when `relative(projectDir, candidate)`
is absolute or starts with `..`. Traversal that lands back inside the project
still works, so `nested/../demo.html` is unaffected.

Containment lives in `scripts/registry-target-paths.mjs` rather than inline,
because the traversal cases have to be testable and importing
`generate-catalog-previews.ts` drags in the producer. `existsSync` is injected
so the decision cannot depend on whether the target happens to exist on the
runner. Eight tests, covering traversal on each field separately, absolute
paths on each field, the sibling directory that shares the project's prefix,
and traversal that returns inside.

Verified end to end on a real tree, not only in unit tests: a manifest asking
to read `../secret.txt` and write `../pwned.txt` produces neither file, while
the legitimate entry still copies.

I introduced the wrapper when I extracted this block for a complexity finding
earlier in the stack, and did not look at what it was joining.
2026-08-04 03:06:31 -07:00
ukimsanov 0dcd06ff62 refactor(scripts): extract mirrorRegistryTargets from prepareProjectDir
Inlining the registry-target mirroring pushed prepareProjectDir to cyclomatic
24, the one complexity finding fallow attributes to this branch — the other
eight are inherited and out of scope here.

Same behaviour, expressed as a filter chain in its own named function, so
prepareProjectDir returns to what it was and the new helper stays flat.
2026-08-04 03:06:31 -07:00
ukimsanov 79c4056a3f docs: make Catalog visual and reproducible 2026-08-04 03:06:31 -07:00
ukimsanov a99ad218f7 docs: fix the types page subtitle to match its softened claim
Rames' residual on #2974. I softened the body sentence last round and left the
frontmatter saying "All exported types from @hyperframes/sdk". Mintlify renders
description as both the page subtitle and the meta description, so the page went
on promising complete coverage in the two places a reader meets first — and the
one a search engine quotes.

Pre-existing on main, and he did not hold the stamp for it, but leaving it makes
the body fix cosmetic.
2026-08-04 02:45:21 -07:00
ukimsanov 7a91b93dd6 docs: correct four developer-reference claims the source contradicts
Miguel's three P2s and Rames' one finding on #2974, all verified in source
before changing anything.

**`render --json` is not a progress stream.** It prints exactly one
`batch-complete` document at the end (`batchRender.ts:408-418`), asserted as a
single `console.log` in `batchRender.test.ts`. Described as a final result now.

**The iframe drag example never captured the pointer.** `event.target` comes
from `iframe.contentDocument`, so `instanceof Element` against this window's
constructor is always false for a cross-realm node and `setPointerCapture()`
never ran — a pointer leaving the frame then loses `pointerup` and drag state
sticks. Structural feature detection instead, with the reason in a comment so it
does not get "simplified" back.

**The preview adapter example did not compile under strict TypeScript.** `comp`
was captured by the callback before definite assignment (TS2454). Optional, with
`comp?.dispatch(op)`.

**`ORIGIN_APPLY_PATCHES` was imported in a fence that did not use it and used in
fences that did not import it.** Imports do not cross fences, so both examples
were wrong in opposite directions. Rames found the pair in
`open-composition.mdx`; the same shape is in `composition.mdx:630`, which he did
not name. All three fences are self-contained now.

**And `types.mdx` claimed coverage it does not have.** It promised "every type
exported from `@hyperframes/sdk`" while omitting 13 of 42. Eleven are documented
on sibling pages, so the sentence now points at those instead of overclaiming.
The two with no home anywhere — `CompositionVariableType` and
`VariableUsageScan`, both re-exported from the barrel — have entries. The second
is worth having written down: `scanIncomplete` means `usedIds` is a lower bound,
so an id missing from it is unknown rather than unused.
2026-08-04 02:45:21 -07:00
ukimsanov bebaf679d9 docs: rebuild developer and rendering reference 2026-08-04 02:45:21 -07:00
ukimsanov f720eb75be docs: lead the Quickstart with a paste-to-agent block, cut the Examples link wall
Two things a non-technical reader hits that the pages did not help with.

**Quickstart made you do it by hand before you could ask.** Install through an
interactive picker, choose the right group, restart the agent, then type a
prompt — four manual terminal steps before anything happens. The copy-to-agent
affordance existed but sat at the bottom of the page, after the step it would
have replaced, and it copied only the prompt.

It leads now, with the whole thing in one visible block: install, make, open the
preview. It uses `hyperframes skills update` rather than the interactive
`skills add`, which is the command the README already says agents should run —
non-interactive, exactly the core set. The manual steps stay below for anyone
who wants to see them.

Plain code fence rather than the AgentAction component, deliberately: that
component renders a Copy button and never shows the request, so a reader copies
something they cannot read. Mintlify fences already carry a copy button and show
the text.

**Examples had four GitHub cards where one belongs.** Brief, Source, Revision,
Checks — two of them pointing into a gate-output report. That is showing
homework, not helping someone who came to see finished work. The render stays,
with one link into the folder.
2026-08-04 02:16:22 -07:00
ukimsanov b36f69a3c0 docs: drop the live embed from the introduction
Removed on request. The demo let a reader change a headline and an accent on a
ten-second composition, which undersold the thing the page is arguing for — the
Showcase wall above it does more for that in less space.

The component and its build apparatus go with it in #2977; nothing else on the
page referenced them.
2026-08-04 02:16:22 -07:00
ukimsanov 3d8db3f44e docs: drop the Source group to two columns
Rames' second item on #2976, fixed here because this is the line that owns it.
It was the only cols={3} in docs/, and all three cards carry body text — the
case docs/AGENTS.md names as hyphenating titles mid-word at this content width.

The rules file is #2976's deliverable, so it should not ship with the tree
beneath it holding the one counter-example.
2026-08-04 02:16:22 -07:00
ukimsanov ac9486980d docs: make the Reference Project links read as links
The four destinations were a single bordered four-column strip, which reads as
a tab bar — a control that switches the panel below it — when every cell is a
link to GitHub. Replaced with a CardGroup, which is what AGENTS.md prescribes
for choosing between destinations, at the two columns it also prescribes.
2026-08-04 02:16:22 -07:00
ukimsanov e60bef3f57 docs: rewrite the guides and landing pages
Rewrites the pages that survive the restructure so they lead with what a reader
can accomplish, and points them at the sections added in the previous commit.
Page set and navigation are unchanged here; only content moves.

Keeps the skill count in README. CLAUDE.md's catalog-maintenance rule requires
the count to live in README and CLAUDE.md, and both now agree with the 19
directories under skills/.
2026-08-04 02:16:22 -07:00
ukimsanov 1a82615f64 fix(docs): drop the nav entry that redirects away from itself
My rebase of this branch onto the squashed main resolved a docs.json conflict by
keeping both sides. One of those sides was a deliberate deletion: this PR removed
"contributing/studio-manual-dom-editing" from the sidebar precisely because it
also became a redirect source pointing at /studio/canvas. Re-inserting it left a
Contributing entry that bounces the reader somewhere else when clicked.

Nav collision count is back to 0. The redirect and the page file are untouched;
contributing/canary-rollouts stays, since that one genuinely arrived from main.
2026-08-04 01:47:10 -07:00
ukimsanov 8b98b41eed fix(docs): keep the changelog and weekly archive reachable
Rames' review on #2978. Two pages left the sidebar without a redirect and
without being deleted, so they survived only as direct URLs: `docs/changelog.mdx`
and `docs/weekly-updates.mdx`.

Not deliberate, and the stack says so — #2979 upgrades `weekly-updates.mdx`,
importing DocsVideo and converting four raw <video> tags. You do not invest in
a page you meant to retire, and it carries `rss: true`, so it is a subscribable
feed. `product-updates.mdx`, which this stack adds to both the nav and the
footer, links to `/changelog` three times and `/weekly-updates` once. One of
those is advice to read the release archive before upgrading a production
workflow.

Both are back in the Explore group next to Product updates, which is where a
reader looking for "what changed" would go.

Worth naming why the verification missed it: the checker walks navigation → file,
which is why it correctly reported zero dangling entries. The file → navigation
direction — a page that exists, is not in the sidebar, and has no redirect — was
never checked, and that is exactly where these two sat.

`--check-redirects` on the existing `mint broken-links` step closes the adjacent
gap: it resolves every redirect destination, so a future restructure cannot
leave a redirect pointing at a page it removed. It does not catch the orphan
case above.

Also retargets `/guides/pipeline`. It pointed at `/concepts`, which explains how
a project is put together; the retired page was a seven-step process. `/workflows`
is the closer intent. The old step 3, "Strategy & Messaging", has no successor
anywhere in the docs — worth deciding deliberately rather than routing around.
2026-08-04 00:39:31 -07:00
ukimsanov 52f3eb5b27 docs: retire superseded pages behind redirects
Removes the thirteen pages the rebuilt sections replaced, and claims a redirect
for each one in the same commit so no published URL starts returning a 404.

Each destination is the page that now answers the question the retired page was
answering — the Studio pages for editor topics, the workflow guides for the
per-source how-tos, Troubleshooting for the mistakes list.
2026-08-04 00:39:31 -07:00
ukimsanov 6fe5b4f82a docs: add the Studio section, workflow guides, and section hubs
Adds the pages the rebuilt navigation is organised around, and moves the sidebar
onto that structure. Every entry points at a file that exists after this commit;
nothing is rewritten or removed yet, and no redirect is claimed yet.

- Studio (13 pages) — the editor had no section of its own
- One guide per creation workflow, matching the shipped agent skills
- Section hubs: Concepts, Catalog, Developers, Deploy, Workflows, Help,
  Go further, Product updates, 30 Days of HyperFrames

Pages a later commit retires drop out of the sidebar here but still exist and
still resolve, so the site stays navigable at this commit.

Incidentally fixes a link to /concepts from the Lambda migration guide that is
broken on main today — the destination did not exist.
2026-08-04 00:39:31 -07:00
ukimsanov c62bd4c454 fix(studio): harden preview recovery 2026-07-27 23:55:06 -07:00
ukimsanov 12452a1156 docs: clarify media treatment contracts 2026-07-27 23:54:10 -07:00
ukimsanov 3af9a7df2d refactor(studio): reuse player probe errors 2026-07-27 22:35:09 -07:00
ukimsanov 0faeb42f86 test(studio): mock composition thumbnails in smoke 2026-07-27 22:25:27 -07:00
ukimsanov 18b9acac12 fix(studio): improve preview loading reliability 2026-07-27 22:14:23 -07:00
ukimsanov 1828627731 docs: refine media treatment guides 2026-07-27 18:27:20 -07:00
ukimsanov 1a2c30a799 docs: document professional grading and media treatments 2026-07-27 18:27:20 -07:00
ukimsanov 794930a075 fix(studio): align professional grading contracts 2026-07-26 01:47:06 -07:00
ukimsanov c1fcf7534f fix(studio): clear stale color scopes 2026-07-26 01:29:32 -07:00
ukimsanov 20ef48abcb feat(studio): add professional grading controls 2026-07-26 01:29:32 -07:00
ukimsanov 20f4bde46a test(cli): harden media treatment parity 2026-07-26 01:29:05 -07:00
ukimsanov 9b504045f7 fix(core): register media analyzer subpath 2026-07-26 01:25:42 -07:00
ukimsanov c1dde28980 fix(cli): address media treatment review findings 2026-07-26 01:25:42 -07:00
ukimsanov 6d5961b802 feat(cli): expose agent-native color grading 2026-07-26 01:25:42 -07:00
ukimsanov e446de6023 fix(core): align secondary mask contracts 2026-07-26 01:25:16 -07:00
ukimsanov 9a515858c9 fix(core): preserve curve compiler subpath exports 2026-07-25 23:38:32 -07:00
ukimsanov 1ffef9a262 fix(core): address grading review findings 2026-07-25 23:16:50 -07:00
ukimsanov f99fc4e568 feat(core): add professional color grading controls 2026-07-25 22:15:30 -07:00
ukimsanov bf47416e14 refactor(studio): simplify preview workspace layout 2026-07-24 18:42:49 -07:00
ukimsanov d5c7d3ee16 fix(studio): preserve composed media treatments 2026-07-24 18:42:37 -07:00
ukimsanov 39c2341c4d feat(studio): add media treatment inspector 2026-07-24 18:42:37 -07:00
ukimsanov b0d3164ddb feat(registry): add media treatment overlays 2026-07-24 18:42:23 -07:00
ukimsanov 4582881d00 feat(cli): add agent-first media treatment tools 2026-07-24 18:42:07 -07:00
ukimsanov 944640c328 feat(runtime): render media treatments deterministically 2026-07-24 11:17:22 -07:00
ukimsanov 70213c5a85 feat(core): define media treatment capabilities 2026-07-24 08:56:24 -07:00
ukimsanov 270179d94b fix(studio): harden flat keyframe retiming 2026-07-20 19:26:10 -07:00
ukimsanov f25a136927 fix(studio): retime flat tween keyframe diamonds 2026-07-20 18:56:14 -07:00
ukimsanov c15819fe88 feat(studio-server): files route extensions
What: the studio-server files route at its final NLE-stack form, with its
test suite (25 tests).

Why: standalone package seam — the server-side dependency of the studio
asset workflow, reviewable in isolation.

How: additive route behavior; existing route consumers unchanged.

Test plan: bunx vitest run src/routes/files.test.ts in packages/studio-server;
tsc --noEmit in packages/studio-server; fallow audit clean.
2026-07-11 18:00:53 -07:00
ukimsanov 8f022d06ec fix(studio): remove selection overlay fill 2026-07-06 21:02:36 -07:00
ukimsanov b38c054baf docs: document Studio Color Grading 2026-07-06 15:29:37 -07:00
ukimsanov 04f96da169 fix(studio): share safe media path cleanup 2026-07-06 14:35:41 -07:00
ukimsanov e0090efbf6 fix(studio): avoid regex in media asset path cleanup 2026-07-06 14:15:30 -07:00
ukimsanov 30a944de34 fix(studio): split color grading inspector files 2026-07-06 13:56:37 -07:00
ukimsanov 139cf568b4 feat(studio): add color grading inspector 2026-07-06 13:40:20 -07:00
ukimsanov 413ee07da5 fix(studio-server): share background removal job runner 2026-07-06 13:27:45 -07:00
ukimsanov a3bf7eb995 feat(studio-server): add media processing routes 2026-07-06 13:25:04 -07:00
ukimsanov 644ae384a5 fix(runtime): satisfy color grading audit 2026-07-06 12:49:34 -07:00
ukimsanov 870964b0cf feat(runtime): render media color grading shaders 2026-07-06 12:44:43 -07:00