Commit Graph
943 Commits
Author SHA1 Message Date
Miguel Ángel 6c191e2292 chore: bump version to 0.6.35 2026-05-22 13:31:45 -04:00
Miguel Ángel 36de02c4bf fix(studio): stop composition fetch-404 flood and cap error telemetry
Two fixes for the 3M+ unhandled_promise_rejection events/day spike:

1. Filter: suppress "Error fetching ... 404" rejections from composition
   code — these are asset-not-found content errors, not Studio bugs.

2. Rate-limit: cap both error and rejection telemetry at 50 per session.
   After the cap, emit a single *_cap_reached event so we know capping
   occurred without generating unlimited events.

3. Root cause: webAudioTransport now checks response.ok before decode
   and caches failed URLs in _failedSrcs so repeat ticks don't re-fetch
   the same 404 on every playback frame.

Also add playground/ to fallow ignorePatterns — local experiment
directory was tripping the audit gate.
2026-05-22 13:22:00 -04:00
Miguel Ángel aebb7b2660 chore: bump version to 0.6.34 2026-05-22 11:44:06 -04:00
Miguel Ángel 4ba735c8ff feat(studio): enable blocks panel by default
Flip the fallback from false to true so the blocks panel is on for
everyone out of the box. Users can still disable it via
VITE_STUDIO_ENABLE_BLOCKS_PANEL=false if needed.
2026-05-22 11:41:50 -04:00
Lirian SuandClaude Opus 4.7 69b7965446 fix(core): preserve replace-pattern characters in bundled runtime IIFE
`injectInterceptor` used `String.prototype.replace(target, replacement)`
to inject the runtime `<script>` before `</head>`. The replacement
string is a substitution template — `$&` expands to the matched
substring, and the minified runtime IIFE contains legitimate `$&`
sequences (e.g. `if(te&&$&!y.hasAttribute(...))`), so every `$&` in
the body was silently rewritten to `</head>`, producing
`Unexpected token '<'` SyntaxErrors and breaking every timeline in
the bundle.

Switch to the function-replacer form so the runtime body is passed
through verbatim. Add a regression test that diffs the bundled
runtime body against `getHyperframeRuntimeScript()` and asserts only
one `</head>` survives in the document — the test exercises the
`<head>`-present injection path (the only branch that uses the
substitution template; the no-`<head>` fallback uses slice+concat
and was unaffected).

Only the bundler is affected — `producer/fileServer.ts` already uses
the function form via `injectScriptsIntoHtml` in
`htmlDocument.ts`, so render output was correct. Snapshot, preview,
studio, layout, and validate all consume `bundleToSingleHtml` and
were broken before this fix.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 15:06:11 +08:00
Miguel Ángel ee4e088434 chore: bump version to 0.6.33 2026-05-21 22:03:20 -04:00
Miguel Ángel 0cc012cc06 fix: update tests for double-pause seek and formatted insertion 2026-05-21 22:00:14 -04:00
Miguel Ángel 2483ab5446 feat(studio): add Tooltip to player and timeline controls 2026-05-21 21:55:13 -04:00
Miguel Ángel 8ffa2eb8c1 fix(studio): use first child element for tooltip positioning 2026-05-21 21:47:29 -04:00
Miguel Ángel 738523b721 feat(studio): add styled Tooltip component to tabs and panels
Create a Tooltip component with styled popover (dark bg, border,
shadow) that appears on hover with a 400ms delay. Applied to:
- Left sidebar tabs: Code, Comps, Assets, Catalog
- Right panel tabs: Design, Layers, Motion, Renders

Replaces native title attributes with proper styled tooltips.
2026-05-21 21:41:30 -04:00
Miguel Ángel 850ff9301f feat(studio): add tooltips to studio controls and tabs
Add title attributes to interactive elements that were missing them:
- PlayerControls: Play/Pause, playback speed, shortcuts panel, clear
  in/out-point buttons, and jump-to-frame Go button
