The single-quote replacement regex ran after key-quoting, causing it
to match apostrophes inside already-double-quoted strings like
"didn't". Moving it before key-quoting fixes the issue since it only
matches actual single-quoted string values at that point.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add change detection to 66ms overlay poll (skip setWordBoxes when unchanged)
- Add activating guard to prevent duplicate caption source fetches
- Remove applyOpacityToColor wrapper (inline hexToRgba)
- Rename rawWords back to words in normalize.ts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace doc!/win!/captionSrcPath! with guards in App.tsx async callback
- Move useCallback hooks above early return in CaptionAnimationPanel
(React hooks must be called unconditionally in the same order)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Delete dead useCaptionDrag.ts (superseded by overlay inline drag)
- Self-cancel 200ms poll after caption activation
- Remove stale effectiveStyle deps from applyToIframeDom useCallback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Panel rotation/scale/position changes now go through the wrapper span
using gsap.set, reading current values first to preserve other properties.
Previously set el.style.transform directly on the word span, overwriting
all existing transforms.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The runtime strips data-composition-src after loading, so the DOM query
couldn't find the captions file path. Now uses the compIdToSrc map (built
from raw index.html) as the primary lookup. Also adds compIdToSrc to
the effect dependency array so it re-runs when the map is populated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Reduce poll interval from 500ms to 200ms for faster detection
- Fall back to DOM querySelector for iframe when ref isn't set yet
- Runtime wrapper sets data-caption-wrapper for consistent detection
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Runtime hook's wrapper was missing data-caption-wrapper attribute, so
the overlay created a second wrapper on top. Transforms were applied to
different wrappers causing values to compound on reload.
Also:
- Suppress auto-save during loadOverrides to prevent immediate re-write
- Only sync non-default transform values to avoid spurious overrides
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove store reset from useEffect cleanup — was wiping state on dep changes
and breaking auto-save subscription
- Move panel auto-open into useEffect instead of during render
- Add fallback 500ms poll alongside postMessage listener for initial load
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace polling with a message listener for hf-preview state/timeline
events. These fire after loadExternalCompositions completes, guaranteeing
.caption-group elements exist in the DOM. Eliminates the race condition
on initial page load.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Caption edit mode now activates automatically when .caption-group elements
are found in the iframe DOM. Works from index.html (master) without
requiring drill-down into the captions sub-composition. Finds the captions
source file via data-composition-src attribute.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Caption overlay and hover states always active on captions compositions
- Clicking a word auto-opens the property panel
- Remove Edit Captions button — edit mode activates automatically
- Reset edit mode when navigating away from captions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove typography, color, opacity, stroke, and background controls.
Keep only Position (X/Y) and Transform (Scale/Rotation) — the controls
that work reliably with the wrapper-based override system.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace single "color" with activeColor (spoken) and dimColor (inactive).
Runtime hook modifies existing GSAP color tweens by detecting dim vs active
states via opacity heuristic. Property panel shows both color pickers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
No longer needed — caption overrides are a JSON data file, not HTML patches.
The runtime hook re-applies overrides on every reload, so preview refresh
during editing is safe and ensures the preview stays in sync.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace manual Save button with 800ms debounced auto-save. Writing to
caption-overrides.json is safe because preview refresh is suppressed
during caption edit mode.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- getWordEl skips wrapper spans and finds actual word elements inside them
- readWordBoxes handles wrapped words in position calculations
- getOrCreateWrapper detects if element IS a wrapper to prevent double-wrapping
- Remove debug logging from save
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- applyCaptionOverrides() now runs both when compositions load async AND when
already ready (standalone composition view)
- Overrides file written to project root (not compositions/) to match <base href>
- Remove debug logging
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of killing transform tweens, wrap each overridden word span in an
inline-block wrapper span. Editor transforms (position, scale, rotation)
are applied to the wrapper while the inner span keeps all GSAP entrance,
exit, and karaoke animations intact.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add optional `id` field to transcript Word interface, assigned as `w{index}` during normalization
- caption-overrides.json uses `wordId` (preferred) with `wordIndex` fallback
- Runtime hook calls gsap.killTweensOf for overridden transform properties before applying
- Studio parser carries wordId through from transcript to segments
- Add typecheck to lefthook pre-commit
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New caption-overrides.json format: [{wordIndex, x, y, scale, rotation, color, ...}]
- Runtime hook in packages/core applies overrides via gsap.set() after compositions load
- Studio writes/reads caption-overrides.json instead of injecting code into HTML
- Original composition source is never modified
- Remove old parseExistingOverrides and source patching code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Canvas: move (translate), scale, and rotate handles on selected words via gsap.set()
- Property panel: real-time style updates applied directly to iframe DOM
- Save: patches original source with gsap.killTweensOf + gsap.set per edited word
- Parser: reads existing editor overrides on re-entry to preserve prior edits
- Suppress preview refresh during caption edit mode to prevent reload cycle
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements generateCaptionHtml(model) which serializes a CaptionModel
into a complete captions.html HyperFrames composition with template
wrapper, CSS per-group classes, TRANSCRIPT array, and GSAP timeline
registration via window.__timelines["captions"].
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds `buildCaptionModel` to the caption parser — groups TranscriptWord
arrays into CaptionGroups (default 5 words each), creates CaptionSegments
with correct timing and groupIndex, and wires in DEFAULT_STYLE,
DEFAULT_CONTAINER, and DEFAULT_ANIMATION_SET. Covers 23 tests (13 new).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ConsoleMessage.type() returns "warn" not "warning"
- Type pageerror callback parameter as Error
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Loads a composition in headless Chrome and reports console errors,
uncaught exceptions, and failed network requests for project assets.
Agents run this before opening the studio to catch runtime issues.
- Bundle HTML with inlined runtime (no CDN dependency)
- Minimal file server for project assets
- Captures JS errors, pageerror events, 404s on project files
- JSON output mode for programmatic consumption
- ~3s execution time, suitable as a blocking pre-preview step
Updates CLAUDE.md and compose skill to require lint + validate
before opening the studio.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Vite dev server was loading the HyperFrames runtime IIFE from
cdn.jsdelivr.net. This caused compositions using window.__hyperframes
(e.g., fitTextFontSize) to fail when the CDN script didn't load in
the iframe context.
Add a /api/runtime.js endpoint to the Vite plugin that serves the
local built runtime, matching what studioServer.ts already does for
the CLI embedded server.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>