Skip to content

docs: add the shared page components and the Reference Project - #2977

Merged
ukimsanov merged 11 commits into
mainfrom
docs/reference-project
Aug 4, 2026
Merged

docs: add the shared page components and the Reference Project#2977
ukimsanov merged 11 commits into
mainfrom
docs/reference-project

Conversation

@ukimsanov

Copy link
Copy Markdown
Collaborator

First of three commits that replace #2973. Everything here is additive — nothing imports these files yet, navigation is untouched, and no published page changes.

The page components (docs/snippets/, docs/custom.css)

Six React snippets the rebuilt pages compose against: DocsVideo / ShowcaseWall, LiveReferenceProject (which embeds a composition through <hyperframes-player>), WorkflowChooser, AgentAction, and two grid snippets.

The scrub indicator is a timecode bubble, not a thumbnail. Driving a preview frame meant mounting a second <video> with the same src, so every page carrying a film downloaded the whole file twice.

The Reference Project (examples/docs-reference-project/)

One real 10-second project the docs can point at instead of describing a hypothetical one — a live capture of example.com, synthesised narration, caption timings measured from that narration.

Verified independently, not quoted from a report:

hyperframes lint    0 errors, 0 warnings
hyperframes check   passed — 0 errors, 0 warnings, 2 info
contrast            28/28 text checks pass WCAG AA

Only the two WAV masters exceed the 500 KB non-LFS limit, so only those go through LFS. The MP3 stings (11 KB, 18 KB) and the capture PNG (21 KB) stay plain, so the example still works after a clone without git lfs pull.

bun run docs:bundle-reference regenerates the single-file embed the Introduction page loads from the CDN.

Review notes

  • 0 dangling navigation entries, 0 broken internal links.
  • The one broken link the checker reports at this commit — /concepts from the Lambda migration guide — already exists on main; the next PR fixes it.

Adds the six React snippets the rebuilt documentation pages compose against,
plus the styles they need. Nothing imports them yet, so this lands with no
user-visible change and no navigation churn.

- DocsVideo / ShowcaseWall — the film player and the Showcase grid
- LiveReferenceProject — embeds the Reference Project via <hyperframes-player>
- WorkflowChooser, AgentAction, and the two grid snippets

The scrub indicator is a timecode bubble rather than a thumbnail. Mounting a
second <video> with the same src to drive a preview frame made every page
carrying a film download the whole file twice, which is not worth a thumbnail.
One real 10-second project the documentation can point at instead of describing
a hypothetical one: a live capture of example.com, synthesised narration, and
caption timings measured from that narration. It passes its own gates —
`hyperframes lint` clean, `hyperframes check` passed, 28/28 text checks WCAG AA.

No page imports it yet, so this lands without touching navigation.

Only the two WAV masters exceed the repository's 500 KB non-LFS limit, so only
those go through LFS. The MP3 stings and the capture PNG stay plain, which keeps
the example usable after a clone without `git lfs pull`.

`bun run docs:bundle-reference` regenerates the single-file embed the
Introduction page loads from the CDN.
Copilot AI review requested due to automatic review settings August 3, 2026 23:48
@mintlify

mintlify Bot commented Aug 3, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
hyperframes 🟢 Ready View Preview Aug 3, 2026, 11:50 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds new additive documentation building blocks (React snippet components + expanded Mintlify CSS) and introduces a real “docs reference project” under examples/ along with a bundling script to produce a single-file HTML embed for docs/CDN usage.

Changes:

  • Add a new scripts/docs/bundle-live-reference.mjs script and a root docs:bundle-reference package script to generate a bundled embed HTML.
  • Introduce examples/docs-reference-project/ as a complete, runnable 10s reference composition with narration, captions, assets, and documentation.
  • Add several new Mintlify React snippets and expand docs/custom.css to style these components and adjust navigation/typography behavior.

Reviewed changes