- StudioRightPanel: Design, Layers, Motion, and Renders tab buttons
2026-05-21 21:34:35 -04:00
Miguel Ángel 3e7b464b3b fix(studio): ensure GSAP timeline stays paused after seek
Call tl.pause() both before AND after tl.seek() in the adapter.
GSAP's seek() can reactivate a timeline depending on internal state;
the second pause() guarantees it stays frozen at the seeked position.
2026-05-21 21:26:32 -04:00
Miguel Ángel 7b1aa23039 fix(studio): pause all media elements in iframe on seek
When seeking via the slider or timeline scrub, explicitly pause all
<video> and <audio> elements inside the preview iframe. The GSAP
timeline pauses but iframe media elements can continue playing
independently, causing audio to keep going after a seek.
2026-05-21 21:17:11 -04:00
Miguel Ángel cf940326e9 feat(studio): make prompt modal editable with textarea 2026-05-21 20:31:00 -04:00
Miguel Ángel 300ef395ea fix(studio): destructure onShowPrompt prop in BlockCard 2026-05-21 20:29:04 -04:00
Miguel Ángel d2da3bf6db feat(studio): show prompt preview modal before copying
Clicking "Ask agent" now opens a modal that shows the full generated
prompt so the user can read it before copying. The modal has a "Copy
prompt" button that turns green on success. This replaces the silent
clipboard copy that gave no visibility into what was copied.
2026-05-21 20:12:02 -04:00
Miguel Ángel 0b0021a310 feat(studio): improve Ask agent UX, add tooltips to tabs and buttons
- Ask agent button turns green with checkmark on copy
- Add button shows tooltip "Add to composition at current time"
- Ask agent shows tooltip "Copy a prompt to paste into your AI agent"
- Tab tooltips: Code, Comps, Assets, Catalog each explain their purpose
- Search placeholder updated to "Search by name, category, or tag…"
2026-05-21 19:57:22 -04:00
Miguel Ángel 76d2c6af5d fix(studio): fix catalog Add button per category, search by tags
Add button rules: VFX, Social, Scenes get Add + Ask agent. Captions,
Transitions, Effects, Data get Ask agent only.

