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>
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>
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>
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>
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>
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>
Per @vai-bot's review on hf#641:
Important #1: dead `src=""` substitution sites
=============================================
Now that `bundleToSingleHtml` inlines the runtime IIFE by default, the empty
`src=""` placeholder is never emitted in the no-env-var path — the 5 downstream
substitution sites that grep for `src=""` were dead.
Two of them (studio dev server + studio vite preview) genuinely WANT the
placeholder so they can hot-reload a local /api/runtime.js endpoint without
re-inlining ~150 KB on every composition edit. Three of them (CLI validate,
snapshot, layout) were just doing the same inlining the bundler already does.
Resolution:
- Add a `runtime: "inline" | "placeholder"` option to `BundleOptions`. Default
is "inline" (matches the self-contained-bundle promise the function name
makes). The two studio surfaces explicitly pass `{ runtime: "placeholder" }`
to opt in.
- studioServer.ts + studio/vite.config.ts: pass the option, keep their
existing string-replace logic unchanged.
- validate.ts + snapshot.ts + layout.ts: delete the now-redundant runtime
substitution code (regex never matches the new inlined-runtime shape).
Important #2: joinJsChunks ASI hazard
======================================
The new helper appended `;` to chunks not already ending in `;` and joined
on `\n`. If a chunk ended with a `// line comment`, the appended semicolon
was eaten by the comment, leaving the next chunk's first statement attached
to the previous chunk's last expression — exactly the ASI hazard the helper
exists to prevent.
Fix: append `\n;` instead of `;` for chunks not already terminated. The
newline closes the line comment, the standalone `;` becomes the statement
separator. For typical chunks (already ending in `;`), output is unchanged
— still clean `\n`-joined chunks with no bare-semicolon lines.
Also added a trailing `;` to `wrapScopedCompositionScript`'s IIFE close
(`})()` → `})();`) so composition scripts join cleanly without falling
through to the `\n;` fallback.
New test: regression guard at the chunk boundary verifies every inline
script body in the bundle parses cleanly via esbuild even when a source JS
file ends with a line comment.
Verification
============
- `bun run --filter @hyperframes/core test` — 653/653 pass
- `bun run --filter @hyperframes/cli test` — 243/243 pass
- `bun run --filter @hyperframes/{core,cli,studio} typecheck` — clean
- `bunx oxfmt --check` + `bunx oxlint` on all touched files — clean
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>