Commit Graph
848 Commits
Author SHA1 Message Date
Miguel Ángel 3174848d73 chore: release v0.6.0-alpha.10 v0.6.0-alpha.10 2026-05-11 10:58:26 -07:00
Miguel Ángel 0d6ac5dd7f Merge pull request #710 from heygen-com/feat/consolidate-keyboard-shortcuts
feat(studio): consolidate keyboard shortcuts into single handler + fixes alpha issues
2026-05-11 19:55:53 +02:00
Miguel Ángel b7aafbf982 fix(studio): prevent panel scroll when wheel-adjusting metric inputs
React registers onWheel passively, so preventDefault had no effect
on the parent scroll container. Replace with a native wheel listener
(passive: false) that blocks both default scroll and propagation.
2026-05-11 10:52:32 -07:00
Miguel Ángel 2127a48ef9 fix(studio): reorder design panel, fix stroke height, rename Blending
- Move Text section to the top of the panel (before Layout)
- Remove Selection Colors section
- Rename "Blending" to "Transparency"
- Fix stroke Width/Style height mismatch by making SelectField
  use inline label layout matching MetricField
2026-05-11 10:37:32 -07:00
Miguel Ángel 9a1f1bef05 fix(studio): suppress loading overlay on hot-reload
Only show the composition loading overlay on the first iframe load.
Hot-reloads (source editor save, timeline edits, element delete)
no longer flash the full-screen loading state.
2026-05-11 10:28:00 -07:00
Miguel Ángel 48682f5fee fix(core): search inside <template> content when removing elements
linkedom's document.querySelectorAll does not traverse <template>
content. Elements in template-based compositions (like .title-word,
.bullet-text) were invisible to the removal logic, so delete
returned changed: false and the element survived the reload.

Fall back to template.querySelectorAll when the document-level
query returns no matches. Uses template.querySelectorAll directly
(not template.content.querySelectorAll) because removing from
the content DocumentFragment doesn't update the serialized output.
2026-05-11 09:59:44 -07:00
Miguel Ángel a85a92e27f fix(studio): forward all keyboard shortcuts to preview iframe
The consolidated handleAppKeyDown was only added to the parent
window. When focus was inside the preview iframe (after clicking
an element), keydown events didn't reach the parent, so Delete
and other shortcuts didn't fire.

Replace the per-function iframe forwarding (handleTimelineToggleHotkey
only) with the full app-level handler via a ref-stable wrapper.
All app shortcuts (Delete, Undo/Redo, Shift+T, Cmd+1/2) now work
from within the preview iframe.
2026-05-11 09:26:29 -07:00
Miguel Ángel b85d572bee fix(studio): remove unused deleteInFlightRef from Timeline
Leftover from moving Delete handling to the consolidated
keyboard handler in App.tsx. Also suppress pre-existing
exhaustive-deps warning on the intentional every-render
selection-change watcher.
2026-05-11 09:22:50 -07:00
Miguel Ángel d868debe2c fix(studio): delete key removes preview-selected elements
The consolidated keyboard handler only checked selectedElementId
(timeline clips). When a user selected a child element in the
preview via the inspector, selectedElementId was null because
the element didn't correspond to a top-level timeline clip, so
Delete/Backspace did nothing.

Add handleDomEditElementDelete that removes the element referenced
by the current domEditSelection via the remove-element mutation
API. The Delete key handler now falls through from timeline
selection to DOM edit selection.
2026-05-11 08:54:44 -07:00
Miguel ÁngelandClaude Opus 4.6 ae2957d094 fix(studio): sidebar tab overflow + hot-reload double-refresh
1. Sidebar tabs: use equal 1fr columns, shorter "Comps" label, truncate
   on overflow, tighter padding. Fixes tabs clipping outside the rounded
   pill at narrow sidebar widths.

2. Hot reload: set domEditSaveTimestampRef before every save-then-refresh
   path (source editor, timeline move/resize/delete, asset drop). The
   file-change watcher already checks this timestamp and suppresses
   echoed events — but source editor saves and timeline operations
   weren't setting it, causing a double refreshKey increment that could
   leave the player in a non-playable state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-11 14:58:00 +00:00
Miguel ÁngelandClaude Opus 4.6 024467c1f2 feat(studio): consolidate keyboard shortcuts into single handler
Move all window-level keyboard shortcuts from 4 separate files into
one `handleAppKeyDown` listener in App.tsx:

- Shift+T: toggle timeline (was App.tsx, separate useMountEffect)
- Cmd/Ctrl+Z: undo (was App.tsx, separate useEffect)
- Cmd/Ctrl+Shift+Z: redo (was App.tsx, separate useEffect)
- Cmd/Ctrl+1: sidebar Compositions tab (was LeftSidebar.tsx)
- Cmd/Ctrl+2: sidebar Assets tab (was LeftSidebar.tsx)
- Delete/Backspace: remove selected element (was Timeline.tsx)

LeftSidebar exposes a ref handle for tab switching. Timeline watches
selectedElement becoming null to clean up popover/range UI state.
History hotkey kept as named function for iframe forwarding.

Playback shortcuts (Space, J/K/L, arrows) and caption nudge remain
in their component hooks — tightly coupled to component state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-11 01:37:45 +00:00
Miguel Ángel f57b8cfeb4 chore: release v0.6.0-alpha.9 v0.6.0-alpha.9 2026-05-10 20:45:10 +00:00
Miguel Ángel 3b66dc55ec Merge pull request #707 from heygen-com/fix/audio-stutter-comprehensive
fix(runtime): comprehensive audio stutter fix
2026-05-10 22:43:00 +02:00
Miguel Ángel 8fc3dfb370 fix(runtime): skip drift corrections on playing video elements
Seeking a playing video resets the browser's decoder pipeline, causing
a ~150ms freeze while it re-buffers. During that freeze the monotonic
clock advances, drift grows, and strict sync fires another seek —
creating a perpetual stutter loop (176 seek events / 8s observed on
the apple-presentation composition).

Skip strict and force drift corrections for playing video elements;
only hard sync (>0.5s catastrophic drift) warrants the decoder-reset
cost. Audio elements are unaffected and retain the full correction
tiers.

Also propagate the asset-loading overlay state to the timeline so
controls are disabled during "Preparing preview assets", matching the
existing behavior for the initial composition loading overlay.
2026-05-10 13:40:45 -07:00
Miguel ÁngelandClaude Opus 4.6 0f48a32208 fix(runtime): comprehensive audio stutter fix
Three changes that together caused audio play/stop/play/stop stutter
during transport-driven playback:

1. seekRuntimeTimeline called timeline.pause() before every totalTime()
   seek, 60x per second. GSAP cascades pause to media elements on every
   frame. Fix: restore original inline seek for the captured timeline
   (totalTime without pause). The timeline is already paused once in
   player.play(). seekRuntimeTimeline with pause() remains only for
   standalone child timelines.

2. player.play() removed the !tl guard, allowing play without a
   captured timeline. But getSafeTimelineDurationSeconds(null) returns
   0, so the clock has no duration → immediately reaches end → stops →
   restarts. Fix: when no timeline provides duration, fall back to the
   root composition element's data-duration attribute.

3. Audio source attachment added networkState guard that could cause
   the clock to flicker between audio-source and monotonic timing
   on transient media states. Fix: keep !rawEl.error guard (prevents
   errored audio from freezing the clock) but drop the networkState
   check.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-10 19:48:01 +00:00
Miguel Ángel 6ef33be647 chore: release v0.6.0-alpha.8 v0.6.0-alpha.8 2026-05-10 19:42:53 +00:00
Miguel ÁngelandClaude Opus 4.6 152b80f78b fix(studio): restore text field handlers lost in rebase
Restores handleDomAddTextField and handleDomRemoveTextField that were
dropped when resolving App.tsx conflicts during the main→next rebase.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-10 19:42:44 +00:00
Miguel Ángel fcf8c17740 chore: release v0.6.0-alpha.7 v0.6.0-alpha.7 2026-05-10 19:36:07 +00:00
Miguel ÁngelandClaude Opus 4.6 912e981eb1 fix(runtime): remove per-tick timeline.pause() that causes audio stutter
The seekRuntimeTimeline helper added timeline.pause() before every
totalTime() seek. During transport-driven playback, this runs 60 times
per second, causing GSAP to cascade pause events to media elements on
every frame. The result: audio plays/stops/plays/stops in a stutter
pattern.

The captured root timeline is already paused once in player.play() —
the TransportClock drives it via totalTime(t) which keeps it paused.
The extra per-tick pause() was redundant for the root timeline but
actively harmful for media sync.

Fix: restore the original inline seek for the captured timeline
(totalTime without pause), keep seekRuntimeTimeline with pause() only
for standalone child timelines where explicit pause control is needed.