Search now matches category names and tags, so searching "captions"
shows all caption blocks.
2026-05-21 19:54:35 -04:00
Miguel Ángel d5518b26f1 fix(studio): destructure onAdd prop in BlockCard 2026-05-21 19:50:36 -04:00
Miguel Ángel 8c698d7f2c feat(studio): show Add button for components, Ask agent for all
Components (hyperframes:component) get both "Add" and "Ask agent"
buttons since they work as drop-in overlays. Blocks (scenes, data,
VFX, transitions) show only "Ask agent" since they need agent-guided
customization.
2026-05-21 19:31:14 -04:00
Miguel Ángel fb48b8d9b4 feat(studio): include composition context in catalog agent prompts
The "Ask agent" button now copies the current composition state along
with the category-specific prompt: playback time, active composition
path, dimensions, and all elements visible at the current time with
their track, timing, and source paths. Gives the agent full context
to place and customize the block correctly.
2026-05-21 19:23:08 -04:00
Miguel Ángel 2614071618 feat(studio): show main composition behind component sub-composition previews
When previewing a component (compositions/components/*), render the
main composition player as a backdrop behind the transparent component
overlay. This lets users see captions, vignettes, and other overlays
in context instead of against a black void.
2026-05-21 19:01:20 -04:00
Miguel Ángel 5c79cd0a8e fix(studio): rewrite block dimensions to match host project on install
Registry blocks are authored at 1920x1080 but projects may use
different dimensions (e.g. 1280x720). After installing a block, the
server now reads the host project's data-width/data-height from
index.html and rewrites the block's viewport meta and CSS dimensions
to match, preventing overflow.
2026-05-21 18:54:36 -04:00
Miguel Ángel e316aa4409 feat(studio): replace Add with Ask agent in catalog cards
Remove the Add button and drag-and-drop from catalog cards — blocks
and components need agent-guided customization, not blind insertion.

Each card now shows a single "Ask agent" button that copies a rich,
category-specific prompt to clipboard with context about what the
block does and how to customize it (captions: transcribe + style,
transitions: place at cut point, data: replace values, etc.).
2026-05-21 18:52:11 -04:00
Miguel Ángel 3e9794a23a feat(studio): add dual-action buttons to catalog cards
Each catalog card now shows two hover buttons:
- "Add" — inserts the block/component into the composition at the
  current playhead position (existing behavior, now with + icon)
- "Agent prompt" — copies a contextual prompt to clipboard tailored
  to the block's category (captions, transitions, VFX, etc.) so the
  user can paste it into their AI agent for guided customization
2026-05-21 18:46:59 -04:00
Miguel Ángel 13be10afb7 chore: bump version to 0.6.32 2026-05-21 18:29:54 -04:00
Miguel Ángel 67701df4e9 fix(studio): add blocks at current playhead, extend root duration
Blocks and components now start at the current playhead position
instead of being appended after all existing content. If the new
element extends beyond the root composition's data-duration, the
root is automatically extended to fit.
2026-05-21 18:28:17 -04:00
Miguel Ángel 6f9b655fff fix(studio): format inserted block HTML with proper indentation
insertTimelineAssetIntoSource now detects the parent indent level and
adds the new element with matching child indentation. Block attributes
are written one-per-line for readability.
2026-05-21 18:28:17 -04:00
Miguel Ángel 997e3948e4 fix(studio): use host composition dimensions for all added blocks
Blocks were using their own native dimensions (e.g. 1920x1080) instead
of the host composition dimensions (e.g. 1280x720), causing them to
overflow the viewport and break the layout. The block's iframe scales
its content to fit the container, so using host dimensions is correct.
2026-05-21 18:28:17 -04:00
Miguel Ángel 34a4ad2ed3 fix(studio): read z-index from live iframe DOM, rename Layer to Z-index
Replace fragile regex z-index parsing with getComputedStyle on the
preview iframe elements — the same source of truth the inspector uses.

Rename "Layer" to "Z-index" in the design panel for clarity.
2026-05-21 18:28:17 -04:00
Miguel Ángel 0999ed56e6 fix(studio): lift block drop handling above DomEditOverlay
The DomEditOverlay sits at z-10 with pointer-events:auto over the
preview, intercepting all drag events before they reach NLEPreview's
viewport. Move block drop handling from NLEPreview up to the wrapper
div in NLELayout that contains both the preview and the overlay, so
drag-and-drop from the Catalog panel onto the preview area works
regardless of inspector state.
2026-05-21 18:28:17 -04:00
Miguel Ángel 289aa03499 fix(studio): inject runtime env overrides for pre-built SPA mode
VITE_STUDIO_* env vars set in the user's shell had no effect when
running `hyperframes preview` because the pre-built studio bundle had
them baked at Vite build time.

The embedded Hono server now collects VITE_STUDIO_* vars from
process.env and injects them as a `window.__HF_STUDIO_ENV__` script
tag into index.html. The client merges this runtime object on top of
the baked `import.meta.env`, so flags like
VITE_STUDIO_ENABLE_BLOCKS_PANEL=1 work as expected at runtime.
2026-05-21 18:27:29 -04:00
Miguel Ángel f51d324ff5 fix(studio): show block preview directly in the player area
Render the catalog hover preview as a full-bleed video inside the
preview overlay slot instead of a dimmed card overlay on top.
2026-05-21 18:27:29 -04:00
Miguel Ángel 9f9b9f4c06 feat(studio): improve blocks panel UX
- Rename "Blocks" tab to "Catalog"
- Replace fullscreen hover popup with inline preview in main area
- Fix z-index: newly added blocks/components use max existing z-index + 1
  instead of element count, ensuring they appear on top
2026-05-21 18:27:29 -04:00
Miguel Ángel 1d6ea53db9 feat(studio): preserve drop position when dropping blocks onto preview
Blocks dragged from the Blocks panel and dropped onto the composition
preview now land at the position where they were dropped, instead of
always being placed at (0, 0).

The preview viewport converts screen coordinates to composition space
using the stage element's bounding rect, accounting for zoom and pan.
A visual drop indicator (dashed border overlay) appears while dragging
over the preview.
2026-05-21 18:27:29 -04:00
Miguel Ángel c61ccdb7a6 fix(studio): suppress ResizeObserver loop noise from error telemetry 2026-05-21 17:13:47 -04:00
Miguel Ángel 4c358e6dcc Merge pull request #1001 from heygen-com/fix/sub-comp-t0-baseline-regen
test(producer): regenerate sub-comp-t0 baseline + add gsap_from_opacity_noop lint rule
2026-05-21 21:42:37 +02:00
Miguel Ángel 4c2a90a1be Merge pull request #1004 from heygen-com/feat/lint-rules-v2
feat(lint): font loading + invalid capture path composition rules
2026-05-21 21:33:38 +02:00
Miguel Ángel 37329b0ba1 Merge pull request #1000 from heygen-com/feat/studio-preserve-playhead
fix(studio): preserve playhead position on composition refresh
2026-05-21 21:29:32 +02:00
Miguel Ángel e2de547a28 feat(studio): auto-reveal source when selecting elements in Code tab
When the Code tab is active and a user clicks an element in the preview,
the code editor now auto-scrolls to the corresponding HTML source. This
removes the need for Alt+click — the existing click-to-source mechanism
fires automatically when the Code tab is already open.

- Add getTab() to LeftSidebarHandle for reading the active tab
- Add getSidebarTab getter to useDomEditSession
- Add effect that calls openSourceForSelection on selection change
  when Code tab is active
2026-05-21 15:25:51 -04:00
Miguel Ángel e0c3b283a0 test(producer): regenerate sub-comp-t0 baseline inside amd64 Docker
Previous baseline was generated on arm64 host, causing 87 failed
frames in CI (amd64). Regenerated with --platform linux/amd64 to
match CI's Chrome/FFmpeg pixel output.
2026-05-21 15:11:07 -04:00
Miguel Ángel ee8dacab1e fix(lint): exclude gsap.fromTo() from gsap_from_opacity_noop rule
gsap.fromTo(target, fromVars, toVars) animates to toVars, not to
the current CSS value — so fromTo({opacity:0}, {opacity:1}) with
CSS opacity:0 is a legitimate 0→1 fade-in, not a noop. The rule
was false-positiving on these calls with error severity, which
would block the render pipeline.

Drop the fromTo branch from the trigger guard and add a test case
that proves fromTo does not fire.
2026-05-21 14:10:57 -04:00
ukimsanov 2c9544f6d4 feat(lint): font loading + invalid capture path composition rules
Two new composition lint rules catching failure modes that recurred
across the 11-round website-to-video eval. Both ship with vitest
coverage; total lint suite goes from 148 to 151 tests.

**`fonts.ts` (new) — two warnings**

- `google_fonts_import`: composition loads fonts from
  `fonts.googleapis.com` via `<link>` or `@import url(...)`. External
  font requests fail in sandboxed/offline renders and add latency.
  Fix hint points to root-relative `capture/assets/fonts/...woff2`
  with a local `@font-face` declaration.
- `font_family_without_font_face`: CSS uses a font-family that
  isn't declared with `@font-face` and isn't in the auto-bundled
  font set (Inter, JetBrains Mono, etc.). Text would silently fall
  back to system-ui — the visual fidelity loss the eval kept hitting.
  Fix hint points to the captured woff2 files.

**`composition.ts` invalid_capture_path (new) — one error**

Sub-compositions live in `compositions/` but get served with the
project root as their base URL. `<img src="../capture/...">` works
on disk but 404s in Studio and renders. Errors with a fix hint
saying replace `../capture/` with root-relative `capture/`.
Three vitest cases: `<img>` triggers, multi-occurrence url()s are
counted, root-relative paths stay clean. Registry source files and
installed blocks are exempted.

**Wiring**

`hyperframeLinter.ts` runs the new fonts rules alongside the existing
rule set; the composition rule was added inline so it picks up
automatically.
2026-05-21 11:08:38 -07:00
Miguel ÁngelandClaude Sonnet 4.6 9e51f5cfaa feat(lint): add gsap_from_opacity_noop rule — catch invisible elements
Detects when an element has CSS `opacity: 0` (inline or style block) AND
is targeted by gsap.from({opacity: 0}). Since from() animates FROM the
specified value TO the CSS value, this produces a 0→0 animation where
the element never becomes visible.

Root cause of all-black renders from the product-launch-video skill:
every text element had opacity:0 in CSS + gsap.from({opacity:0}),
making all text permanently invisible despite the timeline "working."

Fires as error (not warning) to block the render pipeline. Includes
actionable fix hint. 4 test cases: inline style, style block, clean
code (no false positive), and gsap.to() exit (no false positive).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 17:58:44 +00:00
ukimsanov 65c5209be8 chore(cli): bump sharp ^0.34.0 → ^0.34.5
Required by the contact-sheet pagination code added on this PR
(uses Sharp APIs that landed in 0.34.5). Originally bumped on
#987 by mistake — moved here per Copilot review.
2026-05-21 10:57:37 -07:00
ukimsanov 62b55171e9 feat(capture): pipeline improvements — contact sheets, design styles, snapshot
Capture pipeline work that came out of the 11-round website-to-video
eval branch. The wins that actually moved quality were the artifacts
agents read (contact sheets, design-styles) and the snapshot tool
visual-verification fixes; the rest are smaller follow-ons.

**Contact sheets (`contactSheet.ts`, new)**
- Replaces the embedded one-image-per-asset listing with paginated
  labeled grids (3-col screenshots / 4-col raster / 5-col SVG). Each
  page contains 9–15 cells with filename labels baked in via SVG
  text overlay (`escapeXml` covers `&<>"'`).
- `fit: "contain"` keeps every asset visible at its real aspect
  ratio; the old `fit: "cover"` cropped to the first image's box.
- Returns `string[]` (page paths) — single-page captures get one
  file, multi-page produce `contact-sheet-1.jpg`, `contact-sheet-2.jpg`,
  etc.
- `createSvgContactSheet` scans both `assets/svgs/` (inline-extracted
  SVGs) and `assets/` root (external SVGs from `<img src="*.svg">`)
  and de-dupes by filename. Sites with all-external SVGs (huly.io)
  now get coverage they previously didn't.

**Design styles extractor (`designStyleExtractor.ts`, new)**
- Walks the live DOM and reads computed styles to produce
  `extracted/design-styles.json`: typography hierarchy (every text
  role with exact font-size / weight / line-height / letter-spacing),
  button variants (background / padding / radius / shadow), card /
  container / nav styles, spacing scale with base unit, border-radius
  scale, box-shadow values with usage counts.
- Primary data source for DESIGN.md authoring at Step 1. Replaces
  the prior "guess from screenshots" workflow.

**Snapshot tool (`snapshot.ts`)**
- HyperShader pre-rendering used to swallow the entire snapshot
  capture window (every frame after the first showed the loading
  overlay or final-opacity-zero exit fades). Wait signal is now
  `window.__hf.shaderTransitions[].ready` (set after both warm and
  cold cache paths complete); local-time seek for sub-comps means
  exit fades read at their own t=0..duration, not global time.
- Gemini vision per-frame analysis runs by default (`descriptions.md`
  next to the contact sheet). `--describe "custom Q"` overrides the
  prompt; `--describe false` opts out.
- 3-column contact sheet generation for snapshot frames so reviewers
  see all beats at a glance.

**Screenshot capture (`screenshotCapture.ts`)**
- Replaces `querySelectorAll('*') + getComputedStyle` overlay scan
  with a TreeWalker that early-exits on cheap rect checks before
  reaching the expensive style read. Caps at 5000 elements per page.
- Cookie/consent dismissal selectors are scoped under cookie /
  consent / gdpr ancestors so we don't click "Accept invitation" or
  similar unrelated buttons.

**Agent prompt (`agentPromptGenerator.ts`)**
- Auto-discovers contact-sheet page count (matches base name plus
  paginated `-NNN` variants only, with regex escaping on the base
  name and numeric sort for 10+ pages).
- `inferColorRole`: classifies extracted hex colors as bg-dark /
  bg-light / accent / surface / neutral via luminance + saturation,
  so the agent prompt shows `#533AFD (accent)` instead of bare hex.
- `design-styles.json` row is gated on `existsSync` — the upstream
  write is wrapped in try/catch and may skip on failure, so the
  prompt only points to files actually on disk.

**Other CLI ergonomics**
- `cli.ts`: auto-load `.env` from CWD on startup so subcommands like
  `snapshot` don't need explicit `export GEMINI_API_KEY=…`. Handles
  `export FOO=bar`, quoted values, inline `# comments`.
- `commands/transcribe.ts`: default output dir is the input file's
  directory, not CWD. Stops the "wrote transcript.json somewhere
  unexpected" footgun.
- `assetDownloader.ts`: improved asset naming uses catalog context;
  de-duplicates inline SVG filenames.
- `contentExtractor.ts`: captions SVGs via Gemini (code-as-text) and
  integrates them into asset descriptions.
- `tokenExtractor.ts` + `types.ts`: SVG bounding box dimensions and
  new DesignStyles schema added.
2026-05-21 10:57:37 -07:00
Miguel Ángel afa6530a10 refactor(studio): remove dead crossfade scaffolding from NLEPreview
Follow-up to the playhead-preservation fix: clean up the now-unreachable
crossfade infrastructure that was only triggered by refreshKey changes.

- Remove retiringKey state, retiringTimerRef, handleNewPlayerLoad
- Remove the retiring Player render block and conditional onLoad/style
- Drop refreshKey from getPreviewPlayerKey signature and NLEPreviewProps
- Stop passing refreshKey from NLELayout to NLEPreview
- Update NLELayout comment to reflect current iframe.src reload model
- Simplify getPreviewPlayerKey test
2026-05-21 13:21:10 -04:00
Miguel ÁngelandClaude Sonnet 4.6 dc864f3e6c test(producer): regenerate sub-comp-t0 baseline + improve visual contrast
The text-rendering:geometricPrecision rule (b7bd9565) shifted glyph advances
~1% in headless Chrome. Baselines for png-sequence, heygen-promo-preview-assets,
and sub-composition-video were already regenerated — sub-comp-t0 was missed.

Also improves the fixture's visual contrast:
- Background: #07110d (near-black) → #0f172a (slate-900)
- Hook scene: background #1e293b, text #ef4444/#ffffff
- Later scene: background #334155, text #ffffff
- Absolute positioning for text elements (compiler inlines sub-comp
  content at intrinsic width, collapsing the flex container)

Baseline regenerated inside Dockerfile.test per CLAUDE.md. Test passes:
0 failed frames, 100/100 checkpoints green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 17:19:02 +00:00
Ular Kimsanov 12808fd38f Merge pull request #987 from heygen-com/feat/capture-font-extractor
feat(capture): identify hashed fonts via OpenType name table
2026-05-21 10:09:25 -07:00
Miguel Ángel 1e9a175778 fix(studio): preserve playhead position on composition refresh
Stop NLEPreview from including refreshKey in the Player's React key.
Previously, a refreshKey change caused a full Player teardown + remount,
which destroyed the playback adapter before refreshPlayer() could save
the seek position — so the playhead always reset to 0:00.

Now refreshKey changes only trigger refreshPlayer()'s lightweight
iframe.src reload path, which correctly captures the current time via
saveSeekPosition() before reloading the iframe content.

Closes #996
2026-05-21 12:45:30 -04:00