Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
142 commits
Select commit Hold shift + click to select a range
78ab9bc
fix(skills): anonymize CLI feedback repro guidance
vanceingalls Jul 21, 2026
30ca51c
feat(engine): warn when a live map viewport is detected at capture init
vanceingalls Jul 21, 2026
07965e9
fix(studio): play preview audio at speeds above 1x (#2691)
miguel-heygen Jul 21, 2026
84841b8
chore: release v0.7.66
miguel-heygen Jul 21, 2026
696cbdb
chore(skills): package Codex plugin upload (#2668)
jrusso1020 Jul 21, 2026
e786b78
feat(producer): renderStretch to re-time short compositions across lo…
xuanruli Jul 21, 2026
89d4f63
Merge pull request #2677 from heygen-com/via/anonymize-cli-feedback-r…
vanceingalls Jul 21, 2026
ed32898
Merge pull request #2681 from heygen-com/07-21-feat_engine_warn_on_li…
vanceingalls Jul 21, 2026
12e599a
fix(engine): realistic worker memory budget + sizing/feedback telemetry
vanceingalls Jul 21, 2026
63539a0
fix(core): apply position edits to SVG elements, not just HTML (#2724)
hblee12294 Jul 21, 2026
465c9e7
fix(producer): preserve runtime audio variables in distributed plans …
jrusso1020 Jul 21, 2026
4b6bb8f
chore: release v0.7.67
jrusso1020 Jul 21, 2026
2e97e5b
docs(changelog): weekly digest 2026-07-13–2026-07-20 (#2664)
jrusso1020 Jul 22, 2026
807078c
docs(skills): make captions non-optional in changelog-video (#2729)
jrusso1020 Jul 22, 2026
69446e7
Revert "feat(producer): renderStretch to re-time short compositions a…
xuanruli Jul 22, 2026
c6462a0
fix(producer): emit heap advisory at orchestrator, lock message + tel…
vanceingalls Jul 22, 2026
a637f39
fix(producer): credit looping short videos in coverage gate (#2665) (…
jrusso1020 Jul 22, 2026
1e2c7d6
fix(core): preserve nested-rule selectors in composition CSS scoping …
jrusso1020 Jul 22, 2026
71d84ff
chore: release v0.7.68
jrusso1020 Jul 22, 2026
84e4eaf
Merge pull request #2723 from heygen-com/07-21-fix_engine_worker_auto…
vanceingalls Jul 22, 2026
97ec7db
fix(hooks): pre-commit gate denies commit instead of ending the turn
vanceingalls Jul 22, 2026
c39f3cf
Merge pull request #2737 from heygen-com/07-22-fix_hooks_pre-commit_g…
vanceingalls Jul 22, 2026
948264d
fix(producer): resolve residual font and probe failures (#2738)
jrusso1020 Jul 23, 2026
7a294f1
fix(lint): address render preflight review feedback (#2739)
jrusso1020 Jul 23, 2026
222aec4
feat(lint): add rotation_pivot_drift layout check (#2741)
xuanruli Jul 23, 2026
dd7378b
fix(sdk): keep <br> line breaks editable and uncorrupted in setText (…
hblee12294 Jul 23, 2026
f003422
chore: release v0.7.69
jrusso1020 Jul 23, 2026
c67e9dc
feat(registry): make caption-weight-shift data-driven via caption-dat…
vanceingalls Jul 23, 2026
08620b7
feat(registry): make caption-pill-karaoke data-driven via caption-dat…
vanceingalls Jul 23, 2026
020c898
fix(registry): force GSAP render at attach — seek(0) is a no-op on a …
vanceingalls Jul 23, 2026
392a9d2
feat(registry): make caption-emoji-pop data-driven with generic emoji…
vanceingalls Jul 23, 2026
060b6f8
fix(engine): self-verify parallel disk drawElement samples (PRINFRA-352)
vanceingalls Jul 23, 2026
7d4e71d
feat(registry): make caption-highlight data-driven with automatic gro…
vanceingalls Jul 23, 2026
ec791e9
fix(producer): screenshot-retry recovery for disk-path drawElement ve…
vanceingalls Jul 23, 2026
9fc1c2f
fix(producer): verify sequential disk drawElement samples too
vanceingalls Jul 23, 2026
ed89739
feat(registry): make caption-editorial-emphasis data-driven with emph…
vanceingalls Jul 23, 2026
18de2b1
fix(caption-editorial-emphasis,caption-emoji-pop): remove O(n^2) redu…
vanceingalls Jul 23, 2026
5af6203
fix(caption-weight-shift): remove O(n^2) redundant hide-all-others loop
vanceingalls Jul 23, 2026
c85cfae
fix(producer): rethrow disk drawElement verify failure past completen…
vanceingalls Jul 23, 2026
92ffd04
refactor(producer): share DE verify-fallback telemetry mapping across…
vanceingalls Jul 23, 2026
4f69947
fix(registry): clear brand CSS custom properties on unbranded re-atta…
vanceingalls Jul 24, 2026
4889b77
fix(engine): lazy-promisify execFile in psnr so partial child_process…
vanceingalls Jul 24, 2026
b8e1015
fix(producer): close orphaned probe session before verify-triggered r…
vanceingalls Jul 24, 2026
5fd28e1
style: format probe-cleanup retry code (oxfmt)
vanceingalls Jul 24, 2026
fec3bba
Merge pull request #2749 from heygen-com/07-23-fix_engine_verify_disk…
vanceingalls Jul 24, 2026
b9c3e3a
chore: release v0.7.70
vanceingalls Jul 24, 2026
e2846eb
fix(registry): encapsulate caption template runtimes in IIFEs
vanceingalls Jul 24, 2026
688500f
Merge pull request #2757 from heygen-com/release/v0.7.70
vanceingalls Jul 24, 2026
5c2981d
fix(registry): reject non-numeric caption-data versions; boot fetch n…
vanceingalls Jul 24, 2026
8bf9390
fix(registry): liberal emoji-pop brand colors, weight-shift fit fixes…
vanceingalls Jul 24, 2026
9d4c349
Merge pull request #2750 from heygen-com/feat/caption-data-driven
vanceingalls Jul 24, 2026
70213c5
feat(core): define media treatment capabilities
ukimsanov Jul 24, 2026
9e7ddb0
Merge pull request #2751 from heygen-com/feat/media-treatment-schema
ukimsanov Jul 24, 2026
7778c09
fix(preview): serve external symlink assets (#2764)
jrusso1020 Jul 24, 2026
944640c
feat(runtime): render media treatments deterministically
ukimsanov Jul 24, 2026
adb149b
fix(lint): allow bounded GSAP infinite repeats (#2763)
jrusso1020 Jul 24, 2026
d07d187
chore: release v0.7.71
jrusso1020 Jul 24, 2026
7cf6416
Merge pull request #2752 from heygen-com/feat/media-treatment-runtime
ukimsanov Jul 24, 2026
e7f9918
fix(lint): drop false media_in_subcomposition rule (#2765)
miguel-heygen Jul 24, 2026
e710a16
feat(lint): add off_pivot_rotation hub-referenced layout check (#2744)
xuanruli Jul 25, 2026
4582881
feat(cli): add agent-first media treatment tools
ukimsanov Jul 24, 2026
b0d3164
feat(registry): add media treatment overlays
ukimsanov Jul 24, 2026
39c2341
feat(studio): add media treatment inspector
ukimsanov Jul 24, 2026
d5c7d3e
fix(studio): preserve composed media treatments
ukimsanov Jul 24, 2026
bf47416
refactor(studio): simplify preview workspace layout
ukimsanov Jul 24, 2026
3978b8e
Merge pull request #2753 from heygen-com/feat/media-treatment-agent
ukimsanov Jul 25, 2026
359a13f
Merge pull request #2754 from heygen-com/feat/media-treatment-overlays
ukimsanov Jul 25, 2026
05c3029
Merge pull request #2755 from heygen-com/feat/media-treatment-studio
ukimsanov Jul 25, 2026
1f9a8f0
Merge pull request #2756 from heygen-com/feat/studio-preview-layout
ukimsanov Jul 25, 2026
5acbf24
feat(core): add deterministic keyframe ease runtime
miguel-heygen Jul 21, 2026
d84e999
fix(parsers): preserve authored keyframe intent
miguel-heygen Jul 21, 2026
c253dec
feat(studio): add keyframe ease editor
miguel-heygen Jul 21, 2026
4bfbd89
fix(parsers): preserve duration-authored keyframe timing
miguel-heygen Jul 21, 2026
22d9b75
Merge pull request #2672 from heygen-com/codex/studio-timeline-a-runt…
miguel-heygen Jul 25, 2026
60dd431
Merge pull request #2673 from heygen-com/codex/studio-timeline-a-pars…
miguel-heygen Jul 25, 2026
42f80d5
Merge pull request #2674 from heygen-com/codex/studio-timeline-a-ease…
miguel-heygen Jul 25, 2026
b1edeeb
Merge pull request #2675 from heygen-com/codex/studio-timeline-a-arra…
miguel-heygen Jul 25, 2026
3b3d4f5
test(cli): move render module collection outside hooks (#2780)
miguel-heygen Jul 25, 2026
e4d7bde
feat(studio): add keyframe timeline state
miguel-heygen Jul 20, 2026
e34e529
fix(studio): keep gsapAnimations in sync with the keyframe cache
miguel-heygen Jul 25, 2026
acf6766
refactor(studio): one owner for clip-relative keyframe rows
miguel-heygen Jul 25, 2026
d05ecb1
fix(studio): scope the per-file keyframe-cache clear to its own keys
miguel-heygen Jul 26, 2026
3c7400a
fix(studio): close the review findings in this PR instead of at the s…
miguel-heygen Jul 27, 2026
b386b55
fix(studio): clamp the timeline scrub to 0 instead of dropping it
miguel-heygen Jul 27, 2026
521bba6
refactor(studio): resolve tween selector ids through the shared reader
miguel-heygen Jul 27, 2026
d518972
feat(studio): add variable timeline timing and layout
miguel-heygen Jul 20, 2026
95213d7
refactor(studio): give resolveTimelineMove a row-based vertical axis
miguel-heygen Jul 25, 2026
4c7703f
refactor(studio): drop the duplicated row-top docblock
miguel-heygen Jul 26, 2026
e36fb38
feat(studio): add timeline keyframe retiming interactions
miguel-heygen Jul 20, 2026
706f537
fix(studio): let Escape cancel a keyframe retime and throttle its pre…
miguel-heygen Jul 25, 2026
6e0118c
refactor(studio): guard the diamond connector's previous keyframe
miguel-heygen Jul 25, 2026
fed5e5b
feat(studio): add timeline property lanes
miguel-heygen Jul 20, 2026
8bb31b3
feat(studio): add keyframe track headers
miguel-heygen Jul 20, 2026
c264cf2
fix(studio): stop lane-header clicks from reaching the track row
miguel-heygen Jul 25, 2026
4ac57a5
refactor(studio): split the track header's lane math out of its JSX
miguel-heygen Jul 25, 2026
f3ab446
refactor(studio): name the non-keyframe header for what it is
miguel-heygen Jul 25, 2026
555d7f7
refactor(studio): drop the dead expandedElement guard
miguel-heygen Jul 26, 2026
2d1b905
feat(studio): consolidate timeline editor callbacks
miguel-heygen Jul 20, 2026
ba8df26
fix(studio): resolve keyframe fallbacks against the clicked element
miguel-heygen Jul 25, 2026
3f93794
fix(studio): resolve lane-header removal against the clicked element
miguel-heygen Jul 25, 2026
8f66b06
fix(studio): retime the dragged element's own keyframe
miguel-heygen Jul 25, 2026
a423c93
refactor(studio): read animation cache keys through their owner
miguel-heygen Jul 26, 2026
be3b1e3
fix(studio): harden keyframe editing semantics
miguel-heygen Jul 20, 2026
14c10d9
fix(studio): span the clip on the remaining duration-less tween paths
miguel-heygen Jul 25, 2026
b8406e6
refactor(studio): give every keyframe diamond one identity
miguel-heygen Jul 25, 2026
8c0d9a0
refactor(studio): split the diamond row's data contract into its own …
miguel-heygen Jul 26, 2026
2f5f9b1
feat(studio): wire expanded keyframe timeline lanes
miguel-heygen Jul 20, 2026
a6aeffb
fix(studio): address family B timeline review findings
miguel-heygen Jul 25, 2026
b8b9fad
fix(studio): populate keyframe lanes for every timeline composition
miguel-heygen Jul 25, 2026
6c7370b
fix(studio): keep sub-compositions expanded at the end of the timeline
miguel-heygen Jul 25, 2026
e464327
fix(studio): drop the property-group gate in the AST cache load
miguel-heygen Jul 25, 2026
029e4a1
refactor(studio): tighten the expanded-lane cache and height helpers
miguel-heygen Jul 25, 2026
4af9f1a
fix(studio): close the timeline regressions the QA triage attributed …
miguel-heygen Jul 27, 2026
7ec73df
fix(studio): retime flat tween boundaries and target the clicked keyf…
miguel-heygen Jul 27, 2026
48ea642
fix(studio): close the review findings that survived the stack
miguel-heygen Jul 27, 2026
edbb6d1
refactor(studio): double-cast test fixtures and split three dense fun…
miguel-heygen Jul 27, 2026
910ef26
refactor(studio): share keyframe clip-% precision across lane writers
miguel-heygen Jul 27, 2026
1c92c81
fix(studio): target colliding keyframes exactly
miguel-heygen Jul 20, 2026
463429b
feat(studio): bulk-edit easing for merged keyframes
miguel-heygen Jul 20, 2026
899af39
fix(studio): publish keyframe cache refresh atomically
miguel-heygen Jul 20, 2026
c9e12e8
fix(studio): switch keyframe ease modes optimistically
miguel-heygen Jul 20, 2026
fa190d8
perf(studio): define timeline viewport budgets and fixtures
miguel-heygen Jul 20, 2026
bad5a54
test(studio): gate timeline viewport performance in Chromium
miguel-heygen Jul 20, 2026
5e02427
refactor(studio): isolate clip drag lifecycle
miguel-heygen Jul 20, 2026
ecf76d7
refactor(studio): extract timeline render contracts
miguel-heygen Jul 20, 2026
13367e5
perf(studio): centralize timeline viewport geometry
miguel-heygen Jul 20, 2026
9b91ca5
perf(studio): follow playhead across virtualized rows
miguel-heygen Jul 20, 2026
6883a5e
perf(studio): index timeline clip windows
miguel-heygen Jul 20, 2026
b7a9c06
perf(studio): virtualize timeline clip windows
miguel-heygen Jul 20, 2026
059dc30
perf(studio): stabilize virtualized clip gestures
miguel-heygen Jul 18, 2026
1575a4f
perf(studio): stabilize virtualized keyframe retiming
miguel-heygen Jul 18, 2026
41c39e1
perf(studio): stabilize virtualized timeline drops
miguel-heygen Jul 18, 2026
09d085d
perf(studio): virtualize timeline marquee selection
miguel-heygen Jul 18, 2026
dd1a8a4
fix(studio): scope timeline context targets
miguel-heygen Jul 18, 2026
fd55919
perf(studio): stabilize virtualized beat gestures
miguel-heygen Jul 18, 2026
cb93b06
fix(studio): scope timeline ease focus lifecycle
miguel-heygen Jul 20, 2026
5b22e39
refactor(studio): extract live timeline clock
miguel-heygen Jul 20, 2026
2d0f39e
feat(studio): model logical timeline navigation
miguel-heygen Jul 20, 2026
11d12c1
feat(studio): expose timeline treegrid semantics
miguel-heygen Jul 20, 2026
8b6adc4
feat(studio): coordinate logical timeline focus
miguel-heygen Jul 20, 2026
ef7ffbe
feat(studio): add timeline keyboard controls
miguel-heygen Jul 20, 2026
d791bac
perf(studio): schedule adaptive timeline thumbnails
Jul 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
70 changes: 57 additions & 13 deletions .agents/skills/changelog-video/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ ffmpeg -y -stream_loop 15 -i <SKILL_DIR>/assets/bg-pattern.mp4 -t <TOTAL> \
cp <SKILL_DIR>/examples/master-skeleton.html project/index.html
```

Then **read `references/build-spec.md` end-to-end** (not skimmed) — it defines the brand tokens (TT Norms Pro + ABC Solar Display + TT Norms Mono, cream `#f5f6f4`, rationed green `#5ef17c`, glass cards with green-tinted borders, kicker/sec-chip pill shape, caption rail at `top: 1002`) that every scene inherits from the scaffold.
Then **read `references/build-spec.md` end-to-end** (not skimmed) — it defines the brand tokens (TT Norms Pro + ABC Solar Display + TT Norms Mono, cream `#f5f6f4`, rationed green `#5ef17c`, glass cards with green-tinted borders, kicker/sec-chip pill shape, 32px caption rail at `top: 990`) that every scene inherits from the scaffold.

Only THEN begin steps 1-6 below. Steps 1-4 (parse, route, script, VO) plan what goes into the scaffold; step 5 fills placeholders (`<RANGE>`, `<TOTAL>`, `<CUT_N>`, `<DUR_N>`, scene bodies) inside the already-copied `project/index.html` — you do NOT rewrite the scaffold's chrome, fonts, palette, or layout shell.

Expand Down Expand Up @@ -99,6 +99,26 @@ The aligner prints `MISMATCH` warnings — resolve every one before building
is the clock**: all beat times come from `vo-words.json`; a VO regen re-opens
every seam.

**Word-timings are a hard gate.** Before moving on to step 5, verify
`vo-words.json` is non-empty and has a `words: [...]` array with `start`/`end`
per word. If it's empty (0 bytes) or missing the array — a known failure mode
when the TTS provider returns audio but no timestamp payload — DO NOT proceed
without them. Fallback: forced-align the produced audio against the display
script using local whisper:

```bash
uvx --from openai-whisper whisper voiceover.mp3 \
--model base.en --language en --word_timestamps True \
--output_format json --output_dir .
# then run align-captions.mjs with --words voiceover.json (same shape)
```

Whisper mishears TTS renderings ("gee-sap" → "gsap", "heyjen" → "hey Jen",
etc.) — captions still use the DISPLAY spelling from `script-tokens.json`;
whisper only supplies the timestamps. `align-captions.mjs` handles the join.
This fallback is the difference between a captioned build and a silently
uncaptioned one.

### 5 · Build

Follow `references/build-spec.md` exactly: brand tokens + fonts (bundled in
Expand All @@ -107,6 +127,22 @@ chrome, caption rail, one rationed green moment per scene. Then the doctrine
order: `ledger.json` (all ordinary seams cut-the-curve LEFT) → seam-stamp →
internal beats on VO words → seam-gate verify.

**Captions are non-optional.** The master-skeleton ships a caption-rail IIFE
that reads a `LINES` array — leaving that array empty is a shipped bug, not a
style choice. Populate it from `captions.json` before proceeding to step 6:

```javascript
// paste in place of "const LINES = /* … */ []" in the caption-rail IIFE:
const LINES = /* contents of captions.json */ [
{ id: 0, end: 2.74, w: [["This", 0.0], ["week,", 0.30], …] },
];
```

If `align-captions.mjs` was skipped or `LINES` is `[]`, the frame check in
step 6 will fail — do not paper over it by removing `#cap-line` from the
scaffold.

### 6 · Gates (all green before presenting)

1. `bun run --cwd packages/cli hyperframes check` (or the installed
Expand All @@ -120,6 +156,12 @@ internal beats on VO words → seam-gate verify.
4. Do NOT render unless the user asks. After a requested render, verify
frames from the MP4 (`ffmpeg -ss <t> … -frames:v 1`): captions present,
background video not black, no tiny/frozen frames.
5. **Caption presence gate — hard fail.** Sample 3-4 frames spread across
the VO's spoken window (e.g. `t=3`, `t=15`, `t=30`, `t=42` for a 48s VO)
and confirm the caption rail at `top: 990` renders visible text on each.
If any frame in a spoken interval is missing captions, the build ships
uncaptioned — treat it as a red gate and re-check step 5's `LINES`
population. This is exactly what went wrong on the Jul 13-20 v4 build.

## Project layout

Expand All @@ -136,15 +178,17 @@ projects/active/weekly-changelog-<range>/

## Anti-patterns

| Don't | Instead |
| ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| Bullet-point slides for UI changes | Mock the surface acting out the change |
| Fake UI for un-representable items | Honest checklist scene |
| Plain "JSON"/"CLI" in the TTS text | Lexicon spoken forms; display stays standard |
| Phonetic spellings in captions | Captions always render the display layer |
| Guessing an unknown term's pronunciation | Ask, then grow the lexicon |
| Speaking every changelog item | ≤3 per theme; the digest link carries the rest |
| Green accents everywhere | One green moment per scene (#5ef17c) |
| Starting from a prior video's index.html | Step 0 — copy `examples/master-skeleton.html` from this skill into `project/index.html`, always |
| Hand-crafted `@font-face` / WebGL shader / custom BGM | Step 0 — copy this skill's `assets/` verbatim; the skill's assets ARE the brand |
| Delivered without CloudFront invalidation | Run `aws cloudfront create-invalidation` on distribution `E2BSLVSZ7FG3U0` for the exact path after any S3 replace — CDN caches the old file otherwise |
| Don't | Instead |
| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Bullet-point slides for UI changes | Mock the surface acting out the change |
| Fake UI for un-representable items | Honest checklist scene |
| Plain "JSON"/"CLI" in the TTS text | Lexicon spoken forms; display stays standard |
| Phonetic spellings in captions | Captions always render the display layer |
| Guessing an unknown term's pronunciation | Ask, then grow the lexicon |
| Speaking every changelog item | ≤3 per theme; the digest link carries the rest |
| Green accents everywhere | One green moment per scene (#5ef17c) |
| Starting from a prior video's index.html | Step 0 — copy `examples/master-skeleton.html` from this skill into `project/index.html`, always |
| Hand-crafted `@font-face` / WebGL shader / custom BGM | Step 0 — copy this skill's `assets/` verbatim; the skill's assets ARE the brand |
| Delivered without CloudFront invalidation | Run `aws cloudfront create-invalidation` on distribution `E2BSLVSZ7FG3U0` for the exact path after any S3 replace — CDN caches the old file otherwise |
| Shipping with the `LINES` array empty in the scaffold | Step 4 must produce a populated `captions.json`; step 5 must paste it into the IIFE; step 6 gate 5 must confirm captions on rendered frames. An empty `LINES` = uncaptioned ship = re-do the run |
| No `vo-words.json` → skip captions and ship anyway | Fall back to whisper forced alignment on the produced audio; captions are non-optional |
6 changes: 3 additions & 3 deletions .agents/skills/changelog-video/examples/master-skeleton.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@
.glass { background: rgba(10,12,11,.78); border: 1px solid rgba(190,255,205,.32);
border-radius: 22px; box-shadow: 0 24px 60px rgba(0,0,0,.5); }
/* caption rail — overlay on top of the film, never a reserved band */
#cap-line { position: absolute; left: 0; right: 0; top: 1002px; height: 40px; text-align: center;
z-index: 7; font-family: 'TT Norms Pro', sans-serif; font-weight: 500; font-size: 25px;
letter-spacing: .01em; color: rgba(245,246,244,.92);
#cap-line { position: absolute; left: 0; right: 0; top: 990px; height: 52px; text-align: center;
z-index: 7; font-family: 'TT Norms Pro', sans-serif; font-weight: 500; font-size: 32px;
letter-spacing: .01em; color: rgba(245,246,244,.94);
text-shadow: 0 2px 14px rgba(0,0,0,.85), 0 0 3px rgba(0,0,0,.6);
white-space: nowrap; pointer-events: none; }
.cap-phrase { position: absolute; left: 0; right: 0; }
Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/changelog-video/references/build-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ otherwise) and must stay flat 2D (no 3D ancestors).
y ∈ [288, 944].
- Outro (≤3.5s): kicker FULL DIGEST, "See what shipped." ~96px, green rule,
mono URL chip, tag line. Fade all + chrome ~0.5s before end.
- Caption rail per `script-voice.md` (top: 1002).
- Caption rail per `script-voice.md` (top: 990, font-size: 32px, height: 52). Mandatory — populate the master-skeleton's `LINES` array from `captions.json` before render; see SKILL.md step 5.

## Seams + internal life (doctrine mechanics)

Expand Down
6 changes: 3 additions & 3 deletions .agents/skills/changelog-video/references/script-voice.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ transcript) before the captions are trusted.
## Caption rail (rendering)

Per `captions-overlay`: a quiet OVERLAY, never a reserved band. One line,
bottom-center (top: 1002px on 1080-square), TT Norms Pro 500 25px,
ink .92, soft dark text-shadow, words fading in (0.12s) on their timestamps,
phrase swaps as sets. Keep critical small text out of the bottom ~80px
bottom-center (top: 990px, height: 52px on 1080-square), TT Norms Pro 500 32px,
ink .94, soft dark text-shadow, words fading in (0.12s) on their timestamps,
phrase swaps as sets. Keep critical small text out of the bottom ~100px
center span; everything else may run under the rail.
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "hyperframes",
"description": "HyperFrames by HeyGen. Write HTML, render video. Compositions, GSAP and runtime adapter animations, captions, voiceovers, audio-reactive visuals, and website capture for HyperFrames.",
"version": "0.7.65",
"version": "0.7.71",
"author": {
"name": "HeyGen",
"email": "hyperframes@heygen.com",
Expand Down
2 changes: 1 addition & 1 deletion .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"type": "command",
"timeout": 180,
"statusMessage": "Running build + lint + typecheck before commit…",
"command": "node -e \"\nconst chunks = [];\nprocess.stdin.on('data', d => chunks.push(d));\nprocess.stdin.on('end', () => {\n const input = JSON.parse(Buffer.concat(chunks).toString());\n const cmd = input.tool_input?.command || '';\n if (!/git\\s+commit\\b/.test(cmd)) process.exit(0);\n const { execSync } = require('child_process');\n const fs = require('fs');\n const path = require('path');\n const os = require('os');\n // A leading 'cd <path>' in the command is the real target directory:\n // Claude Code prefixes this whenever the session's tracked cwd differs\n // from this hook process's own cwd (e.g. a sibling repo worktree) — the\n // hook process itself always inherits the primary working directory,\n // never the command's actual target.\n function resolveTargetDir() {\n const m = cmd.match(/^\\s*cd\\s+('([^']+)'|(\\S+))\\s*(?:&&|;|\\n|$)/);\n if (m) {\n let dir = m[2] || m[3];\n if (dir.startsWith('~')) dir = path.join(os.homedir(), dir.slice(1));\n dir = path.resolve(process.cwd(), dir);\n if (fs.existsSync(dir)) return dir;\n }\n return process.cwd();\n }\n const targetDir = resolveTargetDir();\n let repoRoot;\n try {\n repoRoot = execSync('git rev-parse --show-toplevel', { cwd: targetDir, encoding: 'utf8' }).trim();\n } catch {\n process.exit(0);\n }\n // Only this repo's own bun build/lint/typecheck applies — skip silently\n // for any other repo (e.g. a sibling Go/Python repo touched in the same session).\n const pkgPath = path.join(repoRoot, 'package.json');\n if (!fs.existsSync(pkgPath)) process.exit(0);\n let pkg;\n try {\n pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));\n } catch {\n process.exit(0);\n }\n if (pkg.name !== 'hyperframes-monorepo') process.exit(0);\n const steps = [\n ['bun run build', 'Build'],\n ['bun run lint', 'Lint'],\n ['bun run --filter \\'*\\' typecheck 2>&1 | grep -v \\'vitest\\\\|test\\\\.ts\\' || true', 'Typecheck'],\n ];\n const failures = [];\n for (const [script, label] of steps) {\n try {\n execSync(script, { cwd: repoRoot, stdio: 'pipe' });\n } catch (e) {\n failures.push(label + ':\\n' + (e.stdout?.toString() || e.message).slice(0, 400));\n }\n }\n if (failures.length > 0) {\n process.stdout.write(JSON.stringify({\n continue: false,\n stopReason: '❌ Pre-commit checks failed:\\n\\n' + failures.join('\\n\\n') + '\\n\\nFix the issues above before committing.',\n }));\n }\n});\""
"command": "node -e \"\nconst chunks = [];\nprocess.stdin.on('data', d => chunks.push(d));\nprocess.stdin.on('end', () => {\n const input = JSON.parse(Buffer.concat(chunks).toString());\n const cmd = input.tool_input?.command || '';\n if (!/git\\s+commit\\b/.test(cmd)) process.exit(0);\n const { execSync } = require('child_process');\n const fs = require('fs');\n const path = require('path');\n const os = require('os');\n // A leading 'cd <path>' in the command is the real target directory:\n // Claude Code prefixes this whenever the session's tracked cwd differs\n // from this hook process's own cwd (e.g. a sibling repo worktree) — the\n // hook process itself always inherits the primary working directory,\n // never the command's actual target.\n function resolveTargetDir() {\n const m = cmd.match(/^\\s*cd\\s+('([^']+)'|(\\S+))\\s*(?:&&|;|\\n|$)/);\n if (m) {\n let dir = m[2] || m[3];\n if (dir.startsWith('~')) dir = path.join(os.homedir(), dir.slice(1));\n dir = path.resolve(process.cwd(), dir);\n if (fs.existsSync(dir)) return dir;\n }\n return process.cwd();\n }\n const targetDir = resolveTargetDir();\n let repoRoot;\n try {\n repoRoot = execSync('git rev-parse --show-toplevel', { cwd: targetDir, encoding: 'utf8' }).trim();\n } catch {\n process.exit(0);\n }\n // Only this repo's own bun build/lint/typecheck applies — skip silently\n // for any other repo (e.g. a sibling Go/Python repo touched in the same session).\n const pkgPath = path.join(repoRoot, 'package.json');\n if (!fs.existsSync(pkgPath)) process.exit(0);\n let pkg;\n try {\n pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));\n } catch {\n process.exit(0);\n }\n if (pkg.name !== 'hyperframes-monorepo') process.exit(0);\n const steps = [\n ['bun run build', 'Build'],\n ['bun run lint', 'Lint'],\n ['bun run --filter \\'*\\' typecheck 2>&1 | grep -v \\'vitest\\\\|test\\\\.ts\\' || true', 'Typecheck'],\n ];\n const failures = [];\n for (const [script, label] of steps) {\n try {\n execSync(script, { cwd: repoRoot, stdio: 'pipe' });\n } catch (e) {\n failures.push(label + ':\\n' + (e.stdout?.toString() || e.message).slice(0, 400));\n }\n }\n if (failures.length > 0) {\n process.stdout.write(JSON.stringify({\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'deny',\n permissionDecisionReason: '❌ Pre-commit checks failed:\\n\\n' + failures.join('\\n\\n') + '\\n\\nFix the issues above, then retry the commit.',\n },\n }));\n }\n});\""
}
]
}
Expand Down
Loading