Copilot reviewed 24 out of 28 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
scripts/docs/bundle-live-reference.mjs New bundling script to emit a single-file HTML embed with an injected <base> tag.
package.json Adds docs:bundle-reference script to run the bundler against the reference project.
examples/docs-reference-project/transcript.json Word timing data used to drive caption cues.
examples/docs-reference-project/STORYBOARD.md Storyboard / direction for the reference project.
examples/docs-reference-project/SCRIPT.md Narration script and regeneration steps.
examples/docs-reference-project/README.md End-to-end reference documentation for running, editing, and rebuilding the embed.
examples/docs-reference-project/package.json Example project scripts pinned to a specific hyperframes version.
examples/docs-reference-project/meta.json Example project metadata (id/name/createdAt).
examples/docs-reference-project/index.html Main reference composition (HTML + GSAP timeline + variable override message handler).
examples/docs-reference-project/hyperframes.json Hyperframes project configuration (paths/media/authoringSkill).
examples/docs-reference-project/frame.md Visual design spec and constraints for the reference project.
examples/docs-reference-project/compositions/captions.html Caption overlay sub-composition with GSAP timing and self-lint.
examples/docs-reference-project/BRIEF.md Confirmed workflow brief and factual source constraints.
examples/docs-reference-project/assets/narration.wav LFS-tracked narration WAV asset pointer.
examples/docs-reference-project/assets/bgm.wav LFS-tracked BGM WAV asset pointer.
examples/docs-reference-project/.gitignore Ignores local outputs/artifacts for the example project.
docs/snippets/workflow-chooser.jsx New workflow chooser snippet (routes + reduced-motion behavior).
docs/snippets/quickstart-continuation-grid.jsx New quickstart continuation grid snippet.
docs/snippets/live-reference-project.jsx New live-embed snippet that loads <hyperframes-player> and posts variable overrides.
docs/snippets/docs-video.jsx New custom docs video component + showcase wall component.
docs/snippets/agent-action.jsx New “copy request” snippet for agent prompts.
docs/snippets/advanced-path-grid.jsx New advanced path grid snippet (reduced-motion behavior).
docs/custom.css Large update: new styling for snippets, nav/layout tweaks, and typography adjustments.
.gitignore Unignores examples/docs-reference-project/** so it can be committed despite examples/* ignore.
.gitattributes Routes reference-project .wav files through Git LFS.
Suppressed comments (1)

docs/snippets/docs-video.jsx:380

  • ShowcaseWall also uses useState/useEffect without importing hooks. If the snippet runtime only exposes React, this will throw at runtime.
export const ShowcaseWall = () => {
  const CDN = "https://static.heygen.ai/hyperframes-oss/docs/images/showcase";

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +8 to +12
}) => {
const videoRef = useRef(null);
const playerRef = useRef(null);
const hideTimerRef = useRef(null);
const progressFrameRef = useRef(null);
Comment on lines +8 to +12
export const WorkflowChooser = () => {
// Keep data inside the component. Mintlify compiles only the named export and
// drops module-level constants from snippet files.
const CDN = "https://static.heygen.ai/hyperframes-oss/docs/images/showcase";
const routes = [
Comment on lines +6 to +8
export const AdvancedPathGrid = () => {
const CDN = "https://static.heygen.ai/hyperframes-oss/docs/images/showcase";
const paths = [
Comment thread docs/snippets/agent-action.jsx Outdated
Comment on lines +1 to +3
export const AgentAction = ({ request }) => {
const [copied, setCopied] = useState(false);
const resetTimerRef = useRef(null);
Comment on lines +1 to +4
export const LiveReferenceProject = ({ src, poster }) => {
const playerRef = useRef(null);
const playerHostRef = useRef(null);
const [mounted, setMounted] = useState(false);
Comment on lines +50 to +54
useEffect(() => {
if (!playerReady || typeof window === "undefined") return undefined;

const controller = new AbortController();
let objectUrl;
Comment on lines +62 to +66
const assetBase = new URL("../", src).toString();
const preparedHtml = html.includes('<base href="../">')
? html.replace('<base href="../">', `<base href="${assetBase}">`)
: html.replace("<head>", `<head><base href="${assetBase}">`);
objectUrl = URL.createObjectURL(new Blob([preparedHtml], { type: "text/html" }));
The Examples page links this file twice — as "What changed after review" and
as "The real verification report" — in the section that makes the project's
brief, source, revision notes, and checks public end to end. It is a published
artifact, not leftover scaffolding.
The composition is fetched from the CDN and handed to the player as a blob:
URL, which inherits the docs origin, and <hyperframes-player> sandboxes its
iframe with allow-scripts + allow-same-origin. So the embedded composition runs
with script access to this origin.

That is a consequence of how the player works — it drives seeking through the
iframe's document, which a cross-origin frame does not expose — not something
this component can fix. Serving the CDN URL directly would isolate the frame
and break playback.

The guard is therefore the source, so the comment says so out loud: src must
stay a first-party path we publish, never user- or community-supplied HTML.

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at f3cb9cbd. 49 checks pass, 3 skipping, none failed. Copilot's pass is at 492952eb, two commits back, so part of it is already answered by the head commit.

Taking the review-as-code framing: no relitigating the custom player, nothing about formatting.

Copilot's most-repeated finding is wrong, and clearing it is worth more than anything else here

Copilot flagged missing hook imports on all five snippet files (docs-video.jsx:12, workflow-chooser.jsx:12, advanced-path-grid.jsx:8, agent-action.jsx:3, live-reference-project.jsx:4), predicting a ReferenceError at render, and cited TemplateCard.jsx as evidence that the runtime exposes only React.

Mintlify documents the opposite:

React hooks are pre-injected: useState, useEffect, useRef, useCallback, useMemo, useContext, and useReducer are available without importing them.

(customize/react-components#constraints)

TemplateCard.jsx is a weak reference point in any case. It uses export function, and the snippet docs say the function keyword is not supported and arrow syntax is required. Every file in this PR uses arrow syntax, so this diff sits closer to the documented contract than the file it was measured against. No action on those five comments.

That same constraints page does bite this PR, twice. Both are below.

The reduced-motion guard resolves one paint too late, in all three grids

docs-video.jsx:422-430, workflow-chooser.jsx:71-79 and advanced-path-grid.jsx:43-51 are the same shape:

const [reducedMotion, setReducedMotion] = useState(false);
useEffect(() => {
  const query = window.matchMedia("(prefers-reduced-motion: reduce)");
  setReducedMotion(query.matches);
  ...
}, []);

The initial value is a hardcoded false and the real one is only read after the effect flushes. So the first committed render always emits <video src={...} autoPlay loop> and only then pulls the attribute. On a reduce-motion machine that is 6 tiles from ShowcaseWall, 8 from WorkflowChooser and 4 from AdvancedPathGrid, each already in the DOM with a src and autoPlay set.

Two things make that worse than a one-frame flash:

  • autoPlay overrides preload="metadata" (docs-video.jsx:513). These are not metadata requests, they are the files.
  • Dropping the src attribute is not itself an abort. A media element keeps its current resource until the load algorithm is re-invoked, so removing the attribute with no following video.load() is not a reliable cancel.

CSS cannot cover this. custom.css:1332 and :1375 are right as far as they go, but they only disable transitions and one keyframe animation. Nothing in CSS stops <video autoplay>.

The fix is a lazy initializer, so the value is known on the first render rather than the second:

const [reducedMotion, setReducedMotion] = useState(
  () =>
    typeof window !== "undefined" &&
    window.matchMedia("(prefers-reduced-motion: reduce)").matches,
);

LiveReferenceProject never sends the initial variables

live-reference-project.jsx:113-131 depends on [playerReady, title, supportingLine, accent]. It reads playerRef.current, which is assigned by the effect above it at :105, and that effect depends on [compositionSrc, playerReady].

The two never line up on one commit:

  1. playerReady flips true. :113 runs, playerRef.current is still null, it bails at :116. The :97 effect also bails, compositionSrc is null.
  2. The fetch resolves and sets compositionSrc, necessarily a later commit. :97 re-runs and creates the player. :113 has no changed dependency, so it does not re-run.

Net: on the initial load, sendVariables() at :129 never fires and the ready listener at :128 is never attached. The first keystroke in the Headline field re-runs the effect and it works from then on, which is why this reads as fine in a click-through.

It is masked further because the three defaults at :9-13 are the same strings the composition already renders, so the untouched initial state looks correct. That coincidence is the only thing holding it up. Change a default, or let the composition's baked-in copy drift, and the embed loads showing the wrong text with no error anywhere.

Adding compositionSrc to the dep array at :131 is the whole fix.

The object URL can outlive its revoke, and the fix pattern is already in the file

:50-95. objectUrl is declared at :54 and assigned at :84, inside the async continuation. The cleanup at :91-94 closes over it.

If the component unmounts after response.text() resolves but before :84 runs, controller.abort() no longer stops the chain. Cleanup runs with objectUrl still undefined, then the blob is created a tick later and nothing ever revokes it.

Narrow, but the effect directly above already carries the guard for exactly this, at :18-27:

let cancelled = false;

Same treatment here: check the flag before createObjectURL, and revoke immediately if it is already set.

postMessage targets "*", which undercuts the isolation comment

The ISOLATION CONTRACT block at :62-79 is the best thing in this diff. It names why the frame is deliberately same-origin and tells the next person the guard is that src stays first-party. It also already answers Copilot's :84 comment, which predates f3cb9cbd.

:124 then posts to "*". By the comment's own reasoning the blob inherits the docs origin, so window.location.origin is the correct target and works today. The wildcard costs nothing right now, since the payload is three display strings. What it costs is later: the day someone ignores the comment and points src at third-party HTML, the frame is handed the message without complaint. Passing the origin turns a prose guard into an enforced one, which is what that comment is asking for.

Mintlify warns against the inline styles, and half this diff already agrees

From the same constraints page:

Avoid using the style prop on HTML elements. It can cause a layout shift on page load. Use Tailwind CSS classes or a custom CSS file instead.

The diff is split down the middle on it:

file style={{ className=
agent-action.jsx 0 1
workflow-chooser.jsx 0 4
live-reference-project.jsx 2 8
docs-video.jsx 13 15
advanced-path-grid.jsx 7 0
quickstart-continuation-grid.jsx 6 0

DocsVideo is fully classed and ShowcaseWall, in the same file, is fully inline. This PR also adds 1009 lines to custom.css, so the destination already exists. Not a blocker and not a formatting point. It is the specific failure the vendor names, layout shift on load, on components that are mostly above-the-fold grids.

"Works after a clone without git lfs pull" covers the visuals, not the audio

.gitattributes routes examples/docs-reference-project/**/*.wav through LFS. At head, bgm.wav is 132 bytes and narration.wav is 131 bytes, both pointers. index.html:264 and :272 are exactly those two: the music bed and the voiceover.

