* 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.
* docs(changelog): weekly digest 2026-07-13–2026-07-20
* docs(changelog): embed the weekly video in the Jul 13-20 digest
Adds the Jul 13-20 changelog video (produced via the changelog-video
skill, hosted at static.heygen.ai) at the top of the Update block using
Mintlify's <Frame> component with a native <video controls>.
Video URL: https://static.heygen.ai/hyperframes/changelog-videos/weekly-changelog-jul13-20.mp4
Slack thread: C0ACCNHLG3U · 1784565857.520599
The Claude Design Send-to guide rendered at its URL but was an orphan — absent from the
docs nav and llms.txt, so it never surfaced as a fetchable search RESULT (only as body
text in another page's excerpt, which Claude Design's web_fetch rejects). Add it to the
Guides nav (→ llms.txt index) next to claude-design, and give it frontmatter title +
description for a clean entry. This makes the full guide discoverable + fetchable via
search, the reliable path since a URL named only in a tool description is not fetchable.
* docs(guides): resolve the fidelity contradiction (preserve substance, adapt form)
The guide called the rebuild 'lossy by nature' while the checklist demanded 'content
match the brief exactly', with no rule for what to preserve vs adapt. On a live import
CD kept fonts/palette/hero but genericized the source's real figures (2.4M signals/sec,
+240% spike) into vague phrases. Resolve around one principle: substance (real copy,
exact palette/fonts, distinctive figures/data, product names, signature visuals) is
preserved verbatim; only the form (static page to timed multi-scene motion) adapts.
* test(guides): pin the Send-to fidelity contract against silent regression
Address review on #2620: the resolved 'preserve substance, adapt form' instruction is
load-bearing LLM-facing prompt text, but validate-docs only proves syntax. Add a semantic
pin (mirroring packages/cli figma skillContent.test.ts) asserting the three positive
concepts are present and the two retired contradictory phrases ('lossy by nature',
'content match the brief exactly') cannot silently return.
* style(guides): oxfmt the Send-to fidelity-contract test
Wrap the readFileSync call to oxfmt form (printWidth 100); unblocks required Format + its
preflight/preview-regression cascade on #2620.
* docs(guides): redirect Send-to imports to the Send-to guide
Claude Design's only doc-discovery tool is web search, which ranks this Template-First
(Download-ZIP) guide first; the Send-to guide is not indexed, so Send-to imports get
authored against the wrong flow. A body link here lets CD's fetch of this page unlock a
fetch of the correct guide (its URL now appears in a prior fetch result).
* docs(guides): add Send-to redirect note to the Claude Design landing page
The /guides/claude-design landing page (claude-design.mdx) is the indexed nav page and
points only at the Download-ZIP instruction file; a Send-to author landing here got no
pointer to the Send-to guide. Mirror the redirect from claude-design-hyperframes.md.