Also fixes rebase artifact: missing PropertyPanel props in App.tsx.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-10 19:35:56 +00:00
Miguel Ángel b68658441b chore: release v0.6.0-alpha.6 v0.6.0-alpha.6 2026-05-10 18:41:10 +00:00
Miguel Ángel 06c12e7b71 chore: release v0.6.0-alpha.5 2026-05-10 18:41:02 +00:00
Miguel ÁngelandClaude Opus 4.6 f37923739d fix(studio): block element selection while composition is loading
Prevent users from selecting elements in the preview while the
composition is still loading (showing "Loading composition" overlay).
Selection and hover highlighting are suppressed until the player fires
the ready event.

Also reverts motion panel and manual drag editing defaults to false —
these were accidentally set to true during the PR #693 merge.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-10 18:41:02 +00:00
Miguel ÁngelandClaude Opus 4.6 4126191db4 fix(runtime): update clock duration when root timeline is late-bound
Compositions with external sub-compositions (like apple-presentation
with 7 slides) load child compositions via fetch(). The root GSAP
timeline is only bound after all external compositions finish loading,
but the TransportClock duration was only set during initial setup.

When bindRootTimelineIfAvailable runs after the external compositions
load, it captures the root timeline but never updates the clock.
player.getDuration() continues returning 0, so the player's probe
interval never fires the 'ready' event, and the Studio shows "Loading
composition" indefinitely.

Now bindRootTimelineIfAvailable updates clock.setDuration when the
root timeline is late-bound. Guarded with try/catch for the early call
site where clock is not yet initialized (temporal dead zone).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-10 18:40:58 +00:00
Miguel Ángel d5ee383ee2 chore: release v0.6.0-alpha.4 2026-05-10 18:40:58 +00:00
Miguel ÁngelandClaude Opus 4.6 70bc81f7c4 fix(studio): thumbnail crash, feature defaults, multi-select UX, fps selector
Power-user audit fixes for the alpha studio:

- vite.config.ts: wrap thumbnail generation in try/catch so Puppeteer
  TimeoutError doesn't crash the entire vite dev server as an uncaught
  rejection. Close the page on error to prevent browser session leaks.

- manualEditingAvailability.ts: enable motion panel and manual canvas
  drag editing by default (were both false, undiscoverable without
  knowing the env vars).

- PropertyPanel.tsx: show "N elements selected" feedback when multiple
  elements are selected instead of the generic "Select an element"
  empty state.

- RenderQueue.tsx + App.tsx: add FPS selector (24/30/60) to the render
  export bar instead of hardcoding 30fps. Pass the user's choice
  through to startRender.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-10 18:40:58 +00:00
Miguel ÁngelandClaude Opus 4.6 6fb15b4c2a fix: alpha preview e2e fixes — exports, init templates, EPIPE crash
Three bugs found via automated e2e testing of the v0.6.0-alpha preview:

1. core: add missing package.json export specifiers for
   studio-api/manual-edits-render-script and
   studio-api/studio-motion-render-script — the alpha.3 npm publish
   failed because the studio build could not resolve these sub-paths.

2. cli: fix init --example creating empty projects — tsup leaves empty
   template directories in dist/ during the build, causing
   existsSync(templateDir) to return true and skip the remote fetch
   fallback. Now checks for index.html inside the dir instead.

3. engine: fix unhandled EPIPE crash in streaming encoder — ffmpeg
   stdin/stdout had no error handlers, so a write after the ffmpeg
   process exits throws an uncaught error that crashes the process.

Verified with 8 consecutive e2e iterations (424 test runs, 0 flaky).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-10 18:40:52 +00:00
Miguel ÁngelandClaude Opus 4.6 5f661b4005 fix(studio): prevent read-only property crash in manual edit wrappers
The seek/play/applyAfter wrapper functions in manualEdits.ts crashed
with "Cannot set property X which has only a getter" when the player
or timeline objects define seek/play as getter-only properties. This
prevented ALL manual edits (position, rotation, size) from persisting
to disk — the error thrown during applyCurrentStudioManualEditsToPreview
aborted the save queue.

Wrapped all three property assignments in try/catch so wrapping
gracefully degrades when the target object is non-configurable.

Verified: position edit (X=42px) now persists to
.hyperframes/studio-manual-edits.json and survives page refresh.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-10 18:40:52 +00:00
Miguel ÁngelandClaude Opus 4.6 706c7992e8 fix(studio): revert motion panel default to false
Motion panel stays opt-in via env var per product direction. Only
the Design panel is enabled by default.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-10 18:40:52 +00:00
Miguel ÁngelandClaude Opus 4.6 a886e70b28 fix(studio): prevent browser launch timeout from crashing dev server
The shared Puppeteer browser pool in getSharedBrowser() could throw a
30s TimeoutError during launch. This error propagated as an uncaught
rejection and killed the vite process, even though generateThumbnail
had its own try/catch — the browser launch promise rejected outside
that scope. Now getSharedBrowser itself catches launch failures and
returns null, so thumbnails degrade gracefully instead of crashing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-10 18:40:52 +00:00
Miguel ÁngelandClaude Opus 4.6 52674dfd86 fix(studio): stale selection, rotation control, design panel polish
Fixes and improvements based on power-user testing feedback:

1. Fix stale selection after style edits — handleDomStyleCommit now
   calls refreshDomEditSelectionFromPreview after persisting, matching
   every other commit handler. Without this, the PropertyPanel showed
   frozen computedStyles after color/radius/shadow edits, making it
   look like editing "didn't work." Also adds error handling around
   the persist call.

2. Add rotation field to the Design panel Layout section — reads the
   current rotation angle from the manual edit manifest and commits
   via the existing handleDomRotationCommit handler.

3. Enable motion panel by default — STUDIO_MOTION_PANEL_ENABLED now
   defaults to true so the Motion tab is discoverable without env vars.

4. Color controls only when element has color — fill color section now
   only shows when the element has an explicit non-transparent
   background-color. Text color shows only when the element has a
   color style. Prevents showing color pickers on elements where
   color edits have no visible effect.

5. Exclude canvas from selection — added "canvas" to
   DOM_LAYER_IGNORED_TAGS so canvas elements are not selectable in the
   preview or listed in the layer panel.

6. Multi-selection feedback — shows "N elements selected" with
   guidance instead of the generic empty state when multiple elements
   are selected.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-10 18:40:52 +00:00
Miguel Ángel 25f60bfb9d fix(studio): handle raster inspector clicks 2026-05-10 18:40:52 +00:00
Miguel Ángel a5434fba06 fix(studio): keep notion preview playback moving 2026-05-10 18:40:52 +00:00
Miguel Ángel 475754ccb5 feat(studio): simplify inspector selection ux 2026-05-10 18:40:43 +00:00
Miguel Ángel c147077b0d chore: release v0.6.0-alpha.3 2026-05-10 18:40:43 +00:00
Miguel Ángel 70fc56387c chore: ignore .claude directory 2026-05-10 18:40:35 +00:00
Miguel Ángel 43734a60f4 feat: disable Studio timeline while composition loads 2026-05-10 18:40:17 +00:00
Miguel Ángel a672d825f4 feat: show composition loading in studio 2026-05-10 18:40:17 +00:00
Miguel Ángel 67867ed965 feat: auto-open timeline clip layers 2026-05-10 18:40:17 +00:00
Miguel Ángel 79b6948996 chore: release v0.6.0-alpha.2 2026-05-10 18:40:17 +00:00
Miguel Ángel 62ee749e67 fix(studio): keep motion panel opt-in 2026-05-10 18:40:17 +00:00
Miguel Ángel e653025a37 feat(studio): enable inspector panels by default 2026-05-10 18:40:17 +00:00
Miguel Ángel 69c8966b40 chore: release v0.6.0-alpha.1 2026-05-10 18:40:17 +00:00
Miguel Ángel defc64e906 feat(studio): add alpha editor layer inspector 2026-05-10 18:40:17 +00:00
Miguel Ángel 99d3fc8099 feat(core): add studio animation preview APIs 2026-05-10 18:40:06 +00:00
Miguel Ángel d4876b4e5b chore: release v0.5.0-alpha.15 2026-05-10 18:39:53 +00:00
Vance Ingalls 073fb74643 fix: share manual edit render runtime 2026-05-10 18:39:45 +00:00
Vance Ingalls 8860695de4 fix: harden manual edit refresh apply 2026-05-10 18:39:14 +00:00
Vance Ingalls 4935ac583d fix: persist manual preview edits on refresh 2026-05-10 18:39:14 +00:00
Vance Ingalls 46df941102 fix: commit drag offsets from gesture math 2026-05-10 18:39:14 +00:00
Vance Ingalls ced335e626 fix: apply nested manual edits in drilled previews 2026-05-10 18:39:14 +00:00