So keeping the stings and the capture plain saves the SFX and the image, not the track. A fresh clone without git lfs pull gets a project whose captions, timed from narration.wav per README.md:40, play over silence. README.md:86-94 lists both WAVs in the Audio table and never mentions LFS.

The split itself is right and the body's numbers are exact (11,702 / 18,390 / 21,297 bytes, and File size check is green). This is one README line, not a change to the routing.

Three smaller ones

No terminal state if the player script never arrives. :30-43. loadFailed is set from the script element's error event, but only on the branch that creates the script (:31-38). Any later mount finds the existing tag via :30, skips the listener, and is left with customElements.whenDefined(...) at :41, which does not reject for a script that failed to load, it simply never settles. Same outcome for a CSP rule or content blocker that kills the request without firing error. Result is a permanent "Loading the composition…" with the failure copy at :179 unreachable. A timeout that flips loadFailed covers both paths. Different mechanism from Copilot's :54 point about loadFailed never being cleared; both are worth having.

Version pins are duplicated with nothing tying them to the workspace. :34 pins @hyperframes/player@0.7.90; examples/docs-reference-project/package.json pins hyperframes@0.7.90 four times. Both match packages/player and packages/cli at this commit, so nothing is wrong today. Releases here land on main directly, so this is the shape of thing that goes stale without anyone noticing.

scripts/docs/bundle-live-reference.mjs. :20 strips trailing whitespace across the whole document, and :15 bundles with runtime: "inline", so that regex runs over inlined script content too. Cosmetic on markup, lossy inside a template literal. And :19 assumes a literal <head>; if that anchor ever moves, the <base> is silently not inserted and the embed resolves against the wrong root with no error. Both are one-liners.

I did check the <base> coupling that looks fragile, and it is correct: the npm script's argument pair makes relative() return .., so baseHref is exactly the '<base href="../">' literal that live-reference-project.jsx:81 matches. Worth knowing it is load-bearing on those two arguments staying in step, since the fallback branch would insert a second <base> rather than fail.

Confirmed

  • Cleanup in the named lens is otherwise complete. hideTimerRef is cleared from both the mount effect (docs-video.jsx:154-156) and the [playing] effect (:170), so whichever owns it at unmount, it is covered. The rAF loop cancels at :181-184. fullscreenchange removes at :163. All three matchMedia listeners remove. agent-action.jsx:30-31 re-arms its reset timer safely and :37-40 clears it on unmount. The only leak in that class is the object-URL race above.
  • controls={!enhanced} at docs-video.jsx:214, driven by the mount effect at :149-157, is a real progressive-enhancement path rather than decoration: native controls until the component mounts.
  • The scrub claim in the body holds. There is no second <video>; :255-266 is a timecode bubble driven from pointer geometry.
  • I did not re-run hyperframes lint, check or the contrast pass. Taking those from VERIFICATION.md.

Verdict

Commenting rather than approving, on the two items that are defects rather than preferences: the reduced-motion initializer across the three grids, and the missing compositionSrc dependency. Neither breaks anything today because nothing imports these files yet, which is exactly what makes them cheap now and expensive to find once the pages land on them. Both are one line.

The rest is good work, and the isolation comment in particular is the kind of thing that prevents a future incident rather than documenting one. Happy to re-review as soon as those two are in.

Review by Rames Jusso

…s dep gap

Both defects from Rames Jusso's review on #2977. Neither is visible today
because nothing imports these files yet, which is what makes them cheap now.

**Reduced motion resolved one paint too late, in all three grids.**
`useState(false)` plus a `matchMedia` read in an effect meant the first
committed render always emitted `<video src autoPlay loop>`; a reduce-motion
visitor had 6 + 8 + 4 tiles already fetching before the attributes came off.
`autoPlay` also overrides `preload="metadata"`, so those were the files, not
metadata probes — and dropping `src` with no following `load()` is not a
reliable abort. A lazy initializer knows the answer on the first render.

**LiveReferenceProject never sent the initial variables.** The sending effect
read `playerRef.current`, assigned by the effect above it on the commit where
`compositionSrc` lands — a commit with nothing in the sending effect's dep
array. So it ran once against a null ref and never again. It looked correct
only because the three defaults match what the composition already renders.

Also from the same review:

- The object URL could outlive its revoke: once the body resolves, `abort()`
  no longer stops the chain, so the blob could be minted after cleanup ran with
  `objectUrl` still undefined. Same `cancelled` guard the effect above uses.
- `postMessage` targeted `"*"` while the isolation comment argues the frame is
  same-origin. Naming `window.location.origin` turns that prose guard into an
  enforced one.
- Nothing reached a terminal state when the player script never arrived:
  `whenDefined()` does not reject, and a later mount reuses the tag without its
  error listener. A CSP rule or content blocker never fires `error` at all.
  A deadline covers every path instead of sitting on "Loading…" forever.
- `loadFailed` was never cleared, so one transient failure stuck.
- The README claimed a clone works without `git lfs pull`. It does for the
  visuals; both WAVs are pointers and they are the bed and the voiceover, so
  the captions would play over silence. Says so now.
- The bundler stripped trailing whitespace document-wide while inlining the
  runtime, which reaches inside script template literals where those spaces are
  data. It also assumed a literal `<head>` and would silently ship an embed with
  no `<base>`. Strip removed, anchor asserted.

Copilot's five "missing hook imports" comments are wrong — Mintlify pre-injects
the hooks, and `TemplateCard.jsx`, cited as the counter-example, uses the
`export function` form the same page says is unsupported.
@ukimsanov

Copy link
Copy Markdown
Collaborator Author

