At this column width the breakout rendered 968px and dominated the page instead
of supporting it. The film is back inside a Frame at the column width. The part
worth keeping was the motion, not the size: it still autoplays muted on a loop
rather than sitting behind a play button, so the page is already moving when it
loads. Removed the now-unused wrapper CSS rather than leaving a dead rule.
The wrapper measured 609px tall against a 545px film - the theme's prose styles
apply a 2em margin to media, which rendered as a 64px black band above and below
it. Zeroed the margin inside the hero wrapper.
The film now reaches into the shell padding so it reads edge to edge, and it
autoplays muted on a loop instead of waiting behind a play button. Motion is
pre-attentive - the eye goes to it before any text is read - so a page that is
already moving answers 'what is this?' faster than a poster the reader has to
decide to click. Controls stay available for scrubbing and for unmuting once a
film with a real audio track is hosted.
Not implemented as 100vw: this column is not centred in the viewport because the
sidebar is a flex sibling, so the usual margin-left: calc(50% - 50vw) breakout
would sit off-centre. Noted in the CSS.
Checked it on the live preview instead of assuming: a whole guide page renders
about three <p> elements, because the content is lists, tables, cards, and code.
The selector matched one of them — the page description in the header — so the
rule capped nothing while reading as though it were load-bearing. Removed it and
corrected the width note in AGENTS.md, which claimed prose was held near 80
characters. The column measures 888px at 1600 and 1048px at 1920.
The previous attempt raised max-width on the article shell, which did nothing
visible: the shell was never hitting that cap. Measuring the chain showed the
left sidebar sits inside #body-content, whose max-w-8xl (1472px) was the binding
limit — 1432 inner minus a 287px sidebar minus 128px of shell padding minus 352px
of contents rail and gap lands on exactly the 664px column that was rendering.
Raising #body-content, dropping the shell cap, trimming the left padding, and
holding the contents rail to the 16rem it actually uses gives a measured column
of 728px at 1440, 888px at 1600, and 1048px at 1920 — the last of which was
previously stuck at 664 no matter how wide the display. No horizontal overflow at
any of those widths. Setting a width on #content-area is pointless, as it is a
flex item with grow that just fills the remainder; noted in the comment so the
next person does not repeat it.
Navigation: Start here now holds exactly the four entry points a person can be
sent - what it is, make one, choose a workflow, and the Studio overview. The
three pages that were sitting in it went where they belong: the project tour is
conceptual so it moved to Learn, authentication is configuration so it moved to
Developers, and the hosted AI-chat route is a creation surface so it moved to
Create with an agent.
Width: the theme capped the article shell at 72rem and reserved 28rem of every
wide viewport for the contents rail, leaving the reading column at ~664px on a
1600px screen - narrower than the 16:9 video the pages are supposed to show.
Raised the shell and returned 9rem from the gutter, taking the column to roughly
950px. Running prose is held near 80 characters because longer lines measurably
hurt reading; tables, cards, code, and media use the full width. Added .hf-wide
for visuals that should escape the measure entirely.
Components: the quickstart source chooser is now Tabs, which is what parallel
variants of one instruction want, and the help symptoms are visible sections with
their own anchors so support can link a symptom directly instead of the hub.
Wrote the doctrine into AGENTS.md - one component per job, cards never point at
anchors on their own page, no diagram that restates adjacent prose, and the rules
for custom React snippets (named exports, no third-party packages, no CDN).
The code-font rule also matched [class*="code"] and [class*="Code"], which
selects any element whose class merely contains that substring. Every Accordion
wrapper carries the Tailwind utility dark:bg-codeblock, so the whole accordion
inherited IBM Plex Mono: titles and body prose rendered as monospace, which read
like terminal output instead of sentences. This affected every accordion on the
site, including the source chooser that already existed on the quickstart page.
Scoped the rule to real code elements; syntax tokens inherit from pre.
Also reverted the workflow chooser to cards. Collapsing eight situations into
grey accordion rows removed duplication but made the page less inviting to scan,
and the eight starter requests it carried were a third copy - every workflow
guide already contains its own. The cards now link straight to those guides
rather than to anchors further down the page.
help: the eight symptom cards linked to anchors immediately below that repeated
their own descriptions. One accordion per symptom now holds the fix, so scanning
and expanding replaces scrolling past the same words twice. The diagnostic block
and the four real escalation links stay.
studio: dropped the decision flowchart that duplicated the table beneath it, and
added the existing Storyboard screenshot so the Storyboard-versus-Preview split
is shown rather than only described.
quickstart: removed the five-node diagram that restated the six numbered steps,
and pointed the request-writing advice at the prompting guide instead of growing
a third copy of it.
introduction: the showcase file has a single h264 stream and no audio track, so
'press play - it has sound' was simply untrue. Removed the claim; the caption now
only says what the clip is.
workflows: the page listed all eight situations twice - once as cards linking to
anchors a short scroll down, then again as full sections repeating the same
'best for' line. Collapsed both into one accordion per situation whose title is
the chooser and whose body holds the brief and the copyable request, so the eight
cards now lead somewhere real instead of jumping down the page. Dropped the
five-step 'what happens after you choose' (it restates quickstart) and the
six-part request checklist (it restates the prompting guide) in favour of links.
187 lines to 165, eleven headings to one; no starter request was lost.
The karaoke clip rendered as an almost-black tile with one small pill, which is
weak proof on first load. Every caption component in the catalog is type on a
black ground rather than captions burned over footage, so 'Captions on footage'
was also the wrong label. Swapped to the highlight style, which holds up at
tile size, and renamed the tile 'Kinetic captions'.
Reviewed the rendered page instead of the source: the four preview tiles came
out ~110px wide in a 730px content column, with labels wrapping mid-phrase, and
the three-column workflow cards were cramped enough to hyphenate a title into
'presentation s'. Two columns is the practical maximum for this column width, so
the tiles are now a readable 2x2 and the cards are back to two across.
- Lead with the film: the video moves directly under the one-line definition,
so a stranger sees a real result before reading anything.
- Delete the mermaid flow diagram. It duplicated the Steps list underneath it
word for word; the section heading already carries the flow.
- Replace the wall of text under 'What can it make?' with four real HyperFrames
outputs playing inline (~960KB total, deliberately the small clips), then keep
the six workflow paths as compact three-column links.
- Compress the four-row Studio-vs-agent table to two lines. A newcomer needs the
principle, not a decision matrix; the detail belongs in Studio.
- One primary next step instead of three equal cards.
Make /introduction the single "What is HyperFrames?" page a stranger can be
sent, and retire the switchboard that sat in front of it.
- introduction: lead with one sentence + a sound-on result (drop `muted`),
simplify the loop diagram to request -> agent -> project -> video, cut the
fake "See how it works" scroll button, and replace the equal-weight card
walls with one clear "Start here" action.
- guides/index: remove the six-card "What do you need to do?" menu-of-menus.
The nav already routes people and /introduction is the real entry point.
Redirect /guides -> /introduction.
- developers: point the "create a video" link at /quickstart.
mint validate and mint broken-links both pass.
* fix(cli): persist authoring skill in hyperframes.json for durable render attribution
authoring_skill was stamped only on the first render through a workflow
passing --skill, so re-renders, `npm run render`, --batch, existing-project
renders, and general-video lost it — leaving 77-96% of real-human render
volume un-attributed and the skills-penetration metric misleadingly low.
Persist the owning skill in hyperframes.json: `init --skill` stamps it at
creation, `render` resolves the flag then falls back to the stored value, and
an explicit --skill seeds it (seed-once, never overwriting the creating
workflow's identity). Activate all render-producing creation workflows to
declare their skill at init.
Forward-only: does not rewrite historical telemetry.
* fix(cli): patch hyperframes.json in place when seeding the authoring skill
seedProjectAuthoringSkill is the only writer that touches an already existing
hyperframes.json — every other writeProjectConfig call site is guarded to write
only when the file is absent, which made the whole-file overwrite safe by
construction. Round-tripping the seed through normalizeConfig broke that: it
rebuilds the object from a field whitelist with no rest-spread, so any key
outside the schema was silently dropped, a media block was materialized in
projects that never had one, and key order was rewritten. hyperframes.json is
normally committed, so a render introduced a diff the user never asked for, and
any field added to the schema later would be deleted by a render on an older
CLI.
Parse the raw JSON, set authoringSkill, write it back, reusing the file's own
indentation. Unknown keys and formatting survive; the only delta is the key
being added. A corrupt config is now left untouched instead of clobbered.
Seed-once semantics are unchanged, still normalized so a hand-edited garbage
slug neither reaches telemetry nor wedges the seed.
Reported independently by both reviewers on #2762.
* fix(cli): create the docker build context with mkdtempSync
The `--docker` build context was created at a guessable path derived from
`Date.now()` in the world-writable OS temp dir. Another local user can
pre-create or symlink that path and have the build read a Dockerfile they
control. mkdtempSync gets a random suffix and 0o700 from the kernel, and it
creates the directory itself, so the separate mkdirSync goes away.
Pre-existing on main (alert #432, 2026-06-04, packages/cli/src/commands/render.ts),
surfaced against this branch only because the seed commit shifted line numbers in
the same file. Fixed here to unblock the CodeQL gate on #2762 rather than left for
a follow-up; the remaining 10 js/insecure-temporary-file alerts elsewhere in the
repo are untouched and still want their own pass.
* fix(cli): drop the check-then-use race when seeding the authoring skill
The seed tested for the config with existsSync and then wrote, which is a
check-then-use race: the file can be created or swapped between the check and
the write (CodeQL js/file-system-race).
Read once and branch on the failure reason instead. Only ENOENT creates a
config from scratch; any other read failure (permissions, I/O) now leaves an
existing file alone rather than overwriting it with a default, so this is also
strictly safer than the version it replaces.
Also replaces the `as Record<string, unknown>` assertion with an isJsonObject
type guard, per the repo's no-assertion convention.
Behaviour unchanged: all 4 seed regression tests still pass, and the
create/preserve/seed-once/corrupt-untouched paths were re-verified end to end.
## What
Adds the weekly digest entry for July 20 to July 27, 2026 to `docs/weekly-updates.mdx`.
Generated with `bun run changelog:weekly --from 2026-07-20 --to 2026-07-27 --write`, then rewritten to publish quality: entries grouped under Features, Fixes, and Docs, prose rewritten to describe user impact, and the `<!-- TODO: review -->` marker removed.
## Why
Keeps the public weekly-updates page current. The digest is the RSS-backed summary readers get between versioned releases.
## How
- Ran the generator over the July 20 to July 27 range.
- Rewrote the new `<Update>` block: 11 Features, 15 Fixes, 3 Docs bullets.
- Every commit and PR link the generator produced for these entries is retained. No link was invented; all references trace back to `updates/weekly/2026-07-27.md`.
- Headline items: professional color grading across core, Studio, and the CLI; a versioned distributed plan protocol with direct S3 and GCS publishing; the Studio keyframe ease editor; data-driven registry caption components.
- `renderStretch` (#2676) is deliberately omitted, since it was reverted in the same window by #2730 and is not present at HEAD.
- CI-only performance and internal refactor entries are omitted as not user-facing.
## Test plan
Docs-only change. No runtime code touched.
- [ ] Unit tests added/updated
- [x] Manual testing performed
- [x] Documentation updated (if applicable)
Verification performed:
- `git diff origin/main --stat` shows only `docs/weekly-updates.mdx`, 49 insertions and 0 deletions (purely additive, the prior week's entry is untouched).
- Confirmed the TODO marker is gone from the file.
- Confirmed every commit SHA and PR number in the new block appears in the generator's own draft, and that each link label matches its href.
- Confirmed the block contains no em-dashes and no stray JSX-unsafe characters outside inline code.
- `node scripts/check-tracked-artifacts.mjs` passes.
* docs(send-to-guide): enhance turns are free; render is the paid step
The shipped pricing model is import + enhance turns free, only the final
render charged (a monthly free-render credit, then per-minute). The guide
labeled enhance as 'the paid step', which misstates the model to the
authoring agent. Move the paid label to Render.
* docs(send-to-guide): state the tiered render billing contract + pin it in the guide test
Address review: the pricing line must teach Claude the real tiered contract,
not a single universal free render. Per heygen-server usage_limits.py: FREE
accounts get 3 renders/month (then blocked, not billed); paid plans are charged
20 credits per rendered minute at completion. Enhance turns are free.
Also pin the invariant in sendToGuideContract.test.ts: assert Enhance=free /
Render=paid + the tiered figures, and a negative assertion blocking the retired
'Enhance ... paid step' wording from returning.
CLI and Studio feedback were emitted as `survey sent` with `$survey_*`
properties, so every rating was ingested as a PostHog survey response even
though no survey definition, targeting, or popover backs them.
Emit `cli_render_feedback` and `studio_feedback` with plain `rating` /
`comment` properties instead. Same fields, same call sites, same opt-out.