Both defects fixed at 3cc42785, along with everything else in the review that was a defect rather than a preference. Thanks — this was a genuinely useful pass, and the framing you chose (taking the slices Magi didn't name) is why it found things a second lifecycle review wouldn't have.

The two blockers

Reduced motion, all three grids. Lazy initializer, exactly as you wrote it. Your point about autoPlay overriding preload="metadata" is the part that turned this from a cosmetic flash into a real fetch — I'd have under-rated it otherwise, and the note about src removal not being an abort is in the code comment now so the next person doesn't "simplify" it back.

compositionSrc missing from the dep array. Fixed. Your read of the commit ordering is exactly right, and so is the observation that the three defaults coincidentally matching the composition's baked-in copy is the only thing holding it up. The comment on the dep array now says that out loud.

Also fixed

  • Object-URL revoke race — cancelled guard, same shape as the effect above, checked before createObjectURL.
  • postMessage now targets window.location.origin. You're right that the wildcard costs nothing today and everything on the day someone ignores the comment.
  • No terminal state when the player script never arrives — this was the sharpest of the three "smaller ones". Added a 10s deadline, which covers the reused-tag path and the CSP/blocker path that never fires error at all.
  • Copilot's loadFailed-never-cleared point was valid and is now reset per load.
  • README: both WAVs are pointers and they're the bed and the voiceover, so "works after a clone" covered the visuals and not the audio. It says git lfs pull now, and why.
  • bundle-live-reference.mjs: the document-wide trailing-whitespace strip is gone — you're right that it reaches into inlined script template literals — and the <head> anchor now fails loudly instead of silently shipping an embed with no <base>. Your note that the <base href="../"> coupling is load-bearing on the npm script's two arguments staying in step is worth having on the record; that's the kind of thing that breaks quietly.

Not taking, with reasons

The inline-style split. You're right that Mintlify names it and right that custom.css is already the destination. I'm leaving it: it's a presentational refactor of ~26 style props across three files, it would land in the PR that reviewers are reading for lifecycle correctness, and advanced-path-grid and quickstart-continuation-grid are the two candidates I most expect to shrink or disappear on their own merits — quickstart-continuation-grid in particular renders three static cards with no interactivity, which docs/AGENTS.md (added in #2976, top of this stack) says should be a CardGroup. Doing the CSS migration first would be work thrown away.

Version pins. Agreed on the diagnosis, and agreed nothing is wrong today. There's no mechanism in this repo that ties a docs-snippet CDN pin to the workspace version, and inventing one belongs in its own change rather than here.

Copilot's five hook-import comments are dismissed on your reasoning. The TemplateCard.jsx observation is the good part — it's measured against a file that violates the same constraints page it's being cited from.

Ready for re-review.

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One remaining lifecycle/accessibility bug at exact head 3cc42785b:

The lazy initializer correctly prevents the first reduced-motion render from emitting autoplaying videos, but all three grids still handle a runtime prefers-reduced-motion change by only setting React props (src={undefined}, autoPlay={false}, loop={false}). Removing a media element's src attribute does not reliably abort its selected resource, and changing autoplay to false does not pause media that is already playing. A visitor who turns Reduce Motion on while the page is open can therefore keep all 6/8/4 loops playing and downloading—the exact state the matchMedia("change") listeners claim to support.

Please make the transition actively stop each tile (pause, remove src, call load()) or remount the media into a poster-only node when reduced motion becomes true, and add a regression for false→true preference changes. Keep the lazy initializer; it solves the separate first-paint fetch.

I rechecked the requested lifecycle axes: DocsVideo clears its timer/rAF/fullscreen listener; AgentAction clears its timer; LiveReferenceProject aborts fetch, guards the post-body race, revokes its object URL, removes the player/ready listener, and the compositionSrc dependency now correctly delivers initial variables. I found no other blocker in those paths.

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at 3cc42785. Both items I flagged are fixed, and so are all five of the smaller ones. Going through them since a couple were resolved differently than I suggested and the difference is an improvement.

Reduced motion. Lazy initializer in all three grids, with the reasoning kept in a comment. Correct.

I checked one thing before calling this complete, because it would have made the fix partial: if Mintlify server-rendered these, the delivered HTML would already carry <video src autoPlay loop> and the browser would start fetching before React ever ran, so no initializer could help. It does not. Mintlify's considerations list "visitors may see a flash before components render" and warn that "search engines may not fully index client-rendered dynamic content", which only makes sense for client rendering. So React's first render is genuinely the first markup, and the typeof window guard is defensive rather than load-bearing. Nothing fetches on a reduce-motion machine now.

The dep gap. compositionSrc added, with a comment explaining why it is not decoration. That comment is worth more than the fix, since the next person to run a dependency linter over this file will be told the opposite.

Object URL. The cancelled guard now matches the effect above it, and checking before createObjectURL rather than after is the right placement.

postMessage. Now window.location.origin. The contract comment and the code agree.

Script load. The 10s deadline covers all three paths I described, and the element name matches the whenDefined call, so the customElements.get check cannot silently never fire. The setLoadFailed(false) at the top of the fetch effect also clears Copilot's :54 point as a side effect.

Bundle script. Hard-failing on a missing <head> is better than what I suggested. A silent wrong-root embed becomes a build failure. Dropping the trailing-whitespace strip entirely is also the right call: it was only ever cosmetic on markup and lossy inside the inlined runtime.

README. Covers it, and naming the 500 KB limit explains why only those two files are pointers.

On the remaining lifecycle point

Miguel is right about the mechanism and the fix, and I want to size it so the cost is clear.

Toggling Reduce Motion on mid-session does leave the tile playing. Removing the src attribute does not re-invoke the resource selection algorithm, so the element keeps the resource it already has. That is the same reason the first-paint bug was worth fixing.

One correction to the impact, though. loop is a live IDL attribute, not a load-time one, so React setting video.loop = false takes effect immediately and the element stops at the end of the pass it is in rather than continuing to loop. The longest tile here is studio-check-render-loop-v2.mp4 at 5.6s and the shortest of the two I probed is 4.4s, so the worst case is a few seconds of residual motion, not indefinite looping.

That makes this a different class of problem from the one that was just fixed. Nothing is being fetched, since the bytes are already local by the time anyone can toggle. It is bounded, and it only affects someone who changes the preference with the page already open.

Still worth doing, and there is a second reason beyond the motion. Once the video ends with src removed, the element holds its last frame. poster only displays before playback begins, so it does not come back. A visitor who toggles Reduce Motion ends up looking at a freeze-frame of wherever each tile happened to stop, which is not what the same page looks like when loaded fresh with the preference already on. The remount-to-poster option handles both the motion and that inconsistency in one move, which is why I would lean that way over pause() plus load().

Verdict

Commenting rather than approving, since there is a changes-requested on this head that is not mine to clear and the call on the remaining item belongs to that review.

Everything I raised is genuinely resolved, and several of them are better than what I proposed. From my side this is good to go once the toggle case is settled.

Review by Rames Jusso

…sion

Miguel's changes-requested on #2977. He is right about the mechanism: dropping
`src` and `autoPlay` through React props neither pauses a playing element nor
aborts its selected resource, so a visitor who turned Reduce Motion on with the
page already open kept every tile running.

Measured in a browser rather than argued from the spec, same clip, same
sequence:

  playing                 paused=false  t=2.90  readyState=4  networkState=1
  React props only        paused=false  t=3.90  readyState=4  networkState=1
  + pause/removeAttr/load paused=true   t=0     readyState=0  networkState=0

The middle row is the bug: time still advancing, resource still held.

Rames' follow-up asked for a remount-to-poster instead, because a video that
ends with `src` removed holds its last frame and `poster` only paints before
playback begins. `load()` covers that too — it drops readyState to
HAVE_NOTHING, which is precisely the state that paints the poster. Confirmed
side by side on screen: the React-props-only tile sits on an arbitrary mid-clip
frame, the pause/load tile shows the poster again. So no remount is needed.

The guard cannot be shared as code — Mintlify compiles each snippet in
isolation and forbids one importing another — so it is copy-pasted into all
three grids. A duplicated invariant is the kind that rots, and a rendering test
would mean adding React to a repo that only carries it inside packages/studio,
plus mocking Mintlify's hook-injection contract with a mock that can stay green
while the page breaks. `scripts/check-docs-snippet-motion.mjs` asserts the
source instead, wired into `bun run lint`, with unit tests covering both edges.

That gate immediately found `docs/snippets/TemplateCard.jsx`: autoplays with no
reduced-motion handling at all. It is imported by zero pages, and it uses the
`export function` form Mintlify's constraints page says is unsupported, so it
would not work if it were. Deleted rather than fixed.
@ukimsanov

Copy link
Copy Markdown
Collaborator Author

Fixed at 49fb4047. @miguel-heygen @jrusso1020 — the two reviews disagreed on impact, so I measured it in a browser instead of arguing the spec.

Same clip, same sequence, readyState/networkState read at each step:

state paused currentTime readyState networkState
playing false 2.90 4 1
React props only (src and autoPlay removed, loop = false) false 3.90 4 1
+ pause() / removeAttribute("src") / load() true 0 0 0

The middle row is the bug, exactly as Miguel described it: time still advancing, resource still held. paused never flips and currentTime keeps climbing.

Rames' correction stands and matters. loop is live, so residual motion is bounded by the current pass rather than indefinite, and nothing is being fetched — the bytes are already local by the time anyone can toggle. That is a real difference in severity from the first-paint bug, and worth having on the record.

On remount-to-poster vs pause() + load(). You were right that this is two problems, not one — the motion and the freeze-frame — and right that poster only paints before playback begins. But load() handles the second one too: it drops readyState to HAVE_NOTHING, which is the state that paints the poster. Confirmed on screen side by side, not inferred: the React-props-only tile sits on an arbitrary mid-clip frame, the pause()/load() tile is showing the poster again. So a remount isn't needed, which keeps this three lines instead of a render-path change.

On the regression test. Wanted, and here's why it isn't a rendering test. The guard can't be shared as code — Mintlify compiles each snippet in isolation and forbids one importing another — so it is necessarily copy-pasted into all three grids. A rendering test would mean adding React to a repo that only carries it inside packages/studio, plus mocking Mintlify's hook-injection contract, and that mock can stay green while the real page breaks.

So the guard is asserted against the source: scripts/check-docs-snippet-motion.mjs, wired into bun run lint, with scripts/check-docs-snippet-motion.test.mjs covering both edges — the late matchMedia read, and the false→true transition with no active stop. It only applies to snippets that actually autoplay. I verified it fails by reintroducing the defect and watching it go red.

It found one immediately. docs/snippets/TemplateCard.jsx autoplays with no reduced-motion handling of any kind. It is imported by zero pages, and it uses the export function form Mintlify's constraints page says is unsupported — so it would not work if anything did import it. Deleted rather than fixed, which also removes the file Copilot was measuring the other five snippets against.

Rames — noted on the inline-style split and the version pins from your first pass; both still declined for the reasons in my earlier reply.

fallow flagged findMotionGuardViolations at CRAP 42 — a finding this branch
introduced, so it gets fixed rather than suppressed, same as the catalog
generator earlier in the stack.

The two conditions are now their own predicates behind a small requirements
table, which drops the branch count under the threshold and makes each rule
readable on its own line. Same output, same tests.

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at e1a03c63. The mid-session fix is the right one and the source gate is a defensible call, but the fix introduced a crash in ShowcaseWall and the gate cannot see it.

Blocker: the new effect is a conditional hook, and it breaks the tile click

docs-video.jsx:492 sits below the if (open) early return at :443-485.

Hook order in ShowcaseWall:

line hook
:421 useState (openId)
:427 useRef (wallRef)
:428 useState (reduced)
:434 useEffect (matchMedia listener)
:443 if (open) { return ... }
:492 useEffect (stop playback)

Five hooks on the default render, four once a tile is open. React requires the same call order every render, so the transition throws Rendered fewer hooks than expected. This may be caused by an accidental early return statement.

That transition is setOpenId(film.id), which is ShowcaseWall's primary interaction: clicking a tile to expand the full film. So this is not a corner case, it is the main path through the component.

Moving the effect above :443 is the whole fix. Nothing else has to change, because on the expanded render the wall <div> is unmounted and React has already nulled the ref, so the effect's own !wallRef.current bail at :493 covers that render correctly.

Worth naming why this landed in only one of the three: workflow-chooser.jsx (:83, :95, :104) and advanced-path-grid.jsx (:55, :67, :76) have no conditional return, so the same placement is fine there. ShowcaseWall is the one component with an early return and it received the same copy-paste position as the two without one. That is the duplication cost the script's own header warns about, showing up in the commit that added the script.

The gate asserts the guard exists, not that it runs

check-docs-snippet-motion.mjs passes docs-video.jsx at this head. All three tokens it looks for are present at :495-497; they are simply unreachable when a tile is open. String matching cannot see reachability, so this class of bug is outside what the script can ever check.

The same whole-file matching has a second gap that will bite sooner. autoplays, readsPreferenceLazily and stopsPlaybackActively all run against the entire file, but the invariant is per component. docs-video.jsx already holds two, and DocsVideo at :451 takes autoPlay from its caller. Add a third autoplaying grid to that file with no guard at all and the gate stays green, because ShowcaseWall's guard already satisfied every predicate. TemplateCard.jsx was caught only because it was a file with one component and no guard.

Also readsPreferenceLazily never ties its two halves together: any useState(() => anywhere plus the string prefers-reduced-motion anywhere passes, even if the lazy initializer belongs to unrelated state and the preference is still read in a mount effect. That is the original bug, passing the check written to prevent it.

The generic rule catches both, and it is nearly wired up already

Both hard bugs on this PR are what the React hooks lints are for. The repo is closer to having this than it looks: .oxlintrc.json already enables the react plugin, and docs/ is not in oxlint's ignorePatterns. Only .prettierignore excludes docs/, and that governs oxfmt, which is why formatting is not a finding here but linting still reaches these files.

I reproduced both bugs and ran oxlint against them rather than assuming:

docs/snippets/probe.jsx:10:3: error react-hooks(rules-of-hooks): React Hook "useEffect" is
  called conditionally. React Hooks must be called in the exact same order in every component
  render.

docs/snippets/probe2.jsx:16:33: error react-hooks(exhaustive-deps): React Hook useEffect has a
  missing dependency: 'compositionSrc'

The first is this blocker. The second is the live-reference-project.jsx dependency gap from round 1, which I found by reading. Both from one config change:

"plugins": ["react", "react-hooks", "typescript"],
"rules": {
  "react-hooks/rules-of-hooks": "error",
  "react-hooks/exhaustive-deps": "warn"
}

One caveat from testing, worth having before you try it: adding react-hooks to plugins on its own reported zero. With only categories.correctness: "error" set, neither rule is in the enabled set, so both have to be named explicitly. And exhaustive-deps repo-wide will almost certainly surface things in packages/studio, so an override scoped to docs/snippets/** is the low-risk way in.

I am not asking you to swap the script for the config in this PR. The script encodes the reduced-motion invariant specifically, which no generic rule knows about, and the header explaining why is genuinely useful. The point is that the two bugs that actually escaped review are both mechanically detectable today, and the bespoke gate caught neither.

Confirmed at this head

  • The mid-session fix is correct where it runs. pause(), then removeAttribute("src"), then load(), in that order, and load() taking readyState to HAVE_NOTHING is exactly what brings the poster back, so your side-by-side matches the spec. Your measurement also settles it better than either review argued it.
  • The true back to false direction works: React re-adding src does invoke the load algorithm, unlike removing it.
  • TemplateCard.jsx is safe to delete. Code search finds no reference to it anywhere in the repo outside the file itself, and it was off-contract on two counts.
  • The bundler and README changes from round 1 still read correctly here.
  • Declines on the inline styles and the version pins are fine by me. I raised both as non-blockers and they are your call.
  • CI: 57 pass, 1 regression shard running, 0 failed. Note Mintlify Deployment is skipped, so nothing in the pipeline renders these snippets. That is why a component that throws on click lands fully green, and it is the strongest argument for the lint rule.

Verdict

Requesting changes on the conditional hook at docs-video.jsx:492. It is a one-line move and everything else at this head is in good shape.

Review by Rames Jusso

@ukimsanov

Copy link
Copy Markdown
Collaborator Author

Fixed at 96f48c26. That was a real crash on the component's main interaction, and I put it there — thank you for catching it before anything imported these files.

The conditional hook. Effect moved above the if (open) return. Your read is exactly right, including why it landed in one of three: workflow-chooser and advanced-path-grid have no early return, so the same paste position was fine there, and ShowcaseWall got the same copy. That is the duplication cost this script's own header warns about, showing up in the commit that added the script. Also right that nothing else has to change — on the expanded render React has already nulled the ref, so the effect's own bail covers it.

The lint rules — taking this, and it works exactly as you described. Including the caveat: adding react-hooks to plugins alone reported nothing, both rules had to be named. Scoped the override to docs/snippets/** as you suggested. Verified rather than assumed, in both directions: reintroducing the conditional hook produces react-hooks(rules-of-hooks) at the right line, and bunx oxlint . is clean repo-wide, so nothing lit up in packages/studio.

Your point that Mintlify Deployment is skipped, so nothing in the pipeline ever renders these snippets, is the part worth pinning. A component that throws on click went fully green through 59 checks. The lint rule is now the only thing in CI that would have said anything.

Both holes in the script are closed, with tests.

Whole-file vs per-component. Now splits by component. That immediately forced the distinction you named: a component that decides to autoplay versus one that forwards its caller's autoPlay. DocsVideo only ever plays because a reader clicked a tile, so it does not owe a preference check, and the predicate now says so explicitly instead of by accident.

The untied halves. You were right that this was the original bug passing the check written to prevent it. Rather than widen the regex I made it read the argument of each useState( call and require the media query inside that expression. There is a test for the exact shape you described — lazy initializer for unrelated state, preference still read in a mount effect — and it fails the check now.

On the larger point. You are right that the two bugs which actually escaped review were both mechanically detectable and the bespoke gate caught neither. I am keeping the script for the reduced-motion invariant specifically, since no generic rule knows about pause()/load(), but the honest ordering is: the lint rules are the load-bearing gate here and the script is the narrow supplement. Its header says that now.

fallow is at 0 introduced, 8/8 script tests pass, oxlint clean.

Rames' changes-requested on `e1a03c63`. The effect I added in the previous
commit landed below `if (open) return`, so `ShowcaseWall` called five hooks on
the grid render and four once a tile was open. That is a conditional hook:
clicking a tile — the component's primary interaction — threw "Rendered fewer
hooks than expected".

Worth naming why it landed in one of three. `workflow-chooser` and
`advanced-path-grid` have no early return, so the same paste position was fine
there. `ShowcaseWall` is the only one with a conditional return and it got the
same copy. That is the duplication cost this script's own header warns about,
showing up in the commit that added the script.

**The bespoke gate could not have caught it, and now the generic one does.**
`.oxlintrc.json` already loaded the `react` plugin and never excluded `docs/`
— only `.prettierignore` does, which is why formatting is not a finding here
but linting reaches these files. Naming the two hook rules in an override
scoped to `docs/snippets/**` reports this bug directly, and also reports the
`compositionSrc` dependency gap from round one that was found by reading.
Verified both ways: reintroducing the conditional hook produces
`react-hooks(rules-of-hooks)`, and `bunx oxlint .` is clean repo-wide, so
nothing lit up in `packages/studio`.

**Two holes in the script itself, both from the same review.**

It matched whole files while the invariant is per component, so a second
unguarded grid in `docs-video.jsx` would have ridden in on `ShowcaseWall`'s
guard. It now splits by component. That immediately surfaced the distinction
between a component that decides to autoplay and one that forwards its caller's
`autoPlay` prop — `DocsVideo` only ever plays because a reader clicked, so it
does not owe a preference check.

And `readsPreferenceLazily` never tied its halves: any lazy initializer plus
the media-query string anywhere in the file passed, which is the original bug
satisfying the check written to prevent it. The query now has to sit inside the
initializer's own expression.

Both holes have tests. fallow is clean at 0 introduced.
@ukimsanov
ukimsanov force-pushed the docs/reference-project branch from 96f48c2 to 2a75276 Compare August 4, 2026 03:02

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocker cleared. Verified at 2a752760.

The conditional hook is genuinely fixed, and I checked the shape rather than the commit message. All five hooks in ShowcaseWall (useState openId, useRef wallRef, useState reduced, the matchMedia effect, the stop effect) now sit above the if (open) early return at :457, and there are zero hook calls below it. So the count is five on both paths and a tile click can no longer render fewer hooks than the previous render. video.load() is still there at :451, which is what makes removing src an actual abort rather than just a DOM change.

Required CI is terminal-green at 2a752760: all eight required contexts pass, nothing red, and the only two non-green contexts (Studio: timeline viewport gate, regression-shards) are skipped rather than failed, and neither is required.

Both gate holes I raised are closed at the structural level rather than commented over, which I checked by reading the composition:

  • Per component now. splitComponents plus the flatMap in auditSnippets means each component is filtered and audited on its own body, so a second unguarded grid can no longer ride in on the first one's guard. Naming the component in the failure output is a good touch.
  • The halves are tied now. readsPreferenceLazily enumerates every useState( call, extracts that call's own argument text by paren depth, and requires the same argument to be both a thunk and to contain the query. The old version was two independent existence checks anywhere in the file, which is exactly why the original bug passed the check written to catch it.

Excluding the autoPlay={autoPlay} passthrough so DocsVideo is not asked to own a decision it only forwards is the right call, and I would keep it.

Two residual gaps, both non-blocking. I found these by running the gate's own functions rather than reading the regexes.

1. autoplays is now narrower than the version it replaced. The old /\bautoPlay(?:=\{|\s|\/?>)/ matched a bare attribute anywhere. The new one matches only autoPlay={ or autoPlay alone on its own line, so a single-line element evades it:

autoplays('<video autoPlay muted />')     -> false
autoplays('<video src={s} autoPlay/>')    -> false
autoplays('<video\n  autoPlay\n/>')       -> true
autoplays('<video autoPlay={autoPlay} />')-> false   (correct, this is the passthrough)

Nothing under docs/snippets is affected today, because the formatter puts these attributes on their own lines and ShowcaseWall is still audited correctly. The reason I would still fix it is the direction it fails: a component that autoplays misses is filtered out before any requirement runs, so the gate reports zero problems rather than a violation. Restoring the old breadth while keeping the passthrough exclusion is one clause, e.g. also testing /\bautoPlay(?=[\s/>])/ against withoutPassthrough.

2. A non-exported component still rides on the preceding component's guard. splitComponents anchors on ^export\s+(?:const|function), so anything not exported folds into the previous exported component's body:

export const Guarded = () => {
  const [r] = useState(() => matchMedia("(prefers-reduced-motion: reduce)").matches);
  return <video autoPlay={true} />;
};
const Sneaky = () => <video autoPlay={true} />;

splitComponents returns one component, Guarded, so Sneaky inherits its lazy-preference guard and passes. That is the same hole as the whole-file match, narrowed from file scope to non-export scope. Dropping export\s+ from the pattern closes it.

On the CI observation: agreed, and it is the part I would keep too. Mintlify Deployment being skipped means nothing in the pipeline renders these snippets, so a component that threw on click went green through the entire suite. That is a standing coverage gap in the docs surface rather than something this PR introduced.

One process point, because "stale" is doing work here that GitHub will not do for you. hyperframes does not dismiss reviews on push: ruleset 14211637 has dismiss_stale_reviews_on_push: false, and there is no classic protection on main. So neither change-request expires on its own no matter how many heads pass. Mine is cleared by this approval. The other one at 3cc42785 stays live and holds reviewDecision at CHANGES_REQUESTED until that same account re-reviews or dismisses it, which it has said it will do at this head. What does dismiss reviews in this repo is a base-branch change, not a push.

Review by Rames Jusso

Both from Rames' approval pass on #2977, and both found by running these
functions rather than reading them. Both fail the same quiet way: a component
`autoplays` misses is filtered out before any requirement runs, so the gate
reports zero problems instead of a violation.

`autoplays` had become narrower than the version it replaced. Excluding the
`autoPlay={autoPlay}` passthrough was right, but the replacement only matched
`autoPlay={` or `autoPlay` alone on a line, so `<video autoPlay muted />` on one
line slipped through. Restored the old breadth. Two things are stripped first
rather than one — the passthrough, and the prop's own default in the signature,
which is a declaration and not a use. Without the second strip, `DocsVideo` is
asked to own a decision it only forwards.

`splitComponents` anchored on `^export`, so anything not exported folded into
the previous exported component and inherited its guard. Same hole as the
whole-file match, narrowed from file scope to non-export scope. The anchor no
longer requires `export`.

Ten tests now, including his exact examples for both.
@ukimsanov

Copy link
Copy Markdown
Collaborator Author

Thanks for the approval — and both residual gaps are now closed at 7030efab, because your point about the failure direction is what makes them worth the extra push rather than a follow-up: a component autoplays misses is filtered out before any requirement runs, so the gate reports zero problems instead of a violation. A gate that stays silent is worse than no gate, and this one is new in this PR, so shipping it with known holes would be shipping false assurance. The PR was blocked on Miguel's stale request anyway, so the push cost no merge time.

autoplays breadth. Restored, and your test table now lives in the test file. One thing your one-clause suggestion would still have hit: \bautoPlay(?=[\s/>]) also matches autoPlay = false, in DocsVideo's own signature, which would have put it straight back to owning a decision it only forwards. So there are two strips before the test rather than one — the passthrough, and the prop's default in the signature, which is a declaration and not a use. [\s/>=] in the lookahead so autoPlay={!reduced} still counts.

Non-exported components. export dropped from the anchor. Your Sneaky example is a test case verbatim and now reports two violations instead of inheriting Guarded's.

Ten tests, fallow at 0 introduced, oxlint clean.

On the standing coverage gap: agreed it predates this PR. Mintlify Deployment being skipped means nothing in CI renders these snippets at all, which is how a component that threw on click cleared 59 checks. Worth someone owning separately — it is a bigger hole than anything this stack introduced.

That is everything from your three passes on this PR. Genuinely useful reviewing: five real defects across the stack, three of them mine, and the two you found by running the gate rather than reading it were the ones I would never have caught myself.

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved at exact head 7030efab.

The blocker from my prior review is closed. All three autoplay grids now resolve prefers-reduced-motion on the first render and, on a live false→true preference change, actively pause(), remove src, and call load(). In ShowcaseWall, all five hooks execute before the if (open) return, so opening a tile preserves hook order. The latest gate fixes also cover the two silent matcher gaps from the approval pass: bare/single-line autoPlay attributes and non-exported components are now detected, with mutation-catching tests.

I independently rechecked the other load-bearing lifecycle paths:

  • LiveReferenceProject creates the player before its variable effect runs, includes compositionSrc in the dependencies, and keeps the ready listener, so the initial variables are delivered.
  • Fetch cancellation guards the post-body race; object URLs are revoked; player/listener/script-deadline cleanup is present; postMessage targets the inherited docs origin.
  • DocsVideo clears its timer, rAF, and fullscreen listener; AgentAction clears its timer.
  • The bundler fails closed when <head> is absent and leaves inlined script whitespace intact.

Verification at this head: motion-gate tests 10/10, motion audit clean, focused oxlint clean, HyperFrames lint/check 0 errors and 0 warnings. CI currently has 48 passing contexts, 3 intentional skips, 0 failures; only Windows tests are still running.

Three non-blocking follow-ups:

  1. live-reference-project.jsx:66-68,184-203: changing src after a successful load does not clear the old compositionSrc. If the replacement fetch fails, the old player branch wins over loadFailed and silently shows stale content. Current use is a static first-party CDN URL, so this does not block this PR; clearing compositionSrc at the start of a new load would make the component correct for dynamic callers.
  2. .oxlintrc.json: react-hooks/exhaustive-deps is warning-only and the lint command does not deny warnings. The rule diagnoses the prior compositionSrc regression but would not fail CI. Scoped error severity would make it a real gate.
  3. The motion checker is intentionally lexical and can still be satisfied by a disconnected lazy media-query state plus unrelated pause/removeAttribute/load calls. Current snippets are correct; either tie those tokens to the state controlling autoplay or document the checker as a narrower safeguard so it is not treated as rendered proof.

Minor docs nit: .gitattributes still says the example renders without git lfs pull, while the corrected README accurately explains that narration and BGM are LFS pointers and render as silence without them.

The Introduction no longer carries the embed (removed in #2979), and nothing
else used any of this: the 200-line snippet, 26 CSS rules, the bundler that
built the single-file HTML for the CDN, its npm script, and the README section
explaining how to regenerate it.

The Reference Project itself stays — Examples, Developers, and Go further all
link to it as the worked example; only the interactive embed of it is gone.

This also retires the isolation contract I documented two rounds ago. That
comment existed because the embed handed CDN HTML to a same-origin blob; with
the embed gone there is no such surface to reason about, which is a better
outcome than a comment explaining why it was acceptable.

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at head 1cc9962e. My approval went stale on the subtractive push, so this is a fresh look at what the push actually removed. Approving.

The diff against my last approved head is +2 / −501 across five files, which matches the stated number exactly.

Deletion audit

The risk on a removal this size is a reference left pointing at something that no longer exists, so I grepped the whole tree — not just docs/ — for every removed artifact:

Removed Remaining references
LiveReferenceProject (the component) 0
docs/snippets/live-reference-project.jsx 0
hf-live-reference (26 CSS rules) 0
scripts/docs/bundle-live-reference.mjs 0
docs:bundle-reference (npm script) 0

Nothing imports, styles, or invokes any of it. The one consumer really was the only consumer.

I also checked the CSS actually survived the surgery rather than trusting that it parses: docs/custom.css is brace-balanced at 154 open / 154 close, depth never goes negative anywhere in the file, and it ends at zero. No rule got orphaned by the removal.

And the Reference Project itself is intact — examples/docs-reference-project/ is still in the tree with BRIEF.md, VERIFICATION.md, compositions and assets, and the three pages that link to it (examples.mdx, developers/index.mdx, go-further.mdx) all point at paths that still exist. Only the embed is gone.

One remnant

docs/custom.css:810:

}@media (max-width: 720px) {}

The rules inside that breakpoint went with the embed but the wrapper stayed, so there is now an empty at-rule sitting on the closing brace of the rule above it. Valid CSS and harmless at runtime — flagging it only because a change whose whole point is subtraction is the one place a leftover wrapper is worth deleting rather than keeping. Same for the }} at :820, which is correctly matched but reads as a typo.

CI

Seven of the eight required contexts are green at this head; regression has not reported yet. Zero failure conclusions among any check on the commit.

Approving on my own verification. This is a statement about 1cc9962e specifically — the repo requires an approval at or after the last push, so another push retires it.

Review by Rames Jusso

Its only consumer is gone. The Quickstart now shows the agent instruction in a
plain fence instead, because this component rendered a Copy button and never
displayed the request — a reader copied text they could not read, which is the
wrong shape for the one affordance a non-technical visitor depends on.

Mintlify fences already carry a copy button and show their contents.

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at head d6e4cf22. Approving.

The removal

AgentAction snippet plus its .hf-agent-action block. Whole-tree audit at this head: zero references to the component, the class prefix, or the import. The two agent-action hits are the phrase "agent-actionable" in the changelog and a release note — substring false positives, unrelated.

docs/custom.css still parses after the cut: 146 open / 146 close braces, depth never negative, ends at 0.

Two empty at-rules now

764: }@media (max-width: 720px) {}
766: @media (max-width: 520px) {}

Line 764 is the remnant I flagged last round, unchanged. Line 766 is new — this deletion pass created it the same way, by cutting the rule bodies and leaving the at-rule wrapper.

Both are valid CSS and emit nothing, so there is no rendering consequence. Raising it only because it is now a repeated signature rather than a one-off: the deletion step removes rules but does not remove the wrapper it just emptied, so each of these passes leaves another one behind. Worth a one-line cleanup while the file is already open.

CI

This is the only PR in the stack whose current head has actually been tested — 8 of 8 required contexts green at d6e4cf22, full matrix. The other five have had no workflow run at all; details in their own reviews.

Approving on my own verification, as a statement about d6e4cf22 specifically — the repo requires an approval at or after the last push, so another push retires it.

Review by Rames Jusso

@ukimsanov
ukimsanov merged commit edfe66a into main Aug 4, 2026
60 checks passed
@ukimsanov
ukimsanov deleted the docs/reference-project branch August 4, 2026 07:37
ukimsanov added a commit that referenced this pull request Aug 4, 2026
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.
ukimsanov added a commit that referenced this pull request Aug 4, 2026
The "One project, open end to end" section on Examples was four cards linking into
a GitHub folder. A reader on the docs site does not want to leave for a repo tree
to read a BRIEF.md. The section is gone, and so is the examples/docs-reference-project
folder it pointed at.

Also removed the prose references that leaned on it: the Reference Project
paragraph on Go further, the two GitHub-inspect links on Developers, and the
mention on the Studio landing. Each was reworded to talk about "a project"
generally rather than that specific folder.

Examples is now purely the nineteen finished films plus Start from a template.
The changelog entry recording #2977 is left as history. 0 broken links.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants