hyperframes 0.7.60 → 0.7.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -686,6 +686,9 @@ function formatFrameTime(time, duration, fps = STUDIO_PREVIEW_FPS) {
686
686
  return `${currentFrame}f / ${totalFrames}f`;
687
687
  }
688
688
 
689
+ // src/player/lib/timelineIframeHelpers.ts
690
+ import { readClipTiming } from "@hyperframes/core/composition-contract";
691
+
689
692
  // src/player/lib/playbackAdapter.ts
690
693
  function isFinitePositive(value) {
691
694
  return Number.isFinite(value) && value > 0;
@@ -1239,95 +1242,130 @@ function stopScrubPreviewAudio() {
1239
1242
  scrubPrevMuted = null;
1240
1243
  scrubPrevVolume = null;
1241
1244
  }
1245
+ function timelineDuration(iframeWin, compositionId) {
1246
+ return iframeWin.__timelines?.[compositionId]?.duration?.() ?? 0;
1247
+ }
1248
+ function createTimedElementLookup(doc) {
1249
+ const timedById = /* @__PURE__ */ new Map();
1250
+ for (const timed of doc.querySelectorAll("[data-start]")) {
1251
+ for (const id of [
1252
+ timed.id,
1253
+ timed.getAttribute("data-hf-id"),
1254
+ timed.getAttribute("data-composition-id")
1255
+ ]) {
1256
+ if (id) timedById.set(id, timed);
1257
+ }
1258
+ }
1259
+ return timedById;
1260
+ }
1261
+ function createReferenceEndResolver(timedById, iframeWin) {
1262
+ const resolveEnd = (refId, visiting) => {
1263
+ if (visiting.has(refId)) return null;
1264
+ const referenced = timedById.get(refId);
1265
+ if (!referenced) return null;
1266
+ const next = new Set(visiting).add(refId);
1267
+ const timing = readClipTiming(referenced, {
1268
+ resolveReferenceEnd: (nestedId) => resolveEnd(nestedId, next)
1269
+ });
1270
+ if (timing.end != null) return timing.end;
1271
+ const compositionId = referenced.getAttribute("data-composition-id");
1272
+ const duration = compositionId ? timelineDuration(iframeWin, compositionId) : 0;
1273
+ return timing.start == null || duration <= 0 ? null : timing.start + duration;
1274
+ };
1275
+ return resolveEnd;
1276
+ }
1277
+ function clampCompositionWindow(start, duration, rootDuration) {
1278
+ if (!Number.isFinite(duration) || duration <= 0) return null;
1279
+ const safeStart = Number.isFinite(start) ? start : 0;
1280
+ if (!Number.isFinite(rootDuration) || rootDuration <= 0) {
1281
+ return { start: safeStart, duration };
1282
+ }
1283
+ if (safeStart >= rootDuration) return null;
1284
+ const clamped = Math.min(duration, Math.max(0, rootDuration - safeStart));
1285
+ return clamped > 0 ? { start: safeStart, duration: clamped } : null;
1286
+ }
1287
+ function nonEmpty(value, fallback) {
1288
+ return value || fallback;
1289
+ }
1290
+ function optionalNonEmpty(value) {
1291
+ return value || void 0;
1292
+ }
1293
+ function attachCompositionSource(entry, element, compositionSrc) {
1294
+ if (compositionSrc) return { ...entry, compositionSrc };
1295
+ const innerVideo = element.querySelector("video[src]");
1296
+ if (!innerVideo) return entry;
1297
+ return { ...entry, src: optionalNonEmpty(innerVideo.getAttribute("src")), tag: "video" };
1298
+ }
1299
+ function buildMissingCompositionEntry(params) {
1300
+ const { doc, iframeWin, element, compositionId, rootDuration, fallbackIndex, resolveEnd } = params;
1301
+ const timing = readClipTiming(element, {
1302
+ resolveReferenceEnd: (refId) => resolveEnd(refId, /* @__PURE__ */ new Set([compositionId]))
1303
+ });
1304
+ const window2 = clampCompositionWindow(
1305
+ timing.start ?? 0,
1306
+ timing.duration ?? timelineDuration(iframeWin, compositionId),
1307
+ rootDuration
1308
+ );
1309
+ if (!window2) return null;
1310
+ const preferredId = nonEmpty(element.id, compositionId);
1311
+ const compositionSrc = element.getAttribute("data-composition-src") ?? element.getAttribute("data-composition-file");
1312
+ const selector = getTimelineElementSelector(element);
1313
+ const sourceFile = getTimelineElementSourceFile(element);
1314
+ const selectorIndex = getTimelineElementSelectorIndex(doc, element, selector);
1315
+ const label = getTimelineElementDisplayLabel({
1316
+ id: preferredId,
1317
+ label: element.getAttribute("data-timeline-label") ?? element.getAttribute("data-label"),
1318
+ tag: element.tagName
1319
+ });
1320
+ const identity = buildTimelineElementIdentity({
1321
+ preferredId,
1322
+ label,
1323
+ fallbackIndex,
1324
+ domId: optionalNonEmpty(element.id),
1325
+ selector,
1326
+ selectorIndex,
1327
+ sourceFile
1328
+ });
1329
+ const entry = {
1330
+ id: identity.id,
1331
+ label,
1332
+ key: identity.key,
1333
+ tag: element.tagName.toLowerCase(),
1334
+ start: window2.start,
1335
+ duration: window2.duration,
1336
+ track: timing.trackIndex,
1337
+ domId: optionalNonEmpty(element.id),
1338
+ hfId: optionalNonEmpty(element.getAttribute("data-hf-id")),
1339
+ selector,
1340
+ selectorIndex,
1341
+ sourceFile,
1342
+ zIndex: readTimelineElementZIndex(element)
1343
+ };
1344
+ return attachCompositionSource(entry, element, compositionSrc);
1345
+ }
1242
1346
  function buildMissingCompositionElements(doc, iframeWin, currentEls, rootDuration) {
1243
1347
  const existingIds = new Set(currentEls.map((e) => e.id));
1244
1348
  const rootComp = doc.querySelector("[data-composition-id]");
1245
1349
  const rootCompId = rootComp?.getAttribute("data-composition-id");
1246
1350
  const hosts = doc.querySelectorAll("[data-composition-id][data-start]");
1247
1351
  const missing = [];
1248
- hosts.forEach((host) => {
1352
+ const resolveEnd = createReferenceEndResolver(createTimedElementLookup(doc), iframeWin);
1353
+ for (const host of hosts) {
1249
1354
  const el = host;
1250
1355
  const compId = el.getAttribute("data-composition-id");
1251
- if (!compId || compId === rootCompId) return;
1252
- if (existingIds.has(el.id) || existingIds.has(compId)) return;
1253
- const startAttr = el.getAttribute("data-start") ?? "0";
1254
- let start = parseFloat(startAttr);
1255
- if (isNaN(start)) {
1256
- const ref = doc.getElementById(startAttr) || doc.querySelector(`[data-composition-id="${CSS.escape(startAttr)}"]`);
1257
- if (ref) {
1258
- const refStartAttr = ref.getAttribute("data-start") ?? "0";
1259
- let refStart = parseFloat(refStartAttr);
1260
- if (isNaN(refStart)) {
1261
- const refRef = doc.getElementById(refStartAttr) || doc.querySelector(`[data-composition-id="${CSS.escape(refStartAttr)}"]`);
1262
- const rrStart = parseFloat(refRef?.getAttribute("data-start") ?? "0") || 0;
1263
- const rrCompId = refRef?.getAttribute("data-composition-id");
1264
- const rrDur = parseFloat(refRef?.getAttribute("data-duration") ?? "") || (rrCompId ? iframeWin.__timelines?.[rrCompId]?.duration?.() ?? 0 : 0);
1265
- refStart = rrStart + rrDur;
1266
- }
1267
- const refCompId = ref.getAttribute("data-composition-id");
1268
- const refDur = parseFloat(ref.getAttribute("data-duration") ?? "") || (refCompId ? iframeWin.__timelines?.[refCompId]?.duration?.() ?? 0 : 0);
1269
- start = refStart + refDur;
1270
- } else {
1271
- start = 0;
1272
- }
1273
- }
1274
- let dur = parseFloat(el.getAttribute("data-duration") ?? "");
1275
- if (isNaN(dur) || dur <= 0) {
1276
- dur = iframeWin.__timelines?.[compId]?.duration?.() ?? 0;
1277
- }
1278
- if (!Number.isFinite(dur) || dur <= 0) return;
1279
- if (!Number.isFinite(start)) start = 0;
1280
- if (Number.isFinite(rootDuration) && rootDuration > 0) {
1281
- if (start >= rootDuration) return;
1282
- dur = Math.min(dur, Math.max(0, rootDuration - start));
1283
- if (dur <= 0) return;
1284
- }
1285
- const trackStr = el.getAttribute("data-track-index");
1286
- const track = trackStr != null ? parseInt(trackStr, 10) : 0;
1287
- const compSrc = el.getAttribute("data-composition-src") || el.getAttribute("data-composition-file");
1288
- const selector = getTimelineElementSelector(el);
1289
- const sourceFile = getTimelineElementSourceFile(el);
1290
- const selectorIndex = getTimelineElementSelectorIndex(doc, el, selector);
1291
- const label = getTimelineElementDisplayLabel({
1292
- id: el.id || compId || null,
1293
- label: el.getAttribute("data-timeline-label") ?? el.getAttribute("data-label"),
1294
- tag: el.tagName
1295
- });
1296
- const identity = buildTimelineElementIdentity({
1297
- preferredId: el.id || compId || null,
1298
- label,
1356
+ if (!compId || compId === rootCompId) continue;
1357
+ if (existingIds.has(el.id) || existingIds.has(compId)) continue;
1358
+ const entry = buildMissingCompositionEntry({
1359
+ doc,
1360
+ iframeWin,
1361
+ element: el,
1362
+ compositionId: compId,
1363
+ rootDuration,
1299
1364
  fallbackIndex: missing.length,
1300
- domId: el.id || void 0,
1301
- selector,
1302
- selectorIndex,
1303
- sourceFile
1365
+ resolveEnd
1304
1366
  });
1305
- const entry = {
1306
- id: identity.id,
1307
- label,
1308
- key: identity.key,
1309
- tag: el.tagName.toLowerCase(),
1310
- start,
1311
- duration: dur,
1312
- track: isNaN(track) ? 0 : track,
1313
- domId: el.id || void 0,
1314
- hfId: el.getAttribute("data-hf-id") || void 0,
1315
- selector,
1316
- selectorIndex,
1317
- sourceFile,
1318
- zIndex: readTimelineElementZIndex(el)
1319
- };
1320
- if (compSrc) {
1321
- entry.compositionSrc = compSrc;
1322
- } else {
1323
- const innerVideo = el.querySelector("video[src]");
1324
- if (innerVideo) {
1325
- entry.src = innerVideo.getAttribute("src") || void 0;
1326
- entry.tag = "video";
1327
- }
1328
- }
1329
- missing.push(entry);
1330
- });
1367
+ if (entry) missing.push(entry);
1368
+ }
1331
1369
  let patched = false;
1332
1370
  const updatedEls = currentEls.map((existing) => {
1333
1371
  if (existing.compositionSrc) return existing;
@@ -3311,6 +3349,7 @@ import { useMemo as useMemo3 } from "react";
3311
3349
 
3312
3350
  // src/player/lib/timelineDOM.ts
3313
3351
  import { resolveCssStackingContextId } from "@hyperframes/core/runtime/stacking-context";
3352
+ import { readClipTiming as readClipTiming2 } from "@hyperframes/core/composition-contract";
3314
3353
  function resolveClipTag(clip) {
3315
3354
  return clip.tagName || clip.kind || "div";
3316
3355
  }
@@ -3466,22 +3505,18 @@ function parseTimelineFromDOM(doc, rootDuration) {
3466
3505
  if (node === rootComp) return;
3467
3506
  if (isTimelineIgnoredElement(node)) return;
3468
3507
  const el = node;
3469
- const startStr = el.getAttribute("data-start");
3470
- if (startStr == null) return;
3471
- const start = parseFloat(startStr);
3472
- if (isNaN(start)) return;
3508
+ const timing = readClipTiming2(el);
3509
+ const start = timing.start;
3510
+ if (start == null) return;
3473
3511
  if (Number.isFinite(rootDuration) && rootDuration > 0 && start >= rootDuration) return;
3474
3512
  const tagLower = el.tagName.toLowerCase();
3475
- let dur = 0;
3476
- const durStr = el.getAttribute("data-duration");
3477
- if (durStr != null) dur = parseFloat(durStr);
3478
- if (isNaN(dur) || dur <= 0) dur = Math.max(0, rootDuration - start);
3513
+ let dur = timing.duration ?? 0;
3514
+ if (dur <= 0) dur = Math.max(0, rootDuration - start);
3479
3515
  if (Number.isFinite(rootDuration) && rootDuration > 0) {
3480
3516
  dur = Math.min(dur, Math.max(0, rootDuration - start));
3481
3517
  }
3482
3518
  if (!Number.isFinite(dur) || dur <= 0) return;
3483
- const trackStr = el.getAttribute("data-track-index");
3484
- const track = trackStr != null ? parseInt(trackStr, 10) : trackCounter++;
3519
+ const track = timing.trackSource === "default" ? trackCounter++ : timing.trackIndex;
3485
3520
  const compId = el.getAttribute("data-composition-id");
3486
3521
  const selector = getTimelineElementSelector(el);
3487
3522
  const sourceFile = getTimelineElementSourceFile(el);
@@ -3507,7 +3542,7 @@ function parseTimelineFromDOM(doc, rootDuration) {
3507
3542
  tag: tagLower,
3508
3543
  start,
3509
3544
  duration: dur,
3510
- track: isNaN(track) ? 0 : track,
3545
+ track,
3511
3546
  domId: el.id || void 0,
3512
3547
  hfId: el.getAttribute("data-hf-id") || void 0,
3513
3548
  selector,
@@ -6498,7 +6533,7 @@ var STUDIO_SDK_RESOLVER_SHADOW_ENABLED = resolveStudioBooleanEnvFlag(
6498
6533
  var STUDIO_FLAT_INSPECTOR_ENABLED = resolveStudioBooleanEnvFlag(
6499
6534
  env,
6500
6535
  ["VITE_STUDIO_ENABLE_FLAT_INSPECTOR", "VITE_STUDIO_FLAT_INSPECTOR_ENABLED"],
6501
- false
6536
+ true
6502
6537
  );
6503
6538
  var STUDIO_MANUAL_EDITING_DISABLED_TITLE = "Manual editing is temporarily disabled";
6504
6539
 
@@ -8765,7 +8800,7 @@ function snapTimelineTime(time, targets, thresholdSecs) {
8765
8800
  }
8766
8801
  return best ? { time: best.time, target: best } : { time, target: null };
8767
8802
  }
8768
- function snapMoveToTargets(start, duration, targets, pixelsPerSecond, timelineDuration) {
8803
+ function snapMoveToTargets(start, duration, targets, pixelsPerSecond, timelineDuration2) {
8769
8804
  if (targets.length === 0) return { start, snapTime: null, snapType: null };
8770
8805
  const thresholdSecs = TIMELINE_SNAP_PX / Math.max(pixelsPerSecond, 1);
8771
8806
  const startSnap = snapTimelineTime(start, targets, thresholdSecs);
@@ -8781,7 +8816,7 @@ function snapMoveToTargets(start, duration, targets, pixelsPerSecond, timelineDu
8781
8816
  candidate = endSnap.time - duration;
8782
8817
  target = endSnap.target;
8783
8818
  }
8784
- const maxStart = Math.max(0, timelineDuration - duration);
8819
+ const maxStart = Math.max(0, timelineDuration2 - duration);
8785
8820
  const roundedCandidate = Math.round(candidate * 1e3) / 1e3;
8786
8821
  const clamped = Math.max(0, Math.min(maxStart, roundedCandidate));
8787
8822
  if (target && Math.abs(clamped - roundedCandidate) > 1e-6) target = null;
@@ -28346,6 +28381,7 @@ function CommitField({
28346
28381
  value,
28347
28382
  disabled,
28348
28383
  liveCommit,
28384
+ align = "left",
28349
28385
  onCommit
28350
28386
  }) {
28351
28387
  const [draft, setDraft] = useState35(value);
@@ -28418,7 +28454,7 @@ function CommitField({
28418
28454
  scheduleCommit(nextDraft);
28419
28455
  },
28420
28456
  title: parseNumericToken(value) ? "Scroll or use Arrow keys to adjust" : void 0,
28421
- className: "min-w-0 w-full bg-transparent text-[11px] font-medium text-neutral-100 outline-none disabled:cursor-not-allowed disabled:text-neutral-600"
28457
+ className: `min-w-0 w-full bg-transparent text-[11px] font-medium text-neutral-100 outline-none disabled:cursor-not-allowed disabled:text-neutral-600 ${align === "right" ? "text-right" : "text-left"}`
28422
28458
  }
28423
28459
  );
28424
28460
  }
@@ -31843,7 +31879,7 @@ function PromotableControl({
31843
31879
  bound && /* @__PURE__ */ jsxs58(
31844
31880
  "span",
31845
31881
  {
31846
- className: "pointer-events-none absolute right-1.5 top-0 z-10 inline-flex max-w-[60%] items-center gap-1 truncate rounded bg-studio-accent/20 px-1 py-px font-mono text-[8px] font-medium text-studio-accent",
31882
+ className: "pointer-events-none absolute -top-2 right-1.5 z-10 inline-flex max-w-[60%] items-center gap-1 truncate rounded bg-studio-accent/20 px-1 py-px font-mono text-[8px] font-medium text-studio-accent",
31847
31883
  title: `Bound to variable "${promote.boundId}"`,
31848
31884
  children: [
31849
31885
  "\u25C6 ",
@@ -31860,7 +31896,7 @@ function PromotableControl({
31860
31896
  e.stopPropagation();
31861
31897
  promote.promote();
31862
31898
  },
31863
- className: "absolute right-1.5 top-0 z-10 inline-flex items-center gap-1 rounded bg-neutral-800/80 px-1 py-px font-mono text-[8px] font-medium text-neutral-400 opacity-70 transition-colors hover:bg-studio-accent/20 hover:text-studio-accent hover:opacity-100",
31899
+ className: "absolute -top-2 right-1.5 z-10 inline-flex items-center gap-1 rounded bg-neutral-800/80 px-1 py-px font-mono text-[8px] font-medium text-neutral-400 opacity-70 transition-colors hover:bg-studio-accent/20 hover:text-studio-accent hover:opacity-100",
31864
31900
  children: "\u25C7 var"
31865
31901
  }
31866
31902
  )
@@ -36274,6 +36310,7 @@ function FlatRow({
36274
36310
  value,
36275
36311
  disabled,
36276
36312
  liveCommit,
36313
+ align: "right",
36277
36314
  onCommit: (nextValue) => {
36278
36315
  track("metric", label);
36279
36316
  onCommit(nextValue);
@@ -36845,7 +36882,7 @@ function FlatTextSection({
36845
36882
  const activeField = textFields.find((field) => field.key === activeFieldKey) ?? textFields[0];
36846
36883
  if (!activeField) return null;
36847
36884
  if (textFields.length > 1) {
36848
- return /* @__PURE__ */ jsxs77("div", { className: "space-y-1.5", children: [
36885
+ return /* @__PURE__ */ jsxs77("div", { className: "space-y-2.5", children: [
36849
36886
  /* @__PURE__ */ jsx90(
36850
36887
  FlatTextLayerList,
36851
36888
  {
@@ -36874,7 +36911,7 @@ function FlatTextSection({
36874
36911
  )
36875
36912
  ] });
36876
36913
  }
36877
- return /* @__PURE__ */ jsxs77("div", { className: "space-y-1.5", children: [
36914
+ return /* @__PURE__ */ jsxs77("div", { className: "space-y-2.5", children: [
36878
36915
  /* @__PURE__ */ jsx90(
36879
36916
  FlatTextFieldEditor,
36880
36917
  {
@@ -36988,17 +37025,6 @@ var STROKE_STYLE_OPTIONS = [
36988
37025
  "inset",
36989
37026
  "outset"
36990
37027
  ];
36991
- function formatStrokeSummary(widthPx, style) {
36992
- return `${widthPx}px ${style}`;
36993
- }
36994
- function parseStrokeSummary(text) {
36995
- const match = /^\s*(-?\d+(?:\.\d+)?)px\s+(\S+)\s*$/.exec(text);
36996
- if (!match) return null;
36997
- const widthPx = Number.parseFloat(match[1]);
36998
- const style = match[2];
36999
- if (!Number.isFinite(widthPx) || !style) return null;
37000
- return { widthPx, style };
37001
- }
37002
37028
 
37003
37029
  // src/components/editor/propertyPanelFlatMaskInsetRows.tsx
37004
37030
  import { Fragment as Fragment28, jsx as jsx91, jsxs as jsxs78 } from "react/jsx-runtime";
@@ -37187,24 +37213,17 @@ function FlatStrokeRow({
37187
37213
  const borderWidthValue = parsePxMetricValue(styles["border-width"] ?? "") ?? parsePxMetricValue(styles["border-top-width"] ?? "") ?? 0;
37188
37214
  const borderStyleValue = styles["border-style"] || styles["border-top-style"] || "none";
37189
37215
  const borderColorValue = styles["border-color"] || styles["border-top-color"] || "rgba(255, 255, 255, 0.18)";
37190
- const summary = formatStrokeSummary(borderWidthValue, borderStyleValue);
37191
- const tier = resolveValueTier(
37192
- styles["border-width"] != null || styles["border-style"] != null ? summary : void 0,
37193
- formatStrokeSummary(0, "none")
37194
- );
37216
+ const widthDisplay = formatPxMetricValue(borderWidthValue);
37195
37217
  return /* @__PURE__ */ jsxs79(Fragment29, { children: [
37196
37218
  /* @__PURE__ */ jsx92(
37197
37219
  FlatRow,
37198
37220
  {
37199
- label: "Stroke",
37200
- value: summary,
37201
- tier,
37221
+ label: "Stroke width",
37222
+ value: widthDisplay,
37223
+ tier: resolveValueTier(styles["border-width"], "0px"),
37202
37224
  disabled,
37203
37225
  onCommit: async (next) => {
37204
- const parsed = parseStrokeSummary(next);
37205
- if (!parsed) return;
37206
- if (!STROKE_STYLE_OPTIONS.includes(parsed.style)) return;
37207
- const normalizedWidth = normalizePanelPxValue(`${parsed.widthPx}px`, {
37226
+ const normalizedWidth = normalizePanelPxValue(next, {
37208
37227
  min: 0,
37209
37228
  max: 200,
37210
37229
  fallback: borderWidthValue
@@ -37212,24 +37231,11 @@ function FlatStrokeRow({
37212
37231
  if (!normalizedWidth) return;
37213
37232
  for (const [property, value] of buildStrokeWidthStyleUpdates(
37214
37233
  normalizedWidth,
37215
- parsed.style
37234
+ borderStyleValue
37216
37235
  )) {
37217
37236
  await onSetStyle(property, value);
37218
37237
  }
37219
- for (const [property, value] of buildStrokeStyleUpdates(parsed.style, normalizedWidth)) {
37220
- await onSetStyle(property, value);
37221
- }
37222
- },
37223
- suffix: /* @__PURE__ */ jsxs79(Fragment29, { children: [
37224
- /* @__PURE__ */ jsx92(
37225
- "span",
37226
- {
37227
- className: "h-4 w-4 flex-shrink-0 rounded border border-panel-border-input",
37228
- style: { backgroundColor: borderColorValue }
37229
- }
37230
- ),
37231
- /* @__PURE__ */ jsx92("span", { className: "font-mono text-[10px] text-panel-text-3", children: borderColorValue })
37232
- ] })
37238
+ }
37233
37239
  }
37234
37240
  ),
37235
37241
  /* @__PURE__ */ jsx92(
@@ -39114,49 +39120,51 @@ function PropertyPanelFlat({
39114
39120
  )
39115
39121
  });
39116
39122
  }
39117
- groups.push({
39118
- id: "layout",
39119
- title: "Layout",
39120
- // No scrub accessory: FlatRow/CommitField has no pointer-drag scrubbing
39121
- // (wheel/arrow keys only) advertising "drag values to scrub" here lies.
39122
- summary: `${formatPxMetricValue(displayX)},${formatPxMetricValue(displayY)} \xB7 ${Math.round(displayW)}\xD7${Math.round(displayH)}`,
39123
- content: /* @__PURE__ */ jsx99(
39124
- FlatLayoutSection,
39125
- {
39126
- element,
39127
- styles,
39128
- onSetStyle,
39129
- disabled: !element.capabilities.canEditStyles,
39130
- displayX,
39131
- displayY,
39132
- displayW,
39133
- displayH,
39134
- displayR,
39135
- manualOffsetEditingDisabled,
39136
- manualSizeEditingDisabled,
39137
- manualRotationEditingDisabled,
39138
- commitManualOffset,
39139
- commitManualSize,
39140
- commitManualRotation,
39141
- gsapAnimId,
39142
- navKeyframes,
39143
- currentPct,
39144
- seekFromKfPct,
39145
- animIdForProp,
39146
- resolveAnimIdForProp: animIdForProp,
39147
- gsapRuntimeValues,
39148
- gsapKeyframes: navKeyframes,
39149
- elStart,
39150
- elDuration,
39151
- onCommitAnimatedProperty,
39152
- onCommitAnimatedProperties,
39153
- onSeekToTime,
39154
- onRemoveKeyframe,
39155
- onConvertToKeyframes,
39156
- onLivePreviewProps: createGsapLivePreview(previewIframeRef ?? { current: null })
39157
- }
39158
- )
39159
- });
39123
+ if (sections.layout) {
39124
+ groups.push({
39125
+ id: "layout",
39126
+ title: "Layout",
39127
+ // No scrub accessory: FlatRow/CommitField has no pointer-drag scrubbing
39128
+ // (wheel/arrow keys only) advertising "drag values to scrub" here lies.
39129
+ summary: `${formatPxMetricValue(displayX)},${formatPxMetricValue(displayY)} \xB7 ${Math.round(displayW)}\xD7${Math.round(displayH)}`,
39130
+ content: /* @__PURE__ */ jsx99(
39131
+ FlatLayoutSection,
39132
+ {
39133
+ element,
39134
+ styles,
39135
+ onSetStyle,
39136
+ disabled: !element.capabilities.canEditStyles,
39137
+ displayX,
39138
+ displayY,
39139
+ displayW,
39140
+ displayH,
39141
+ displayR,
39142
+ manualOffsetEditingDisabled,
39143
+ manualSizeEditingDisabled,
39144
+ manualRotationEditingDisabled,
39145
+ commitManualOffset,
39146
+ commitManualSize,
39147
+ commitManualRotation,
39148
+ gsapAnimId,
39149
+ navKeyframes,
39150
+ currentPct,
39151
+ seekFromKfPct,
39152
+ animIdForProp,
39153
+ resolveAnimIdForProp: animIdForProp,
39154
+ gsapRuntimeValues,
39155
+ gsapKeyframes: navKeyframes,
39156
+ elStart,
39157
+ elDuration,
39158
+ onCommitAnimatedProperty,
39159
+ onCommitAnimatedProperties,
39160
+ onSeekToTime,
39161
+ onRemoveKeyframe,
39162
+ onConvertToKeyframes,
39163
+ onLivePreviewProps: createGsapLivePreview(previewIframeRef ?? { current: null })
39164
+ }
39165
+ )
39166
+ });
39167
+ }
39160
39168
  if (showMotionGroup) {
39161
39169
  groups.push({
39162
39170
  id: "motion",
@@ -39623,8 +39631,8 @@ var PropertyPanel = memo31(function PropertyPanel2(props) {
39623
39631
  const manualSizeEditingDisabled = !element.capabilities.canApplyManualSize;
39624
39632
  const manualRotationEditingDisabled = !element.capabilities.canApplyManualRotation;
39625
39633
  const sourceLabel = element.id ? `#${element.id}` : element.selector ?? "";
39626
- const showEditableSections = element.capabilities.canEditStyles;
39627
39634
  const sections = resolveEditingSections(domEditSelectionToFacts(element, gsapAnimations.length));
39635
+ const showEditableSections = element.capabilities.canEditStyles && sections.style;
39628
39636
  const manualOffset = readStudioPathOffset(element.element);
39629
39637
  const manualSize = readStudioBoxSize(element.element);
39630
39638
  const resolvedWidth = manualSize.width > 0 ? manualSize.width : parsePxMetricValue(styles.width ?? "") ?? element.boundingBox.width;
@@ -39788,7 +39796,7 @@ var PropertyPanel = memo31(function PropertyPanel2(props) {
39788
39796
  onRemoveBackground
39789
39797
  }
39790
39798
  ),
39791
- /* @__PURE__ */ jsxs89(Section, { title: "Layout", icon: /* @__PURE__ */ jsx102(Move, { size: 15 }), children: [
39799
+ sections.layout && /* @__PURE__ */ jsxs89(Section, { title: "Layout", icon: /* @__PURE__ */ jsx102(Move, { size: 15 }), children: [
39792
39800
  /* @__PURE__ */ jsxs89("div", { className: RESPONSIVE_GRID, children: [
39793
39801
  /* @__PURE__ */ jsxs89("div", { className: "flex items-center gap-1", children: [
39794
39802
  /* @__PURE__ */ jsx102("div", { className: "flex-1", children: /* @__PURE__ */ jsx102(
@@ -40788,7 +40796,7 @@ var FileTree = memo33(function FileTree2({
40788
40796
  });
40789
40797
 
40790
40798
  // src/App.tsx
40791
- import { useState as useState119, useCallback as useCallback141, useRef as useRef119, useMemo as useMemo50, useEffect as useEffect100, useLayoutEffect as useLayoutEffect4 } from "react";
40799
+ import { useState as useState120, useCallback as useCallback142, useRef as useRef120, useMemo as useMemo50, useEffect as useEffect102, useLayoutEffect as useLayoutEffect4 } from "react";
40792
40800
 
40793
40801
  // src/components/renders/useRenderQueue.ts
40794
40802
  import { useState as useState64, useEffect as useEffect52, useCallback as useCallback58, useRef as useRef63, useMemo as useMemo31 } from "react";
@@ -50811,8 +50819,20 @@ function useBlockHandlers({
50811
50819
  };
50812
50820
  }
50813
50821
 
50822
+ // src/hooks/useAddAssetAtPlayhead.ts
50823
+ import { useCallback as useCallback97 } from "react";
50824
+ function useAddAssetAtPlayhead(handleTimelineAssetDrop) {
50825
+ return useCallback97(
50826
+ (assetPath) => handleTimelineAssetDrop(assetPath, {
50827
+ start: usePlayerStore.getState().currentTime,
50828
+ track: 0
50829
+ }),
50830
+ [handleTimelineAssetDrop]
50831
+ );
50832
+ }
50833
+
50814
50834
  // src/hooks/useAppHotkeys.ts
50815
- import { useCallback as useCallback97, useEffect as useEffect66, useRef as useRef90 } from "react";
50835
+ import { useCallback as useCallback98, useEffect as useEffect66, useRef as useRef90 } from "react";
50816
50836
  function iframeContentWindow(iframe) {
50817
50837
  try {
50818
50838
  return iframe?.contentWindow ?? null;
@@ -51022,22 +51042,22 @@ function useAppHotkeys({
51022
51042
  }) {
51023
51043
  const previewHotkeyWindowRef = useRef90(null);
51024
51044
  const previewHistoryCleanupRef = useRef90(null);
51025
- const readHistoryFile = useCallback97(
51045
+ const readHistoryFile = useCallback98(
51026
51046
  (path) => path === STUDIO_MOTION_PATH ? readOptionalProjectFile(path) : readProjectFile(path),
51027
51047
  [readOptionalProjectFile, readProjectFile]
51028
51048
  );
51029
- const writeHistoryFile = useCallback97(
51049
+ const writeHistoryFile = useCallback98(
51030
51050
  async (path, content) => {
51031
51051
  domEditSaveTimestampRef.current = Date.now();
51032
51052
  await writeProjectFile(path, content);
51033
51053
  },
51034
51054
  [domEditSaveTimestampRef, writeProjectFile]
51035
51055
  );
51036
- const serializeHistoryFiles = useCallback97(
51056
+ const serializeHistoryFiles = useCallback98(
51037
51057
  (paths, task) => serializeStudioFileMutations(writeProjectFile, paths, task),
51038
51058
  [writeProjectFile]
51039
51059
  );
51040
- const applyHistory = useCallback97(
51060
+ const applyHistory = useCallback98(
51041
51061
  async (direction) => {
51042
51062
  if (tryApplyBeatHistory(direction, editHistory.state, showToast)) return;
51043
51063
  await waitForPendingDomEditSaves();
@@ -51075,8 +51095,8 @@ function useAppHotkeys({
51075
51095
  forceReloadSdkSession
51076
51096
  ]
51077
51097
  );
51078
- const handleUndo = useCallback97(() => applyHistory("undo"), [applyHistory]);
51079
- const handleRedo = useCallback97(() => applyHistory("redo"), [applyHistory]);
51098
+ const handleUndo = useCallback98(() => applyHistory("undo"), [applyHistory]);
51099
+ const handleRedo = useCallback98(() => applyHistory("redo"), [applyHistory]);
51080
51100
  const cbRef = useRef90(null);
51081
51101
  cbRef.current = {
51082
51102
  handleTimelineElementDelete,
@@ -51096,7 +51116,7 @@ function useAppHotkeys({
51096
51116
  domEditSelectionRef,
51097
51117
  showToast
51098
51118
  };
51099
- const handleAppKeyDown = useCallback97((event) => {
51119
+ const handleAppKeyDown = useCallback98((event) => {
51100
51120
  const cb = cbRef.current;
51101
51121
  const key = event.key.toLowerCase();
51102
51122
  if (event.metaKey || event.ctrlKey) {
@@ -51109,7 +51129,7 @@ function useAppHotkeys({
51109
51129
  window.addEventListener("keydown", handleAppKeyDown, true);
51110
51130
  return () => window.removeEventListener("keydown", handleAppKeyDown, true);
51111
51131
  }, [handleAppKeyDown]);
51112
- const syncPreviewTimelineHotkey = useCallback97(
51132
+ const syncPreviewTimelineHotkey = useCallback98(
51113
51133
  (iframe) => {
51114
51134
  const nextWindow = iframeContentWindow(iframe);
51115
51135
  if (previewHotkeyWindowRef.current === nextWindow) return;
@@ -51134,7 +51154,7 @@ function useAppHotkeys({
51134
51154
  },
51135
51155
  [handleAppKeyDown]
51136
51156
  );
51137
- const handleHistoryHotkey = useCallback97((event) => {
51157
+ const handleHistoryHotkey = useCallback98((event) => {
51138
51158
  if (!(event.metaKey || event.ctrlKey) || shouldIgnoreHistoryShortcut(event.target)) return;
51139
51159
  handleUndoRedoKey(
51140
51160
  event,
@@ -51142,7 +51162,7 @@ function useAppHotkeys({
51142
51162
  () => void cbRef.current.handleRedo()
51143
51163
  );
51144
51164
  }, []);
51145
- const syncPreviewHistoryHotkey = useCallback97(
51165
+ const syncPreviewHistoryHotkey = useCallback98(
51146
51166
  (iframe) => {
51147
51167
  previewHistoryCleanupRef.current?.();
51148
51168
  previewHistoryCleanupRef.current = null;
@@ -51180,7 +51200,7 @@ function useAppHotkeys({
51180
51200
  }
51181
51201
 
51182
51202
  // src/hooks/useClipboard.ts
51183
- import { useCallback as useCallback98, useRef as useRef91 } from "react";
51203
+ import { useCallback as useCallback99, useRef as useRef91 } from "react";
51184
51204
 
51185
51205
  // src/utils/clipboardPayload.ts
51186
51206
  function insertAsSibling(source, newHtml, selector, selectorIndex) {
@@ -51290,7 +51310,7 @@ function useClipboard({
51290
51310
  const clipboardRef = useRef91(null);
51291
51311
  const projectIdRef = useRef91(projectId);
51292
51312
  projectIdRef.current = projectId;
51293
- const handleCopy = useCallback98(() => {
51313
+ const handleCopy = useCallback99(() => {
51294
51314
  const { selectedElementId, elements } = usePlayerStore.getState();
51295
51315
  if (selectedElementId) {
51296
51316
  const element = elements.find((el) => (el.key ?? el.id) === selectedElementId);
@@ -51344,7 +51364,7 @@ function useClipboard({
51344
51364
  }
51345
51365
  return false;
51346
51366
  }, [activeCompPath, domEditSelectionRef, previewIframeRef, showToast]);
51347
- const handlePaste = useCallback98(async () => {
51367
+ const handlePaste = useCallback99(async () => {
51348
51368
  const payload = clipboardRef.current;
51349
51369
  if (!payload) {
51350
51370
  showToast("Nothing to paste.", "info");
@@ -51400,7 +51420,7 @@ function useClipboard({
51400
51420
  showToast,
51401
51421
  writeProjectFile
51402
51422
  ]);
51403
- const handleCut = useCallback98(async () => {
51423
+ const handleCut = useCallback99(async () => {
51404
51424
  const copied = handleCopy();
51405
51425
  if (!copied) return false;
51406
51426
  const { selectedElementId, elements } = usePlayerStore.getState();
@@ -51762,11 +51782,11 @@ function useCaptionDetection({
51762
51782
  }
51763
51783
 
51764
51784
  // src/hooks/useRenderClipContent.ts
51765
- import { useCallback as useCallback101 } from "react";
51785
+ import { useCallback as useCallback102 } from "react";
51766
51786
  import { createElement as createElement2 } from "react";
51767
51787
 
51768
51788
  // src/player/components/AudioWaveform.tsx
51769
- import { memo as memo34, useRef as useRef92, useState as useState77, useCallback as useCallback99, useEffect as useEffect68 } from "react";
51789
+ import { memo as memo34, useRef as useRef92, useState as useState77, useCallback as useCallback100, useEffect as useEffect68 } from "react";
51770
51790
  import { jsx as jsx112, jsxs as jsxs98 } from "react/jsx-runtime";
51771
51791
  var BAR_W = 2;
51772
51792
  var GAP = 1;
@@ -51843,7 +51863,7 @@ var AudioWaveform = memo34(function AudioWaveform2({
51843
51863
  cancelled = true;
51844
51864
  };
51845
51865
  }, [audioUrl, waveformUrl, cacheKey, peaks]);
51846
- const draw = useCallback99(() => {
51866
+ const draw = useCallback100(() => {
51847
51867
  const container = containerRef.current;
51848
51868
  const barsEl = barsRef.current;
51849
51869
  if (!container || !barsEl || !peaks) return;
@@ -51864,7 +51884,7 @@ var AudioWaveform = memo34(function AudioWaveform2({
51864
51884
  }
51865
51885
  barsEl.innerHTML = html2;
51866
51886
  }, [peaks, trimStartFraction, trimEndFraction]);
51867
- const setContainerRef = useCallback99(
51887
+ const setContainerRef = useCallback100(
51868
51888
  (el) => {
51869
51889
  roRef.current?.disconnect();
51870
51890
  containerRef.current = el;
@@ -51908,7 +51928,7 @@ var AudioWaveform = memo34(function AudioWaveform2({
51908
51928
  });
51909
51929
 
51910
51930
  // src/player/components/ImageThumbnail.tsx
51911
- import { memo as memo35, useRef as useRef93, useState as useState78, useCallback as useCallback100, useEffect as useEffect69 } from "react";
51931
+ import { memo as memo35, useRef as useRef93, useState as useState78, useCallback as useCallback101, useEffect as useEffect69 } from "react";
51912
51932
  import { jsx as jsx113, jsxs as jsxs99 } from "react/jsx-runtime";
51913
51933
  var ImageThumbnail = memo35(function ImageThumbnail2({
51914
51934
  imageSrc,
@@ -51921,7 +51941,7 @@ var ImageThumbnail = memo35(function ImageThumbnail2({
51921
51941
  const [aspect, setAspect] = useState78(16 / 9);
51922
51942
  const ioRef = useRef93(null);
51923
51943
  const roRef = useRef93(null);
51924
- const setContainerRef = useCallback100((el) => {
51944
+ const setContainerRef = useCallback101((el) => {
51925
51945
  ioRef.current?.disconnect();
51926
51946
  roRef.current?.disconnect();
51927
51947
  if (!el) return;
@@ -52074,7 +52094,7 @@ function useRenderClipContent({
52074
52094
  activePreviewUrl,
52075
52095
  effectiveTimelineDuration
52076
52096
  }) {
52077
- return useCallback101(
52097
+ return useCallback102(
52078
52098
  // Pre-existing clip-content dispatcher; reduced by extracting renderAudioClip.
52079
52099
  // fallow-ignore-next-line complexity
52080
52100
  (el, style) => {
@@ -52146,11 +52166,11 @@ function useRenderClipContent({
52146
52166
  }
52147
52167
 
52148
52168
  // src/hooks/useConsoleErrorCapture.ts
52149
- import { useCallback as useCallback102, useEffect as useEffect70, useRef as useRef94, useState as useState79 } from "react";
52169
+ import { useCallback as useCallback103, useEffect as useEffect70, useRef as useRef94, useState as useState79 } from "react";
52150
52170
  function useConsoleErrorCapture(previewIframe) {
52151
52171
  const [consoleErrors, setConsoleErrors] = useState79(null);
52152
52172
  const consoleErrorsRef = useRef94([]);
52153
- const resetErrors = useCallback102(() => {
52173
+ const resetErrors = useCallback103(() => {
52154
52174
  consoleErrorsRef.current = [];
52155
52175
  setConsoleErrors(null);
52156
52176
  }, []);
@@ -52219,7 +52239,7 @@ function useConsoleErrorCapture(previewIframe) {
52219
52239
  }
52220
52240
 
52221
52241
  // src/hooks/useFrameCapture.ts
52222
- import { useState as useState80, useCallback as useCallback103, useRef as useRef95 } from "react";
52242
+ import { useState as useState80, useCallback as useCallback104, useRef as useRef95 } from "react";
52223
52243
 
52224
52244
  // src/utils/projectRouting.ts
52225
52245
  var PROJECT_HASH_PREFIX = "#project/";
@@ -52308,10 +52328,10 @@ function useFrameCapture({
52308
52328
  setCaptureFrameTime(usePlayerStore.getState().currentTime);
52309
52329
  return liveTime.subscribe(setCaptureFrameTime);
52310
52330
  });
52311
- const refreshCaptureFrameTime = useCallback103(() => {
52331
+ const refreshCaptureFrameTime = useCallback104(() => {
52312
52332
  setCaptureFrameTime(usePlayerStore.getState().currentTime);
52313
52333
  }, []);
52314
- const handleCaptureFrameClick = useCallback103(
52334
+ const handleCaptureFrameClick = useCallback104(
52315
52335
  async (event) => {
52316
52336
  if (!projectId) return;
52317
52337
  event.preventDefault();
@@ -52388,7 +52408,7 @@ function useFrameCapture({
52388
52408
  }
52389
52409
 
52390
52410
  // src/hooks/useLintModal.ts
52391
- import { useState as useState81, useCallback as useCallback104, useEffect as useEffect71, useRef as useRef96, useMemo as useMemo37 } from "react";
52411
+ import { useState as useState81, useCallback as useCallback105, useEffect as useEffect71, useRef as useRef96, useMemo as useMemo37 } from "react";
52392
52412
  function parseFinding(f) {
52393
52413
  return {
52394
52414
  severity: f.severity === "error" ? "error" : "warning",
@@ -52403,7 +52423,7 @@ function useLintModal(projectId, refreshKey) {
52403
52423
  const [linting, setLinting] = useState81(false);
52404
52424
  const [backgroundFindings, setBackgroundFindings] = useState81([]);
52405
52425
  const autoLintRanRef = useRef96(false);
52406
- const runLint = useCallback104(
52426
+ const runLint = useCallback105(
52407
52427
  async (opts) => {
52408
52428
  if (!projectId) return;
52409
52429
  if (!opts?.background) setLinting(true);
@@ -52428,7 +52448,7 @@ function useLintModal(projectId, refreshKey) {
52428
52448
  },
52429
52449
  [projectId]
52430
52450
  );
52431
- const handleLint = useCallback104(() => runLint(), [runLint]);
52451
+ const handleLint = useCallback105(() => runLint(), [runLint]);
52432
52452
  const prevProjectIdRef = useRef96(projectId);
52433
52453
  useEffect71(() => {
52434
52454
  if (projectId !== prevProjectIdRef.current) {
@@ -52444,8 +52464,8 @@ function useLintModal(projectId, refreshKey) {
52444
52464
  const timer = setTimeout(() => void runLint({ background: true }), 1e3);
52445
52465
  return () => clearTimeout(timer);
52446
52466
  }, [projectId, refreshKey, runLint]);
52447
- const closeLintModal = useCallback104(() => setLintModal(null), []);
52448
- const groupFindings = useCallback104(
52467
+ const closeLintModal = useCallback105(() => setLintModal(null), []);
52468
+ const groupFindings = useCallback105(
52449
52469
  (keyFn) => {
52450
52470
  const map = /* @__PURE__ */ new Map();
52451
52471
  for (const f of backgroundFindings) {
@@ -52477,7 +52497,7 @@ function useLintModal(projectId, refreshKey) {
52477
52497
  }
52478
52498
 
52479
52499
  // src/hooks/useToast.ts
52480
- import { useState as useState82, useCallback as useCallback105, useRef as useRef97 } from "react";
52500
+ import { useState as useState82, useCallback as useCallback106, useRef as useRef97 } from "react";
52481
52501
  var AUTO_DISMISS_MS2 = 4e3;
52482
52502
  var EXIT_MS = 160;
52483
52503
  var MAX_TOASTS = 3;
@@ -52485,21 +52505,21 @@ var nextToastId = 1;
52485
52505
  function useToast() {
52486
52506
  const [toasts, setToasts] = useState82([]);
52487
52507
  const timersRef = useRef97(/* @__PURE__ */ new Map());
52488
- const clearTimer = useCallback105((id) => {
52508
+ const clearTimer = useCallback106((id) => {
52489
52509
  const timer = timersRef.current.get(id);
52490
52510
  if (timer) {
52491
52511
  clearTimeout(timer);
52492
52512
  timersRef.current.delete(id);
52493
52513
  }
52494
52514
  }, []);
52495
- const removeToast = useCallback105(
52515
+ const removeToast = useCallback106(
52496
52516
  (id) => {
52497
52517
  clearTimer(id);
52498
52518
  setToasts((prev) => prev.filter((t) => t.id !== id));
52499
52519
  },
52500
52520
  [clearTimer]
52501
52521
  );
52502
- const dismissToast = useCallback105(
52522
+ const dismissToast = useCallback106(
52503
52523
  (id) => {
52504
52524
  clearTimer(id);
52505
52525
  setToasts((prev) => prev.map((t) => t.id === id ? { ...t, leaving: true } : t));
@@ -52508,7 +52528,7 @@ function useToast() {
52508
52528
  },
52509
52529
  [clearTimer, removeToast]
52510
52530
  );
52511
- const showToast = useCallback105(
52531
+ const showToast = useCallback106(
52512
52532
  (message, tone = "error") => {
52513
52533
  const id = nextToastId++;
52514
52534
  setToasts((prev) => {
@@ -52534,14 +52554,14 @@ function useToast() {
52534
52554
  }
52535
52555
 
52536
52556
  // src/hooks/useCompositionContentLoader.ts
52537
- import { useCallback as useCallback106 } from "react";
52557
+ import { useCallback as useCallback107 } from "react";
52538
52558
  function useCompositionContentLoader({
52539
52559
  projectId,
52540
52560
  setEditingFile,
52541
52561
  setActiveCompPath,
52542
52562
  showToast
52543
52563
  }) {
52544
- return useCallback106(
52564
+ return useCallback107(
52545
52565
  (comp) => {
52546
52566
  setActiveCompPath(comp.endsWith(".html") ? comp : null);
52547
52567
  setEditingFile({ path: comp, content: null });
@@ -52560,7 +52580,7 @@ function useCompositionContentLoader({
52560
52580
  }
52561
52581
 
52562
52582
  // src/hooks/useStudioUrlState.ts
52563
- import { useCallback as useCallback107, useEffect as useEffect72, useRef as useRef98, useState as useState83 } from "react";
52583
+ import { useCallback as useCallback108, useEffect as useEffect72, useRef as useRef98, useState as useState83 } from "react";
52564
52584
 
52565
52585
  // src/utils/studioUrlState.ts
52566
52586
  var VALID_TABS = ["layers", "design", "renders", "slideshow", "variables"];
@@ -52693,7 +52713,7 @@ function useStudioUrlState({
52693
52713
  const [selectionHydrated, setSelectionHydrated] = useState83(initialState2.selection == null);
52694
52714
  const pendingSelectionRef = useRef98(initialState2.selection);
52695
52715
  const stableTimeRef = useRef98(initialState2.currentTime);
52696
- const buildUrlState = useCallback107(
52716
+ const buildUrlState = useCallback108(
52697
52717
  () => ({
52698
52718
  activeCompPath,
52699
52719
  currentTime: stableTimeRef.current,
@@ -52704,7 +52724,7 @@ function useStudioUrlState({
52704
52724
  }),
52705
52725
  [activeCompPath, domEditSelection, rightCollapsed, rightPanelTab]
52706
52726
  );
52707
- const applyUrlSelection = useCallback107(
52727
+ const applyUrlSelection = useCallback108(
52708
52728
  (selection) => {
52709
52729
  if (!selection) {
52710
52730
  applyDomSelection(null, { revealPanel: false });
@@ -52820,7 +52840,7 @@ function useStudioUrlState({
52820
52840
  }
52821
52841
 
52822
52842
  // src/hooks/useStudioContextValue.ts
52823
- import { useCallback as useCallback108, useMemo as useMemo38, useRef as useRef99, useState as useState84 } from "react";
52843
+ import { useCallback as useCallback109, useMemo as useMemo38, useRef as useRef99, useState as useState84 } from "react";
52824
52844
  function buildStudioContextValue(input) {
52825
52845
  return {
52826
52846
  projectId: input.projectId,
@@ -52866,21 +52886,21 @@ function useInspectorState(rightPanelTab, rightInspectorPanes, rightCollapsed, i
52866
52886
  function useDragOverlay(onImportFiles) {
52867
52887
  const [active, setActive] = useState84(false);
52868
52888
  const counterRef = useRef99(0);
52869
- const onDragOver = useCallback108((e) => {
52889
+ const onDragOver = useCallback109((e) => {
52870
52890
  if (!e.dataTransfer.types.includes("Files")) return;
52871
52891
  e.preventDefault();
52872
52892
  }, []);
52873
- const onDragEnter = useCallback108((e) => {
52893
+ const onDragEnter = useCallback109((e) => {
52874
52894
  if (!e.dataTransfer.types.includes("Files")) return;
52875
52895
  e.preventDefault();
52876
52896
  counterRef.current++;
52877
52897
  setActive(true);
52878
52898
  }, []);
52879
- const onDragLeave = useCallback108(() => {
52899
+ const onDragLeave = useCallback109(() => {
52880
52900
  counterRef.current--;
52881
52901
  if (counterRef.current === 0) setActive(false);
52882
52902
  }, []);
52883
- const onDrop = useCallback108(
52903
+ const onDrop = useCallback109(
52884
52904
  (e) => {
52885
52905
  counterRef.current = 0;
52886
52906
  setActive(false);
@@ -52893,7 +52913,7 @@ function useDragOverlay(onImportFiles) {
52893
52913
  return { active, onDragOver, onDragEnter, onDragLeave, onDrop };
52894
52914
  }
52895
52915
  function useGlobalFileDrop(handleTimelineFileDrop) {
52896
- const onDrop = useCallback108(
52916
+ const onDrop = useCallback109(
52897
52917
  (files) => {
52898
52918
  const start = usePlayerStore.getState().currentTime;
52899
52919
  void handleTimelineFileDrop(Array.from(files), { start, track: 0 });
@@ -52904,7 +52924,7 @@ function useGlobalFileDrop(handleTimelineFileDrop) {
52904
52924
  }
52905
52925
 
52906
52926
  // src/components/StudioHeader.tsx
52907
- import { useRef as useRef100 } from "react";
52927
+ import { useRef as useRef101 } from "react";
52908
52928
 
52909
52929
  // src/contexts/PanelLayoutContext.tsx
52910
52930
  import { createContext as createContext8, useContext as useContext8, useMemo as useMemo39 } from "react";
@@ -52983,10 +53003,11 @@ function PanelLayoutProvider({
52983
53003
  // src/contexts/ViewModeContext.tsx
52984
53004
  import {
52985
53005
  createContext as createContext9,
52986
- useCallback as useCallback109,
53006
+ useCallback as useCallback110,
52987
53007
  useContext as useContext9,
52988
53008
  useEffect as useEffect73,
52989
53009
  useMemo as useMemo40,
53010
+ useRef as useRef100,
52990
53011
  useState as useState85
52991
53012
  } from "react";
52992
53013
  import { jsx as jsx115 } from "react/jsx-runtime";
@@ -53005,18 +53026,59 @@ function writeViewModeToUrl(mode) {
53005
53026
  }
53006
53027
  window.history.replaceState(window.history.state, "", url);
53007
53028
  }
53029
+ function readHistoryLocation() {
53030
+ if (typeof window === "undefined") return null;
53031
+ return {
53032
+ href: window.location.href,
53033
+ state: window.history.state
53034
+ };
53035
+ }
53008
53036
  function useViewModeState() {
53009
53037
  const [viewMode, setMode] = useState85(() => readViewModeFromUrl());
53038
+ const guardsRef = useRef100(/* @__PURE__ */ new Set());
53039
+ const acceptedLocationRef = useRef100(readHistoryLocation());
53040
+ const canSetViewMode = useCallback110((mode) => {
53041
+ for (const guard of guardsRef.current) {
53042
+ if (!guard(mode)) return false;
53043
+ }
53044
+ return true;
53045
+ }, []);
53010
53046
  useEffect73(() => {
53011
- const onPopState = () => setMode(readViewModeFromUrl());
53047
+ const onPopState = () => {
53048
+ const mode = readViewModeFromUrl();
53049
+ if (mode !== viewMode && !canSetViewMode(mode)) {
53050
+ const acceptedLocation = acceptedLocationRef.current;
53051
+ if (acceptedLocation) {
53052
+ window.history.pushState(acceptedLocation.state, "", acceptedLocation.href);
53053
+ }
53054
+ return;
53055
+ }
53056
+ setMode(mode);
53057
+ acceptedLocationRef.current = readHistoryLocation();
53058
+ };
53012
53059
  window.addEventListener("popstate", onPopState);
53013
53060
  return () => window.removeEventListener("popstate", onPopState);
53061
+ }, [canSetViewMode, viewMode]);
53062
+ const setViewMode = useCallback110(
53063
+ (mode) => {
53064
+ if (!canSetViewMode(mode)) return false;
53065
+ setMode(mode);
53066
+ writeViewModeToUrl(mode);
53067
+ acceptedLocationRef.current = readHistoryLocation();
53068
+ return true;
53069
+ },
53070
+ [canSetViewMode]
53071
+ );
53072
+ const registerViewModeGuard = useCallback110((guard) => {
53073
+ guardsRef.current.add(guard);
53074
+ return () => {
53075
+ guardsRef.current.delete(guard);
53076
+ };
53014
53077
  }, []);
53015
- const setViewMode = useCallback109((mode) => {
53016
- setMode(mode);
53017
- writeViewModeToUrl(mode);
53018
- }, []);
53019
- return useMemo40(() => ({ viewMode, setViewMode }), [viewMode, setViewMode]);
53078
+ return useMemo40(
53079
+ () => ({ viewMode, setViewMode, registerViewModeGuard }),
53080
+ [viewMode, setViewMode, registerViewModeGuard]
53081
+ );
53020
53082
  }
53021
53083
  var ViewModeContext = createContext9(null);
53022
53084
  function useViewMode() {
@@ -53221,11 +53283,10 @@ var VIEW_MODE_OPTIONS = [
53221
53283
  ];
53222
53284
  function ViewModeToggle() {
53223
53285
  const { viewMode, setViewMode } = useViewMode();
53224
- const tabRefs = useRef100([]);
53286
+ const tabRefs = useRef101([]);
53225
53287
  const selectMode = (mode) => {
53226
53288
  if (mode === viewMode) return;
53227
- trackStudioEvent("view_mode_toggle", { mode });
53228
- setViewMode(mode);
53289
+ if (setViewMode(mode)) trackStudioEvent("view_mode_toggle", { mode });
53229
53290
  };
53230
53291
  const handleKeyDown = (e, index) => {
53231
53292
  if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
@@ -53453,10 +53514,10 @@ function StudioHeader({
53453
53514
  }
53454
53515
 
53455
53516
  // src/hooks/useGestureCommit.ts
53456
- import { useState as useState87, useCallback as useCallback111, useRef as useRef102, useEffect as useEffect75 } from "react";
53517
+ import { useState as useState87, useCallback as useCallback112, useRef as useRef103, useEffect as useEffect75 } from "react";
53457
53518
 
53458
53519
  // src/hooks/useGestureRecording.ts
53459
- import { useCallback as useCallback110, useEffect as useEffect74, useRef as useRef101, useState as useState86 } from "react";
53520
+ import { useCallback as useCallback111, useEffect as useEffect74, useRef as useRef102, useState as useState86 } from "react";
53460
53521
  function readBasePosition(element, iframeEl) {
53461
53522
  let baseOpacity = 1;
53462
53523
  let baseScale = 1;
@@ -53575,10 +53636,10 @@ function createRecordingRefs() {
53575
53636
  function useGestureRecording() {
53576
53637
  const [isRecording, setIsRecording] = useState86(false);
53577
53638
  const [recordingDuration, setRecordingDuration] = useState86(0);
53578
- const isRecordingRef = useRef101(false);
53579
- const refs = useRef101(createRecordingRefs());
53580
- const samplesRef = useRef101(refs.current.samples);
53581
- const trailRef = useRef101(refs.current.trail);
53639
+ const isRecordingRef = useRef102(false);
53640
+ const refs = useRef102(createRecordingRefs());
53641
+ const samplesRef = useRef102(refs.current.samples);
53642
+ const trailRef = useRef102(refs.current.trail);
53582
53643
  useEffect74(() => {
53583
53644
  const r = refs.current;
53584
53645
  return () => {
@@ -53587,7 +53648,7 @@ function useGestureRecording() {
53587
53648
  isRecordingRef.current = false;
53588
53649
  };
53589
53650
  }, []);
53590
- const startRecording = useCallback110(
53651
+ const startRecording = useCallback111(
53591
53652
  (element, iframeEl, elementEndTime) => {
53592
53653
  if (isRecordingRef.current) return;
53593
53654
  isRecordingRef.current = true;
@@ -53687,7 +53748,7 @@ function useGestureRecording() {
53687
53748
  []
53688
53749
  // No deps — uses refs only for all mutable state
53689
53750
  );
53690
- const stopRecording = useCallback110(() => {
53751
+ const stopRecording = useCallback111(() => {
53691
53752
  if (!isRecordingRef.current) return [];
53692
53753
  isRecordingRef.current = false;
53693
53754
  const r = refs.current;
@@ -53717,7 +53778,7 @@ function useGestureRecording() {
53717
53778
  setIsRecording(false);
53718
53779
  return frozen;
53719
53780
  }, []);
53720
- const clearSamples = useCallback110(() => {
53781
+ const clearSamples = useCallback111(() => {
53721
53782
  const r = refs.current;
53722
53783
  r.samples = [];
53723
53784
  r.trail = [];
@@ -53974,13 +54035,13 @@ function useGestureCommit({
53974
54035
  }) {
53975
54036
  const gestureRecording = useGestureRecording();
53976
54037
  const [gestureState, setGestureState] = useState87("idle");
53977
- const gestureStateRef = useRef102("idle");
53978
- const recordingAutoStopRef = useRef102(void 0);
53979
- const recordingStartTimeRef = useRef102(0);
53980
- const commitInFlightRef = useRef102(false);
53981
- const capturedSelectionRef = useRef102(null);
54038
+ const gestureStateRef = useRef103("idle");
54039
+ const recordingAutoStopRef = useRef103(void 0);
54040
+ const recordingStartTimeRef = useRef103(0);
54041
+ const commitInFlightRef = useRef103(false);
54042
+ const capturedSelectionRef = useRef103(null);
53982
54043
  useEffect75(() => () => clearInterval(recordingAutoStopRef.current), []);
53983
- const stopAndCommitRecording = useCallback111(async () => {
54044
+ const stopAndCommitRecording = useCallback112(async () => {
53984
54045
  clearInterval(recordingAutoStopRef.current);
53985
54046
  if (commitInFlightRef.current) {
53986
54047
  return;
@@ -54146,7 +54207,7 @@ function useGestureCommit({
54146
54207
  commitInFlightRef.current = false;
54147
54208
  }
54148
54209
  }, [gestureRecording, showToast, isGestureRecordingRef, domEditSessionRef]);
54149
- const handleToggleRecording = useCallback111(() => {
54210
+ const handleToggleRecording = useCallback112(() => {
54150
54211
  if (gestureStateRef.current === "recording") {
54151
54212
  void stopAndCommitRecording();
54152
54213
  return;
@@ -54304,20 +54365,20 @@ var GestureTrailOverlay = memo36(function GestureTrailOverlay2({
54304
54365
  });
54305
54366
 
54306
54367
  // src/components/StudioLeftSidebar.tsx
54307
- import { useCallback as useCallback118 } from "react";
54368
+ import { useCallback as useCallback119 } from "react";
54308
54369
 
54309
54370
  // src/components/sidebar/LeftSidebar.tsx
54310
54371
  import {
54311
54372
  memo as memo40,
54312
54373
  useState as useState96,
54313
- useCallback as useCallback117,
54374
+ useCallback as useCallback118,
54314
54375
  useImperativeHandle,
54315
- useRef as useRef108,
54376
+ useRef as useRef109,
54316
54377
  forwardRef as forwardRef3
54317
54378
  } from "react";
54318
54379
 
54319
54380
  // src/components/sidebar/CompositionsTab.tsx
54320
- import { memo as memo37, useCallback as useCallback112, useEffect as useEffect76, useRef as useRef103, useState as useState88 } from "react";
54381
+ import { memo as memo37, useCallback as useCallback113, useEffect as useEffect76, useRef as useRef104, useState as useState88 } from "react";
54321
54382
  import { jsx as jsx118, jsxs as jsxs102 } from "react/jsx-runtime";
54322
54383
  var DEFAULT_PREVIEW_STAGE = { width: 1920, height: 1080 };
54323
54384
  var CARD_W = 80;
@@ -54386,10 +54447,10 @@ function CompCard({
54386
54447
  }) {
54387
54448
  const [hovered, setHovered] = useState88(false);
54388
54449
  const [stageSize, setStageSize] = useState88(DEFAULT_PREVIEW_STAGE);
54389
- const iframeRef = useRef103(null);
54390
- const hoverTimer = useRef103(null);
54391
- const syncTimer = useRef103(null);
54392
- const requestIframePlaybackSync = useCallback112((shouldPlay) => {
54450
+ const iframeRef = useRef104(null);
54451
+ const hoverTimer = useRef104(null);
54452
+ const syncTimer = useRef104(null);
54453
+ const requestIframePlaybackSync = useCallback113((shouldPlay) => {
54393
54454
  if (syncTimer.current) {
54394
54455
  clearTimeout(syncTimer.current);
54395
54456
  syncTimer.current = null;
@@ -54547,7 +54608,7 @@ var CompositionsTab = memo37(function CompositionsTab2({
54547
54608
  });
54548
54609
 
54549
54610
  // src/components/sidebar/AssetsTab.tsx
54550
- import { memo as memo38, useState as useState93, useCallback as useCallback115, useRef as useRef106, useMemo as useMemo43, useEffect as useEffect81 } from "react";
54611
+ import { memo as memo38, useState as useState93, useCallback as useCallback116, useRef as useRef107, useMemo as useMemo43, useEffect as useEffect81 } from "react";
54551
54612
 
54552
54613
  // src/components/sidebar/assetHelpers.ts
54553
54614
  function getCategory(path) {
@@ -54597,7 +54658,7 @@ var CATEGORY_LABELS = {
54597
54658
  var FILTER_ORDER = ["audio", "images", "video", "fonts"];
54598
54659
 
54599
54660
  // src/components/sidebar/AudioRow.tsx
54600
- import { useState as useState89, useRef as useRef104, useEffect as useEffect77, useCallback as useCallback113 } from "react";
54661
+ import { useState as useState89, useRef as useRef105, useEffect as useEffect77, useCallback as useCallback114 } from "react";
54601
54662
 
54602
54663
  // src/components/sidebar/AssetContextMenu.tsx
54603
54664
  import { jsx as jsx119, jsxs as jsxs103 } from "react/jsx-runtime";
@@ -54726,24 +54787,24 @@ function AudioRow({
54726
54787
  const [contextMenu, setContextMenu] = useState89(null);
54727
54788
  const [playing, setPlaying] = useState89(false);
54728
54789
  const [bars, setBars] = useState89([]);
54729
- const audioRef = useRef104(null);
54730
- const actxRef = useRef104(null);
54731
- const analyserRef = useRef104(null);
54732
- const sourceRef = useRef104(null);
54733
- const animRef = useRef104(0);
54790
+ const audioRef = useRef105(null);
54791
+ const actxRef = useRef105(null);
54792
+ const analyserRef = useRef105(null);
54793
+ const sourceRef = useRef105(null);
54794
+ const animRef = useRef105(0);
54734
54795
  const name = basename2(asset);
54735
54796
  const subtype = getAudioSubtype(asset);
54736
54797
  const serveUrl = resolveMediaPreviewUrl(asset, projectId);
54737
- const pointerDownRef = useRef104(null);
54798
+ const pointerDownRef = useRef105(null);
54738
54799
  const setSelectedElementId = usePlayerStore((s) => s.setSelectedElementId);
54739
54800
  const requestClipReveal = usePlayerStore((s) => s.requestClipReveal);
54740
54801
  const elements = usePlayerStore((s) => s.elements);
54741
54802
  const setPreviewAsset = useAssetPreviewStore((s) => s.setPreviewAsset);
54742
54803
  const clearPreviewAsset = useAssetPreviewStore((s) => s.clearPreviewAsset);
54743
- const handlePointerDown = useCallback113((e) => {
54804
+ const handlePointerDown = useCallback114((e) => {
54744
54805
  pointerDownRef.current = { x: e.clientX, y: e.clientY };
54745
54806
  }, []);
54746
- const handlePointerUp = useCallback113(
54807
+ const handlePointerUp = useCallback114(
54747
54808
  (e) => {
54748
54809
  const origin = pointerDownRef.current;
54749
54810
  pointerDownRef.current = null;
@@ -54807,7 +54868,7 @@ function AudioRow({
54807
54868
  }
54808
54869
  return () => cancelAnimationFrame(animRef.current);
54809
54870
  }, [playing]);
54810
- const togglePlay = useCallback113(async () => {
54871
+ const togglePlay = useCallback114(async () => {
54811
54872
  if (playing) {
54812
54873
  audioRef.current?.pause();
54813
54874
  setPlaying(false);
@@ -54976,7 +55037,7 @@ function GlobalAssetsView({ searchQuery }) {
54976
55037
  }
54977
55038
 
54978
55039
  // src/components/sidebar/AssetCard.tsx
54979
- import { useState as useState92, useEffect as useEffect80, useRef as useRef105, useCallback as useCallback114 } from "react";
55040
+ import { useState as useState92, useEffect as useEffect80, useRef as useRef106, useCallback as useCallback115 } from "react";
54980
55041
 
54981
55042
  // src/components/ui/VideoFrameThumbnail.tsx
54982
55043
  import { useState as useState91, useEffect as useEffect79 } from "react";
@@ -55101,16 +55162,16 @@ function AssetCard({
55101
55162
  const probedDuration = useProbedDuration(serveUrl, !isVideo || duration != null);
55102
55163
  const resolvedDuration = duration ?? probedDuration ?? void 0;
55103
55164
  const durationLabel = formatDuration(resolvedDuration ?? 0);
55104
- const pointerDownRef = useRef105(null);
55165
+ const pointerDownRef = useRef106(null);
55105
55166
  const setSelectedElementId = usePlayerStore((s) => s.setSelectedElementId);
55106
55167
  const requestClipReveal = usePlayerStore((s) => s.requestClipReveal);
55107
55168
  const elements = usePlayerStore((s) => s.elements);
55108
55169
  const setPreviewAsset = useAssetPreviewStore((s) => s.setPreviewAsset);
55109
55170
  const clearPreviewAsset = useAssetPreviewStore((s) => s.clearPreviewAsset);
55110
- const handlePointerDown = useCallback114((e) => {
55171
+ const handlePointerDown = useCallback115((e) => {
55111
55172
  pointerDownRef.current = { x: e.clientX, y: e.clientY };
55112
55173
  }, []);
55113
- const handlePointerUp = useCallback114(
55174
+ const handlePointerUp = useCallback115(
55114
55175
  (e) => {
55115
55176
  const origin = pointerDownRef.current;
55116
55177
  pointerDownRef.current = null;
@@ -55303,7 +55364,7 @@ var AssetsTab = memo38(function AssetsTab2({
55303
55364
  onRename,
55304
55365
  onAddAssetToTimeline
55305
55366
  }) {
55306
- const fileInputRef = useRef106(null);
55367
+ const fileInputRef = useRef107(null);
55307
55368
  const [dragOver, setDragOver] = useState93(false);
55308
55369
  const [copiedPath, setCopiedPath] = useState93(null);
55309
55370
  const [activeFilter, setActiveFilter] = useState93("all");
@@ -55311,7 +55372,7 @@ var AssetsTab = memo38(function AssetsTab2({
55311
55372
  const [searchQuery, setSearchQuery] = useState93("");
55312
55373
  const [viewMode, setViewMode] = useState93("local");
55313
55374
  const [manifest, setManifest] = useState93(/* @__PURE__ */ new Map());
55314
- const manifest404Ref = useRef106(/* @__PURE__ */ new Set());
55375
+ const manifest404Ref = useRef107(/* @__PURE__ */ new Set());
55315
55376
  const assetsKey = assets.join("|");
55316
55377
  useEffect81(() => {
55317
55378
  if (manifest404Ref.current.has(projectId)) return;
@@ -55340,7 +55401,7 @@ var AssetsTab = memo38(function AssetsTab2({
55340
55401
  cancelled = true;
55341
55402
  };
55342
55403
  }, [projectId, assetsKey]);
55343
- const handleDrop = useCallback115(
55404
+ const handleDrop = useCallback116(
55344
55405
  (e) => {
55345
55406
  e.preventDefault();
55346
55407
  setDragOver(false);
@@ -55348,7 +55409,7 @@ var AssetsTab = memo38(function AssetsTab2({
55348
55409
  },
55349
55410
  [onImport]
55350
55411
  );
55351
- const handleCopyPath = useCallback115(async (path) => {
55412
+ const handleCopyPath = useCallback116(async (path) => {
55352
55413
  const copied = await copyTextToClipboard(path);
55353
55414
  if (copied) {
55354
55415
  setCopiedPath(path);
@@ -55632,7 +55693,7 @@ var AssetsTab = memo38(function AssetsTab2({
55632
55693
  });
55633
55694
 
55634
55695
  // src/components/sidebar/BlocksTab.tsx
55635
- import { memo as memo39, useState as useState95, useCallback as useCallback116, useRef as useRef107, useEffect as useEffect83 } from "react";
55696
+ import { memo as memo39, useState as useState95, useCallback as useCallback117, useRef as useRef108, useEffect as useEffect83 } from "react";
55636
55697
  import { createPortal as createPortal7 } from "react-dom";
55637
55698
 
55638
55699
  // src/hooks/useBlockCatalog.ts
@@ -55926,16 +55987,16 @@ function BlockCard({
55926
55987
  }) {
55927
55988
  const [hovered, setHovered] = useState95(false);
55928
55989
  const [adding, setAdding] = useState95(false);
55929
- const hoverTimer = useRef107(null);
55990
+ const hoverTimer = useRef108(null);
55930
55991
  const colors = getCategoryColors(category);
55931
55992
  const needsWebGL = tags?.includes("html-in-canvas") || tags?.includes("webgl");
55932
- const handleEnter = useCallback116(() => {
55993
+ const handleEnter = useCallback117(() => {
55933
55994
  hoverTimer.current = setTimeout(() => {
55934
55995
  setHovered(true);
55935
55996
  onPreview?.({ videoUrl, posterUrl, title });
55936
55997
  }, 300);
55937
55998
  }, [onPreview, videoUrl, posterUrl, title]);
55938
- const handleLeave = useCallback116(() => {
55999
+ const handleLeave = useCallback117(() => {
55939
56000
  if (hoverTimer.current) {
55940
56001
  clearTimeout(hoverTimer.current);
55941
56002
  hoverTimer.current = null;
@@ -55948,7 +56009,7 @@ function BlockCard({
55948
56009
  if (hoverTimer.current) clearTimeout(hoverTimer.current);
55949
56010
  };
55950
56011
  }, []);
55951
- const handleAdd = useCallback116(
56012
+ const handleAdd = useCallback117(
55952
56013
  (e) => {
55953
56014
  e.stopPropagation();
55954
56015
  if (adding || !onAdd) return;
@@ -55959,7 +56020,7 @@ function BlockCard({
55959
56020
  [onAdd, adding]
55960
56021
  );
55961
56022
  const { activeCompPath, compositionDimensions } = useStudioShellContext();
55962
- const handleShowPrompt = useCallback116(
56023
+ const handleShowPrompt = useCallback117(
55963
56024
  (e) => {
55964
56025
  e.stopPropagation();
55965
56026
  const state = usePlayerStore.getState();
@@ -56104,11 +56165,11 @@ function PromptPreviewModal({
56104
56165
  }) {
56105
56166
  const [value, setValue] = useState95(prompt);
56106
56167
  const [copied, setCopied] = useState95(false);
56107
- const textareaRef = useRef107(null);
56168
+ const textareaRef = useRef108(null);
56108
56169
  useEffect83(() => {
56109
56170
  requestAnimationFrame(() => textareaRef.current?.focus());
56110
56171
  }, []);
56111
- const handleCopy = useCallback116(() => {
56172
+ const handleCopy = useCallback117(() => {
56112
56173
  navigator.clipboard.writeText(value);
56113
56174
  setCopied(true);
56114
56175
  setTimeout(() => setCopied(false), 1500);
@@ -56232,14 +56293,14 @@ var LeftSidebar = memo40(
56232
56293
  onAddAssetToTimeline
56233
56294
  }, ref) {
56234
56295
  const [tab, setTab] = useState96(getPersistedTab);
56235
- const tabRef = useRef108(tab);
56296
+ const tabRef = useRef109(tab);
56236
56297
  tabRef.current = tab;
56237
- const selectTab = useCallback117((t) => {
56298
+ const selectTab = useCallback118((t) => {
56238
56299
  setTab(t);
56239
56300
  localStorage.setItem(STORAGE_KEY, t);
56240
56301
  trackStudioEvent("tab_switch", { panel: "left_sidebar", tab: t });
56241
56302
  }, []);
56242
- const getTab = useCallback117(() => tabRef.current, []);
56303
+ const getTab = useCallback118(() => tabRef.current, []);
56243
56304
  useImperativeHandle(ref, () => ({ selectTab, getTab }), [selectTab, getTab]);
56244
56305
  return /* @__PURE__ */ jsx126(
56245
56306
  "div",
@@ -56580,7 +56641,7 @@ function StudioLeftSidebar({
56580
56641
  handleImportFiles,
56581
56642
  handleContentChange
56582
56643
  } = useFileManagerContext();
56583
- const handleRenderComposition = useCallback118(
56644
+ const handleRenderComposition = useCallback119(
56584
56645
  async (comp) => {
56585
56646
  await waitForPendingDomEditSaves();
56586
56647
  const { format, quality, fps } = getPersistedRenderSettings();
@@ -56700,13 +56761,13 @@ function StudioLeftSidebar({
56700
56761
  }
56701
56762
 
56702
56763
  // src/components/StudioRightPanel.tsx
56703
- import { useCallback as useCallback134, useEffect as useEffect89, useMemo as useMemo46, useRef as useRef114 } from "react";
56764
+ import { useCallback as useCallback135, useEffect as useEffect90, useRef as useRef115 } from "react";
56704
56765
 
56705
56766
  // src/components/editor/LayersPanel.tsx
56706
- import { memo as memo41, useState as useState99, useCallback as useCallback120, useEffect as useEffect84, useRef as useRef110 } from "react";
56767
+ import { memo as memo41, useState as useState99, useCallback as useCallback121, useEffect as useEffect84, useRef as useRef111 } from "react";
56707
56768
 
56708
56769
  // src/components/editor/useLayerDrag.ts
56709
- import { useRef as useRef109, useState as useState98, useCallback as useCallback119 } from "react";
56770
+ import { useRef as useRef110, useState as useState98, useCallback as useCallback120 } from "react";
56710
56771
  var DRAG_THRESHOLD_PX3 = 4;
56711
56772
  function isLayerDraggable(layer) {
56712
56773
  if (!(layer.selector || layer.id)) return false;
@@ -56773,10 +56834,10 @@ function useLayerDrag({
56773
56834
  onReorder,
56774
56835
  onSingleSibling
56775
56836
  }) {
56776
- const dragRef = useRef109(null);
56837
+ const dragRef = useRef110(null);
56777
56838
  const [dragKey, setDragKey] = useState98(null);
56778
56839
  const [insertionLineY, setInsertionLineY] = useState98(null);
56779
- const handleRowPointerDown = useCallback119(
56840
+ const handleRowPointerDown = useCallback120(
56780
56841
  (layerIndex, e) => {
56781
56842
  if (e.button !== 0) return;
56782
56843
  const layer = visibleLayers[layerIndex];
@@ -56803,7 +56864,7 @@ function useLayerDrag({
56803
56864
  },
56804
56865
  [visibleLayers, scrollContainerRef, onSingleSibling]
56805
56866
  );
56806
- const handleContainerPointerMove = useCallback119(
56867
+ const handleContainerPointerMove = useCallback120(
56807
56868
  (e) => {
56808
56869
  const drag = dragRef.current;
56809
56870
  if (!drag) return;
@@ -56829,7 +56890,7 @@ function useLayerDrag({
56829
56890
  },
56830
56891
  [visibleLayers, scrollContainerRef]
56831
56892
  );
56832
- const handleContainerPointerUp = useCallback119(() => {
56893
+ const handleContainerPointerUp = useCallback120(() => {
56833
56894
  const drag = dragRef.current;
56834
56895
  dragRef.current = null;
56835
56896
  setDragKey(null);
@@ -56978,15 +57039,15 @@ var LayersPanel = memo41(function LayersPanel2() {
56978
57039
  } = useDomEditContext();
56979
57040
  const [layers, setLayers] = useState99([]);
56980
57041
  const [collapsed, setCollapsed] = useState99({});
56981
- const prevDocVersionRef = useRef110(0);
56982
- const scrollContainerRef = useRef110(null);
57042
+ const prevDocVersionRef = useRef111(0);
57043
+ const scrollContainerRef = useRef111(null);
56983
57044
  const mirrorLayerReorderToTimeline = useLayerReorderTimelineMirror();
56984
57045
  const { scheduleReveal } = useLayerRevealOverride({
56985
57046
  isPlaying,
56986
57047
  selectedElement: domEditSelection?.element ?? null
56987
57048
  });
56988
57049
  const isMasterView = !activeCompPath || activeCompPath === "index.html";
56989
- const collectLayers = useCallback120(() => {
57050
+ const collectLayers = useCallback121(() => {
56990
57051
  const iframe = previewIframeRef.current;
56991
57052
  if (!iframe) return;
56992
57053
  let doc = null;
@@ -57033,7 +57094,7 @@ var LayersPanel = memo41(function LayersPanel2() {
57033
57094
  throttle.cancel();
57034
57095
  };
57035
57096
  }, [collectLayers]);
57036
- const resolveSelection = useCallback120(
57097
+ const resolveSelection = useCallback121(
57037
57098
  (layer) => {
57038
57099
  let el = layer.element;
57039
57100
  if (!el.isConnected) {
@@ -57053,7 +57114,7 @@ var LayersPanel = memo41(function LayersPanel2() {
57053
57114
  },
57054
57115
  [activeCompPath, isMasterView, previewIframeRef, activeGroupElement]
57055
57116
  );
57056
- const seekToLayer = useCallback120(
57117
+ const seekToLayer = useCallback121(
57057
57118
  async (layer) => {
57058
57119
  const selection = await resolveSelection(layer);
57059
57120
  if (!selection) return;
@@ -57082,7 +57143,7 @@ var LayersPanel = memo41(function LayersPanel2() {
57082
57143
  },
57083
57144
  [currentTime, resolveSelection, timelineElements]
57084
57145
  );
57085
- const handleSelectLayer = useCallback120(
57146
+ const handleSelectLayer = useCallback121(
57086
57147
  async (layer) => {
57087
57148
  const selection = await resolveSelection(layer);
57088
57149
  if (!selection) return;
@@ -57092,7 +57153,7 @@ var LayersPanel = memo41(function LayersPanel2() {
57092
57153
  },
57093
57154
  [resolveSelection, applyDomSelection, seekToLayer, scheduleReveal]
57094
57155
  );
57095
- const handleLayerDoubleClick = useCallback120(
57156
+ const handleLayerDoubleClick = useCallback121(
57096
57157
  async (layer) => {
57097
57158
  const selection = await resolveSelection(layer);
57098
57159
  if (selection?.element.hasAttribute("data-hf-group")) {
@@ -57103,7 +57164,7 @@ var LayersPanel = memo41(function LayersPanel2() {
57103
57164
  },
57104
57165
  [resolveSelection, setActiveGroupElement, handleSelectLayer]
57105
57166
  );
57106
- const handleLayerHover = useCallback120(
57167
+ const handleLayerHover = useCallback121(
57107
57168
  async (layer) => {
57108
57169
  if (!layer) {
57109
57170
  updateDomEditHoverSelection(null);
@@ -57114,11 +57175,11 @@ var LayersPanel = memo41(function LayersPanel2() {
57114
57175
  },
57115
57176
  [resolveSelection, updateDomEditHoverSelection]
57116
57177
  );
57117
- const toggleCollapse = useCallback120((key, e) => {
57178
+ const toggleCollapse = useCallback121((key, e) => {
57118
57179
  e.stopPropagation();
57119
57180
  setCollapsed((prev) => ({ ...prev, [key]: !prev[key] }));
57120
57181
  }, []);
57121
- const handleReorder = useCallback120(
57182
+ const handleReorder = useCallback121(
57122
57183
  (event) => {
57123
57184
  const { siblingLayers, fromIndex, toIndex } = event;
57124
57185
  const reordered = [...siblingLayers];
@@ -57181,7 +57242,7 @@ var LayersPanel = memo41(function LayersPanel2() {
57181
57242
  );
57182
57243
  const selectedKey = domEditSelection ? getDomEditLayerKey(domEditSelection) : null;
57183
57244
  const visibleLayers = getVisibleLayers(layers, collapsed);
57184
- const handleSingleSibling = useCallback120(() => {
57245
+ const handleSingleSibling = useCallback121(() => {
57185
57246
  showToast("Only one layer at this level", "info");
57186
57247
  }, [showToast]);
57187
57248
  const {
@@ -57310,10 +57371,10 @@ var LayersPanel = memo41(function LayersPanel2() {
57310
57371
  });
57311
57372
 
57312
57373
  // src/captions/components/CaptionPropertyPanel.tsx
57313
- import { memo as memo43, useCallback as useCallback122, useState as useState100 } from "react";
57374
+ import { memo as memo43, useCallback as useCallback123, useState as useState100 } from "react";
57314
57375
 
57315
57376
  // src/captions/components/CaptionAnimationPanel.tsx
57316
- import { memo as memo42, useCallback as useCallback121 } from "react";
57377
+ import { memo as memo42, useCallback as useCallback122 } from "react";
57317
57378
 
57318
57379
  // src/captions/components/shared.tsx
57319
57380
  import { jsx as jsx130, jsxs as jsxs113 } from "react/jsx-runtime";
@@ -57476,25 +57537,25 @@ var CaptionAnimationPanel = memo42(function CaptionAnimationPanel2() {
57476
57537
  }
57477
57538
  const group = resolvedGroupId ? model?.groups.get(resolvedGroupId) : void 0;
57478
57539
  const animation = group?.animation;
57479
- const handleEntranceChange = useCallback121(
57540
+ const handleEntranceChange = useCallback122(
57480
57541
  (update) => {
57481
57542
  if (resolvedGroupId) updateGroupAnimation(resolvedGroupId, "entrance", update);
57482
57543
  },
57483
57544
  [resolvedGroupId, updateGroupAnimation]
57484
57545
  );
57485
- const handleHighlightChange = useCallback121(
57546
+ const handleHighlightChange = useCallback122(
57486
57547
  (update) => {
57487
57548
  if (resolvedGroupId) updateGroupAnimation(resolvedGroupId, "highlight", update);
57488
57549
  },
57489
57550
  [resolvedGroupId, updateGroupAnimation]
57490
57551
  );
57491
- const handleExitChange = useCallback121(
57552
+ const handleExitChange = useCallback122(
57492
57553
  (update) => {
57493
57554
  if (resolvedGroupId) updateGroupAnimation(resolvedGroupId, "exit", update);
57494
57555
  },
57495
57556
  [resolvedGroupId, updateGroupAnimation]
57496
57557
  );
57497
- const handleApplyToAll = useCallback121(() => {
57558
+ const handleApplyToAll = useCallback122(() => {
57498
57559
  if (animation) applyAnimationToAll(animation);
57499
57560
  }, [animation, applyAnimationToAll]);
57500
57561
  if (!group || !resolvedGroupId || !animation) {
@@ -57572,7 +57633,7 @@ var CaptionPropertyPanel = memo43(function CaptionPropertyPanel2({
57572
57633
  ...groupStyle,
57573
57634
  ...segmentOverrides
57574
57635
  };
57575
- const applyToIframeDom = useCallback122(
57636
+ const applyToIframeDom = useCallback123(
57576
57637
  (updates) => {
57577
57638
  const iframe = iframeRef.current;
57578
57639
  if (!iframe || !model) return;
@@ -57646,7 +57707,7 @@ var CaptionPropertyPanel = memo43(function CaptionPropertyPanel2({
57646
57707
  },
57647
57708
  [iframeRef, model, selectedSegmentIds]
57648
57709
  );
57649
- const handleStyleChange = useCallback122(
57710
+ const handleStyleChange = useCallback123(
57650
57711
  (updates) => {
57651
57712
  if (selectedGroupId) {
57652
57713
  updateGroupStyle(selectedGroupId, updates);
@@ -57746,7 +57807,7 @@ var CaptionPropertyPanel = memo43(function CaptionPropertyPanel2({
57746
57807
  });
57747
57808
 
57748
57809
  // src/components/editor/BlockParamsPanel.tsx
57749
- import { memo as memo44, useState as useState101, useCallback as useCallback123 } from "react";
57810
+ import { memo as memo44, useState as useState101, useCallback as useCallback124 } from "react";
57750
57811
  import { jsx as jsx133, jsxs as jsxs116 } from "react/jsx-runtime";
57751
57812
  var BlockParamsPanel = memo44(function BlockParamsPanel2({
57752
57813
  blockTitle,
@@ -57761,7 +57822,7 @@ var BlockParamsPanel = memo44(function BlockParamsPanel2({
57761
57822
  }
57762
57823
  return initial;
57763
57824
  });
57764
- const handleChange = useCallback123((key, value) => {
57825
+ const handleChange = useCallback124((key, value) => {
57765
57826
  setValues((prev) => ({ ...prev, [key]: value }));
57766
57827
  }, []);
57767
57828
  return /* @__PURE__ */ jsxs116("div", { className: "flex flex-col h-full", children: [
@@ -57871,10 +57932,10 @@ function ParamControl({
57871
57932
  }
57872
57933
 
57873
57934
  // src/components/renders/RenderQueue.tsx
57874
- import { memo as memo46, useState as useState103, useRef as useRef111, useEffect as useEffect85, useId as useId2 } from "react";
57935
+ import { memo as memo46, useState as useState103, useRef as useRef112, useEffect as useEffect85, useId as useId2 } from "react";
57875
57936
 
57876
57937
  // src/components/renders/RenderQueueItem.tsx
57877
- import { memo as memo45, useCallback as useCallback124, useState as useState102 } from "react";
57938
+ import { memo as memo45, useCallback as useCallback125, useState as useState102 } from "react";
57878
57939
  import { Fragment as Fragment42, jsx as jsx134, jsxs as jsxs117 } from "react/jsx-runtime";
57879
57940
  function formatDuration2(ms) {
57880
57941
  if (ms < 1e3) return `${ms}ms`;
@@ -57897,10 +57958,10 @@ var RenderQueueItem = memo45(function RenderQueueItem2({
57897
57958
  const [videoReady, setVideoReady] = useState102(false);
57898
57959
  const [confirmingDelete, setConfirmingDelete] = useState102(false);
57899
57960
  const fileSrc = `/api/projects/${projectId}/renders/file/${job.filename}`;
57900
- const handleOpen = useCallback124(() => {
57961
+ const handleOpen = useCallback125(() => {
57901
57962
  window.open(fileSrc, "_blank");
57902
57963
  }, [fileSrc]);
57903
- const handleDownload = useCallback124(
57964
+ const handleDownload = useCallback125(
57904
57965
  (e) => {
57905
57966
  e.stopPropagation();
57906
57967
  const a = document.createElement("a");
@@ -58161,7 +58222,7 @@ var FORMAT_INFO = {
58161
58222
  };
58162
58223
  function FormatInfoTooltip({ format }) {
58163
58224
  const [open, setOpen] = useState103(false);
58164
- const timeoutRef = useRef111(void 0);
58225
+ const timeoutRef = useRef112(void 0);
58165
58226
  const panelId = useId2();
58166
58227
  const show = () => {
58167
58228
  clearTimeout(timeoutRef.current);
@@ -58375,7 +58436,7 @@ var RenderQueue = memo46(function RenderQueue2({
58375
58436
  onDismissActionError,
58376
58437
  compositionDimensions
58377
58438
  }) {
58378
- const listRef = useRef111(null);
58439
+ const listRef = useRef112(null);
58379
58440
  useEffect85(() => {
58380
58441
  if (listRef.current) {
58381
58442
  listRef.current.scrollTo({ top: listRef.current.scrollHeight, behavior: "smooth" });
@@ -58484,11 +58545,11 @@ var RenderQueue = memo46(function RenderQueue2({
58484
58545
  });
58485
58546
 
58486
58547
  // src/components/panels/SlideshowPanel.tsx
58487
- import { useState as useState105, useEffect as useEffect86, useCallback as useCallback126, useRef as useRef112 } from "react";
58548
+ import { useState as useState105, useEffect as useEffect86, useCallback as useCallback127, useRef as useRef113 } from "react";
58488
58549
  import { parseSlideshowManifest } from "@hyperframes/core/slideshow";
58489
58550
 
58490
58551
  // src/components/panels/SlideshowSubPanels.tsx
58491
- import { useState as useState104, useCallback as useCallback125, useId as useId3 } from "react";
58552
+ import { useState as useState104, useCallback as useCallback126, useId as useId3 } from "react";
58492
58553
  import { jsx as jsx136, jsxs as jsxs119 } from "react/jsx-runtime";
58493
58554
  function SectionHeader({
58494
58555
  children,
@@ -58673,7 +58734,7 @@ function BranchTree({
58673
58734
  }) {
58674
58735
  const [newLabel, setNewLabel] = useState104("");
58675
58736
  const inputId = useId3();
58676
- const handleCreate = useCallback125(() => {
58737
+ const handleCreate = useCallback126(() => {
58677
58738
  const label = newLabel.trim();
58678
58739
  if (!label) return;
58679
58740
  onCreateSequence(label);
@@ -58734,7 +58795,7 @@ function BranchItem({
58734
58795
  }) {
58735
58796
  const [editing, setEditing] = useState104(false);
58736
58797
  const [draft, setDraft] = useState104(seq.label);
58737
- const commitRename = useCallback125(() => {
58798
+ const commitRename = useCallback126(() => {
58738
58799
  const label = draft.trim();
58739
58800
  if (label && label !== seq.label) onRename(seq.id, label);
58740
58801
  setEditing(false);
@@ -58832,7 +58893,7 @@ function HotspotTool({
58832
58893
  const selectedElementId = domEditSelection?.element?.id ?? null;
58833
58894
  const selectedHfId = domEditSelection?.hfId ?? null;
58834
58895
  const elementKey2 = selectedElementId || selectedHfId;
58835
- const handleMakeHotspot = useCallback125(() => {
58896
+ const handleMakeHotspot = useCallback126(() => {
58836
58897
  if (!selectedSceneId || !targetSequenceId || !elementKey2) return;
58837
58898
  const id = `hotspot-${elementKey2}-${generateId()}`;
58838
58899
  const label = hotspotLabel.trim() || elementKey2;
@@ -59121,8 +59182,8 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
59121
59182
  );
59122
59183
  const currentTime = usePlayerStore((s) => s.currentTime);
59123
59184
  const { domEditSelection } = useDomEditSelectionContext();
59124
- const manifestRef = useRef112(manifest);
59125
- const notesCtrlRef = useRef112(makeSlideshowNotesController());
59185
+ const manifestRef = useRef113(manifest);
59186
+ const notesCtrlRef = useRef113(makeSlideshowNotesController());
59126
59187
  useEffect86(() => {
59127
59188
  if (!compHtml) {
59128
59189
  notesCtrlRef.current.flush();
@@ -59136,7 +59197,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
59136
59197
  manifestRef.current = parsed;
59137
59198
  setSelectedSequenceId(null);
59138
59199
  }, [compHtml]);
59139
- const applyManifest = useCallback126(
59200
+ const applyManifest = useCallback127(
59140
59201
  async (next) => {
59141
59202
  const merged = notesCtrlRef.current.mergeIntoDiscrete(next);
59142
59203
  setManifest(merged);
@@ -59149,7 +59210,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
59149
59210
  },
59150
59211
  [onPersist]
59151
59212
  );
59152
- const applyNotesManifest = useCallback126(
59213
+ const applyNotesManifest = useCallback127(
59153
59214
  (next) => {
59154
59215
  setManifest(next);
59155
59216
  manifestRef.current = next;
@@ -59163,7 +59224,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
59163
59224
  ctrl.flush();
59164
59225
  };
59165
59226
  }, []);
59166
- const toggleSection = useCallback126((key) => {
59227
+ const toggleSection = useCallback127((key) => {
59167
59228
  setExpandedSections((prev) => {
59168
59229
  const next = new Set(prev);
59169
59230
  if (next.has(key)) {
@@ -59177,25 +59238,25 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
59177
59238
  const activeSlides = selectedSequenceId ? (manifest.slideSequences ?? []).find((s) => s.id === selectedSequenceId)?.slides ?? [] : manifest.slides;
59178
59239
  const selectedSlide = activeSlides.find((s) => s.sceneId === selectedSceneId);
59179
59240
  const sequences = manifest.slideSequences ?? [];
59180
- const handleSelectBranchSlide = useCallback126((sequenceId, sceneId) => {
59241
+ const handleSelectBranchSlide = useCallback127((sequenceId, sceneId) => {
59181
59242
  setSelectedSceneId(sceneId);
59182
59243
  setSelectedSequenceId(sequenceId);
59183
59244
  }, []);
59184
- const handleToggleSlide = useCallback126(
59245
+ const handleToggleSlide = useCallback127(
59185
59246
  (sceneId) => {
59186
59247
  applyManifest(toggleMainLineSlide(manifestRef.current, sceneId)).catch(() => {
59187
59248
  });
59188
59249
  },
59189
59250
  [applyManifest]
59190
59251
  );
59191
- const handleReorder = useCallback126(
59252
+ const handleReorder = useCallback127(
59192
59253
  (sceneId, dir) => {
59193
59254
  applyManifest(reorderMainLineSlide(manifestRef.current, sceneId, dir)).catch(() => {
59194
59255
  });
59195
59256
  },
59196
59257
  [applyManifest]
59197
59258
  );
59198
- const handleSetNotes = useCallback126(
59259
+ const handleSetNotes = useCallback127(
59199
59260
  (notes) => {
59200
59261
  if (!selectedSceneId) return;
59201
59262
  applyNotesManifest(
@@ -59204,7 +59265,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
59204
59265
  },
59205
59266
  [selectedSceneId, selectedSequenceId, applyNotesManifest]
59206
59267
  );
59207
- const handleMarkFragment = useCallback126(() => {
59268
+ const handleMarkFragment = useCallback127(() => {
59208
59269
  if (!selectedSceneId) return;
59209
59270
  applyManifest(
59210
59271
  addFragment(
@@ -59216,7 +59277,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
59216
59277
  ).catch(() => {
59217
59278
  });
59218
59279
  }, [selectedSceneId, selectedSequenceId, currentTime, applyManifest]);
59219
- const handleRemoveFragment = useCallback126(
59280
+ const handleRemoveFragment = useCallback127(
59220
59281
  (time) => {
59221
59282
  if (!selectedSceneId) return;
59222
59283
  applyManifest(
@@ -59226,7 +59287,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
59226
59287
  },
59227
59288
  [selectedSceneId, selectedSequenceId, applyManifest]
59228
59289
  );
59229
- const handleCreateSequence = useCallback126(
59290
+ const handleCreateSequence = useCallback127(
59230
59291
  (label) => {
59231
59292
  const id = `seq-${generateId()}`;
59232
59293
  applyManifest(createSequence(manifestRef.current, id, label)).catch(() => {
@@ -59234,14 +59295,14 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
59234
59295
  },
59235
59296
  [applyManifest]
59236
59297
  );
59237
- const handleRenameSequence = useCallback126(
59298
+ const handleRenameSequence = useCallback127(
59238
59299
  (id, label) => {
59239
59300
  applyManifest(renameSequence(manifestRef.current, id, label)).catch(() => {
59240
59301
  });
59241
59302
  },
59242
59303
  [applyManifest]
59243
59304
  );
59244
- const handleDeleteSequence = useCallback126(
59305
+ const handleDeleteSequence = useCallback127(
59245
59306
  (id) => {
59246
59307
  const seq = (manifestRef.current.slideSequences ?? []).find((s) => s.id === id);
59247
59308
  const count = seq?.slides.length ?? 0;
@@ -59255,7 +59316,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
59255
59316
  },
59256
59317
  [applyManifest]
59257
59318
  );
59258
- const handleAssign = useCallback126(
59319
+ const handleAssign = useCallback127(
59259
59320
  (sequenceId, sceneId, assign) => {
59260
59321
  applyManifest(assignToBranch(manifestRef.current, sequenceId, sceneId, assign)).catch(
59261
59322
  () => {
@@ -59264,7 +59325,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
59264
59325
  },
59265
59326
  [applyManifest]
59266
59327
  );
59267
- const handleAddHotspot = useCallback126(
59328
+ const handleAddHotspot = useCallback127(
59268
59329
  (sceneId, hotspot) => {
59269
59330
  applyManifest(
59270
59331
  addHotspot(manifestRef.current, sceneId, hotspot, selectedSequenceId ?? void 0)
@@ -59273,7 +59334,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
59273
59334
  },
59274
59335
  [selectedSequenceId, applyManifest]
59275
59336
  );
59276
- const handleRemoveHotspot = useCallback126(
59337
+ const handleRemoveHotspot = useCallback127(
59277
59338
  (sceneId, hotspotId) => {
59278
59339
  applyManifest(
59279
59340
  removeHotspot(manifestRef.current, sceneId, hotspotId, selectedSequenceId ?? void 0)
@@ -59377,10 +59438,10 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
59377
59438
  }
59378
59439
 
59379
59440
  // src/components/panels/VariablesPanel.tsx
59380
- import { memo as memo47, useCallback as useCallback130, useEffect as useEffect88, useMemo as useMemo45, useState as useState109 } from "react";
59441
+ import { memo as memo47, useCallback as useCallback131, useEffect as useEffect88, useMemo as useMemo45, useState as useState109 } from "react";
59381
59442
 
59382
59443
  // src/hooks/useVariablesPersist.ts
59383
- import { useCallback as useCallback127 } from "react";
59444
+ import { useCallback as useCallback128 } from "react";
59384
59445
  function useVariablesPersist({
59385
59446
  sdkSession,
59386
59447
  activeCompPath,
@@ -59391,7 +59452,7 @@ function useVariablesPersist({
59391
59452
  domEditSaveTimestampRef,
59392
59453
  publishSdkSession
59393
59454
  }) {
59394
- return useCallback127(
59455
+ return useCallback128(
59395
59456
  async (label, mutate) => {
59396
59457
  if (!sdkSession) return false;
59397
59458
  const path = activeCompPath ?? "index.html";
@@ -59428,10 +59489,10 @@ function useVariablesPersist({
59428
59489
  }
59429
59490
 
59430
59491
  // src/components/panels/VariablesOtherCompositions.tsx
59431
- import { useCallback as useCallback129, useState as useState108 } from "react";
59492
+ import { useCallback as useCallback130, useState as useState108 } from "react";
59432
59493
 
59433
59494
  // src/hooks/useProjectCompositionVariables.ts
59434
- import { useCallback as useCallback128, useEffect as useEffect87, useState as useState106 } from "react";
59495
+ import { useCallback as useCallback129, useEffect as useEffect87, useState as useState106 } from "react";
59435
59496
  import { openComposition as openComposition4 } from "@hyperframes/sdk";
59436
59497
  async function readGroup(path, readProjectFile) {
59437
59498
  let content;
@@ -59474,7 +59535,7 @@ function useProjectCompositionVariables(fileTree, excludePath, readProjectFile,
59474
59535
  }
59475
59536
  function useEditVariablesInFile(deps) {
59476
59537
  const { readProjectFile, writeProjectFile, recordEdit, reloadPreview, domEditSaveTimestampRef } = deps;
59477
- return useCallback128(
59538
+ return useCallback129(
59478
59539
  async (path, label, mutate) => {
59479
59540
  const originalContent = await readProjectFile(path);
59480
59541
  await persistSdkSerialize(
@@ -59854,7 +59915,7 @@ function VariablesOtherCompositions({
59854
59915
  domEditSaveTimestampRef
59855
59916
  });
59856
59917
  const [editingKey, setEditingKey] = useState108(null);
59857
- const onSave = useCallback129(
59918
+ const onSave = useCallback130(
59858
59919
  (path, decl) => {
59859
59920
  setEditingKey(null);
59860
59921
  void editInFile(
@@ -59865,7 +59926,7 @@ function VariablesOtherCompositions({
59865
59926
  },
59866
59927
  [editInFile]
59867
59928
  );
59868
- const onRemove = useCallback129(
59929
+ const onRemove = useCallback130(
59869
59930
  (path, id) => {
59870
59931
  void editInFile(
59871
59932
  path,
@@ -60101,7 +60162,7 @@ var VariablesPanel = memo47(function VariablesPanel2({
60101
60162
  // eslint-disable-next-line react-hooks/exhaustive-deps
60102
60163
  [sdkSession, previewValues, refreshKey, revision]
60103
60164
  );
60104
- const copyToClipboard = useCallback130(
60165
+ const copyToClipboard = useCallback131(
60105
60166
  (text, what) => {
60106
60167
  void copyTextToClipboard(text).then(
60107
60168
  (ok) => showToast(
@@ -60112,7 +60173,7 @@ var VariablesPanel = memo47(function VariablesPanel2({
60112
60173
  },
60113
60174
  [showToast]
60114
60175
  );
60115
- const dropPreviewOverride = useCallback130(
60176
+ const dropPreviewOverride = useCallback131(
60116
60177
  (id) => {
60117
60178
  if (previewValues && id in previewValues) {
60118
60179
  const next = { ...previewValues };
@@ -60122,7 +60183,7 @@ var VariablesPanel = memo47(function VariablesPanel2({
60122
60183
  },
60123
60184
  [previewValues, setPreviewValues]
60124
60185
  );
60125
- const commitPreviewValue = useCallback130(
60186
+ const commitPreviewValue = useCallback131(
60126
60187
  (id, value, declDefault) => {
60127
60188
  const next = { ...previewValues ?? {} };
60128
60189
  if (JSON.stringify(value) === JSON.stringify(declDefault)) {
@@ -60135,7 +60196,7 @@ var VariablesPanel = memo47(function VariablesPanel2({
60135
60196
  },
60136
60197
  [previewValues, setPreviewValues, reloadPreview]
60137
60198
  );
60138
- const runSchemaEdit = useCallback130(
60199
+ const runSchemaEdit = useCallback131(
60139
60200
  async (label, mutate) => {
60140
60201
  try {
60141
60202
  const changed = await persistVariables(label, mutate);
@@ -60149,7 +60210,7 @@ var VariablesPanel = memo47(function VariablesPanel2({
60149
60210
  },
60150
60211
  [persistVariables, showToast]
60151
60212
  );
60152
- const handleAdd = useCallback130(
60213
+ const handleAdd = useCallback131(
60153
60214
  (decl) => {
60154
60215
  if (!sdkSession) return;
60155
60216
  const check = sdkSession.can({ type: "declareVariable", declaration: decl });
@@ -60162,7 +60223,7 @@ var VariablesPanel = memo47(function VariablesPanel2({
60162
60223
  },
60163
60224
  [sdkSession, runSchemaEdit, showToast]
60164
60225
  );
60165
- const handleUpdate = useCallback130(
60226
+ const handleUpdate = useCallback131(
60166
60227
  (decl) => {
60167
60228
  if (!sdkSession) return;
60168
60229
  const check = sdkSession.can({
@@ -60182,7 +60243,7 @@ var VariablesPanel = memo47(function VariablesPanel2({
60182
60243
  },
60183
60244
  [sdkSession, runSchemaEdit, showToast]
60184
60245
  );
60185
- const handleRemove = useCallback130(
60246
+ const handleRemove = useCallback131(
60186
60247
  (id) => {
60187
60248
  if (!sdkSession) return;
60188
60249
  const check = sdkSession.can({ type: "removeVariableDeclaration", id });
@@ -60198,18 +60259,18 @@ var VariablesPanel = memo47(function VariablesPanel2({
60198
60259
  },
60199
60260
  [sdkSession, runSchemaEdit, dropPreviewOverride, showToast]
60200
60261
  );
60201
- const handleSetDefault = useCallback130(
60262
+ const handleSetDefault = useCallback131(
60202
60263
  (id, value) => {
60203
60264
  void runSchemaEdit(`Set default for "${id}"`, (s) => s.setVariableValue(id, value));
60204
60265
  dropPreviewOverride(id);
60205
60266
  },
60206
60267
  [runSchemaEdit, dropPreviewOverride]
60207
60268
  );
60208
- const resetPreview = useCallback130(() => {
60269
+ const resetPreview = useCallback131(() => {
60209
60270
  setPreviewValues(null);
60210
60271
  reloadPreview();
60211
60272
  }, [setPreviewValues, reloadPreview]);
60212
- const handleBind = useCallback130(
60273
+ const handleBind = useCallback131(
60213
60274
  // Guard chain (session, selection, type-compat) — one branch per guard.
60214
60275
  // fallow-ignore-next-line complexity
60215
60276
  (action, id) => {
@@ -60342,7 +60403,7 @@ function PanelTabButton({
60342
60403
  }
60343
60404
 
60344
60405
  // src/hooks/useSlideshowPersist.ts
60345
- import { useCallback as useCallback131 } from "react";
60406
+ import { useCallback as useCallback132 } from "react";
60346
60407
 
60347
60408
  // src/utils/setSlideshowManifest.ts
60348
60409
  import {
@@ -60400,7 +60461,7 @@ function useSlideshowPersist({
60400
60461
  publishSdkSession,
60401
60462
  coalesceKey
60402
60463
  }) {
60403
- return useCallback131(
60464
+ return useCallback132(
60404
60465
  async (manifest) => {
60405
60466
  if (!sdkSession) return;
60406
60467
  const path = activeCompPath ?? "index.html";
@@ -60434,26 +60495,65 @@ function useSlideshowPersist({
60434
60495
  );
60435
60496
  }
60436
60497
 
60498
+ // src/hooks/useSlideshowTabState.ts
60499
+ import { useEffect as useEffect89, useMemo as useMemo46 } from "react";
60500
+ import { SLIDESHOW_ISLAND_TYPE as SLIDESHOW_ISLAND_TYPE2, slideshowIslandRegex as slideshowIslandRegex2 } from "@hyperframes/core/slideshow";
60501
+ function useSlideshowTabState(params) {
60502
+ const { editingFileContent, previewIframeRef, refreshKey, rightPanelTab, setRightPanelTab } = params;
60503
+ const isSlideshowComposition = useMemo46(() => {
60504
+ if (!editingFileContent || !editingFileContent.includes(SLIDESHOW_ISLAND_TYPE2)) return false;
60505
+ return slideshowIslandRegex2("i").test(editingFileContent);
60506
+ }, [editingFileContent]);
60507
+ const slideshowScenes = useMemo46(() => {
60508
+ try {
60509
+ const win = previewIframeRef.current?.contentWindow;
60510
+ return (win?.__clipManifest?.scenes ?? []).map((s) => ({
60511
+ id: s.id,
60512
+ label: s.label,
60513
+ start: s.start,
60514
+ duration: s.duration
60515
+ }));
60516
+ } catch {
60517
+ return [];
60518
+ }
60519
+ }, [previewIframeRef, rightPanelTab, refreshKey]);
60520
+ useEffect89(() => {
60521
+ if (rightPanelTab === "slideshow" && !isSlideshowComposition) {
60522
+ setRightPanelTab("renders");
60523
+ }
60524
+ }, [rightPanelTab, isSlideshowComposition, setRightPanelTab]);
60525
+ return { isSlideshowComposition, slideshowScenes };
60526
+ }
60527
+
60437
60528
  // src/components/DesignPanelPromoteProvider.tsx
60438
- import { useCallback as useCallback132 } from "react";
60529
+ import { useCallback as useCallback133 } from "react";
60439
60530
  import { jsx as jsx143 } from "react/jsx-runtime";
60440
60531
  function DesignPanelPromoteProvider({
60441
60532
  selection,
60442
60533
  projectId,
60443
60534
  activeCompPath,
60444
60535
  showToast,
60536
+ forceReloadSharedSdkSession,
60445
60537
  children,
60446
60538
  ...persistDeps
60447
60539
  }) {
60448
60540
  const targetPath = selection?.sourceFile || activeCompPath || "index.html";
60449
60541
  const handle = useSdkSession(projectId, targetPath, persistDeps.domEditSaveTimestampRef);
60450
- const persist2 = useVariablesPersist({
60542
+ const rawPersist = useVariablesPersist({
60451
60543
  ...persistDeps,
60452
60544
  sdkSession: handle.session,
60453
60545
  publishSdkSession: handle.publish,
60454
60546
  activeCompPath: targetPath
60455
60547
  });
60456
- const handlePersistError = useCallback132(
60548
+ const persist2 = useCallback133(
60549
+ async (label, mutate) => {
60550
+ const committed = await rawPersist(label, mutate);
60551
+ if (committed) forceReloadSharedSdkSession?.();
60552
+ return committed;
60553
+ },
60554
+ [rawPersist, forceReloadSharedSdkSession]
60555
+ );
60556
+ const handlePersistError = useCallback133(
60457
60557
  (error) => showToast(getStudioSaveErrorMessage(error), "error"),
60458
60558
  [showToast]
60459
60559
  );
@@ -60688,14 +60788,14 @@ async function applyColorGradingScopeUpdate({
60688
60788
  }
60689
60789
 
60690
60790
  // src/hooks/useInspectorSplitResize.ts
60691
- import { useCallback as useCallback133, useRef as useRef113, useState as useState110 } from "react";
60791
+ import { useCallback as useCallback134, useRef as useRef114, useState as useState110 } from "react";
60692
60792
  var MIN_INSPECTOR_SPLIT_PERCENT = 20;
60693
60793
  var MAX_INSPECTOR_SPLIT_PERCENT = 75;
60694
60794
  function useInspectorSplitResize() {
60695
60795
  const [layersPanePercent, setLayersPanePercent] = useState110(40);
60696
- const splitContainerRef = useRef113(null);
60697
- const splitDragRef = useRef113(null);
60698
- const handleInspectorSplitResizeStart = useCallback133(
60796
+ const splitContainerRef = useRef114(null);
60797
+ const splitDragRef = useRef114(null);
60798
+ const handleInspectorSplitResizeStart = useCallback134(
60699
60799
  (event) => {
60700
60800
  event.preventDefault();
60701
60801
  event.currentTarget.setPointerCapture(event.pointerId);
@@ -60708,7 +60808,7 @@ function useInspectorSplitResize() {
60708
60808
  },
60709
60809
  [layersPanePercent]
60710
60810
  );
60711
- const handleInspectorSplitResizeMove = useCallback133((event) => {
60811
+ const handleInspectorSplitResizeMove = useCallback134((event) => {
60712
60812
  const drag = splitDragRef.current;
60713
60813
  if (!drag || drag.height <= 0) return;
60714
60814
  const deltaPercent = (event.clientY - drag.startY) / drag.height * 100;
@@ -60718,7 +60818,7 @@ function useInspectorSplitResize() {
60718
60818
  );
60719
60819
  setLayersPanePercent(next);
60720
60820
  }, []);
60721
- const handleInspectorSplitResizeEnd = useCallback133(() => {
60821
+ const handleInspectorSplitResizeEnd = useCallback134(() => {
60722
60822
  splitDragRef.current = null;
60723
60823
  }, []);
60724
60824
  return {
@@ -60741,6 +60841,7 @@ function StudioRightPanel({
60741
60841
  onToggleRecording,
60742
60842
  sdkSession,
60743
60843
  publishSdkSession,
60844
+ forceReloadSdkSession,
60744
60845
  reloadPreview,
60745
60846
  domEditSaveTimestampRef,
60746
60847
  recordEdit,
@@ -60820,7 +60921,8 @@ function StudioRightPanel({
60820
60921
  refreshFileTree,
60821
60922
  readProjectFile,
60822
60923
  writeProjectFile,
60823
- fileTree
60924
+ fileTree,
60925
+ editingFile
60824
60926
  } = useFileManagerContext();
60825
60927
  const onPersistSlideshow = useSlideshowPersist({
60826
60928
  sdkSession,
@@ -60850,8 +60952,8 @@ function StudioRightPanel({
60850
60952
  handleInspectorSplitResizeMove,
60851
60953
  handleInspectorSplitResizeEnd
60852
60954
  } = useInspectorSplitResize();
60853
- const backgroundRemovalAbortRef = useRef114(null);
60854
- useEffect89(
60955
+ const backgroundRemovalAbortRef = useRef115(null);
60956
+ useEffect90(
60855
60957
  () => () => {
60856
60958
  backgroundRemovalAbortRef.current?.abort();
60857
60959
  },
@@ -60859,19 +60961,13 @@ function StudioRightPanel({
60859
60961
  );
60860
60962
  const renderJobs = renderQueue.jobs;
60861
60963
  const inspectorTabActive = rightPanelTab === "design" || rightPanelTab === "layers";
60862
- const slideshowScenes = useMemo46(() => {
60863
- try {
60864
- const win = previewIframeRef.current?.contentWindow;
60865
- return (win?.__clipManifest?.scenes ?? []).map((s) => ({
60866
- id: s.id,
60867
- label: s.label,
60868
- start: s.start,
60869
- duration: s.duration
60870
- }));
60871
- } catch {
60872
- return [];
60873
- }
60874
- }, [previewIframeRef, rightPanelTab, refreshKey]);
60964
+ const { isSlideshowComposition, slideshowScenes } = useSlideshowTabState({
60965
+ editingFileContent: editingFile?.content,
60966
+ previewIframeRef,
60967
+ refreshKey,
60968
+ rightPanelTab,
60969
+ setRightPanelTab
60970
+ });
60875
60971
  const designPaneOpen = inspectorTabActive && rightInspectorPanes.design && designPanelActive;
60876
60972
  const layersPaneOpen = inspectorTabActive && rightInspectorPanes.layers && STUDIO_INSPECTOR_PANELS_ENABLED;
60877
60973
  const handleInspectorPaneButtonClick = (pane) => {
@@ -60885,7 +60981,7 @@ function StudioRightPanel({
60885
60981
  }
60886
60982
  toggleRightInspectorPane(pane);
60887
60983
  };
60888
- const handleApplyColorGradingScope = useCallback134(
60984
+ const handleApplyColorGradingScope = useCallback135(
60889
60985
  async (scope, value) => applyColorGradingScopeUpdate({
60890
60986
  scope,
60891
60987
  value,
@@ -60920,7 +61016,7 @@ function StudioRightPanel({
60920
61016
  writeProjectFile
60921
61017
  ]
60922
61018
  );
60923
- const handleRemoveBackground = useCallback134(
61019
+ const handleRemoveBackground = useCallback135(
60924
61020
  // fallow-ignore-next-line complexity
60925
61021
  async (inputPath, options) => {
60926
61022
  const response = await fetch(
@@ -60973,6 +61069,7 @@ function StudioRightPanel({
60973
61069
  recordEdit,
60974
61070
  reloadPreview,
60975
61071
  domEditSaveTimestampRef,
61072
+ forceReloadSharedSdkSession: forceReloadSdkSession,
60976
61073
  children: /* @__PURE__ */ jsx144(
60977
61074
  PropertyPanel,
60978
61075
  {
@@ -61129,7 +61226,7 @@ function StudioRightPanel({
61129
61226
  onClick: () => setRightPanelTab("renders")
61130
61227
  }
61131
61228
  ),
61132
- /* @__PURE__ */ jsx144(
61229
+ isSlideshowComposition && /* @__PURE__ */ jsx144(
61133
61230
  PanelTabButton,
61134
61231
  {
61135
61232
  label: "Slideshow",
@@ -61158,7 +61255,7 @@ function StudioRightPanel({
61158
61255
  onClose: onCloseBlockParams ?? (() => {
61159
61256
  })
61160
61257
  }
61161
- ) : rightPanelTab === "slideshow" ? /* @__PURE__ */ jsx144(
61258
+ ) : rightPanelTab === "slideshow" && isSlideshowComposition ? /* @__PURE__ */ jsx144(
61162
61259
  SlideshowPanel,
61163
61260
  {
61164
61261
  scenes: slideshowScenes,
@@ -61224,11 +61321,11 @@ function StudioRightPanel({
61224
61321
  }
61225
61322
 
61226
61323
  // src/components/TimelineToolbar.tsx
61227
- import { useEffect as useEffect91, useRef as useRef115 } from "react";
61324
+ import { useEffect as useEffect92, useRef as useRef116 } from "react";
61228
61325
  import { Magnet, MagnifyingGlassMinus, MagnifyingGlassPlus } from "@phosphor-icons/react";
61229
61326
 
61230
61327
  // src/hooks/useEnableKeyframes.ts
61231
- import { useCallback as useCallback135 } from "react";
61328
+ import { useCallback as useCallback136 } from "react";
61232
61329
  var enableKeyframesTransactionCounter = 0;
61233
61330
  function animatedProps(anim) {
61234
61331
  if (!anim) return ["x", "y"];
@@ -61445,7 +61542,7 @@ async function applyArcWaypointAtPlayhead(session, sel, arcAnim, t, iframe) {
61445
61542
  );
61446
61543
  }
61447
61544
  function useEnableKeyframes(sessionRef) {
61448
- return useCallback135(async () => {
61545
+ return useCallback136(async () => {
61449
61546
  const session = sessionRef.current;
61450
61547
  if (!session) return;
61451
61548
  const sel = session.domEditSelection;
@@ -61527,7 +61624,7 @@ function useEnableKeyframes(sessionRef) {
61527
61624
  }
61528
61625
 
61529
61626
  // src/hooks/useKeyframeKeyboard.ts
61530
- import { useEffect as useEffect90, useCallback as useCallback136 } from "react";
61627
+ import { useEffect as useEffect91, useCallback as useCallback137 } from "react";
61531
61628
  function isTextInput(el) {
61532
61629
  if (!el) return false;
61533
61630
  const tag = el.tagName;
@@ -61544,7 +61641,7 @@ function useKeyframeKeyboard({
61544
61641
  onToggleExpand,
61545
61642
  onNudgeKeyframe
61546
61643
  }) {
61547
- const handler = useCallback136(
61644
+ const handler = useCallback137(
61548
61645
  (e) => {
61549
61646
  if (!enabled2) return;
61550
61647
  if (isTextInput(document.activeElement)) return;
@@ -61595,7 +61692,7 @@ function useKeyframeKeyboard({
61595
61692
  onNudgeKeyframe
61596
61693
  ]
61597
61694
  );
61598
- useEffect90(() => {
61695
+ useEffect91(() => {
61599
61696
  if (!enabled2) return;
61600
61697
  window.addEventListener("keydown", handler, { capture: true });
61601
61698
  return () => window.removeEventListener("keydown", handler, { capture: true });
@@ -61606,7 +61703,7 @@ function useKeyframeKeyboard({
61606
61703
  import { Fragment as Fragment45, jsx as jsx145, jsxs as jsxs125 } from "react/jsx-runtime";
61607
61704
  function useKeyframeToggle(session) {
61608
61705
  const currentTime = usePlayerStore((s) => s.currentTime);
61609
- const sessionRef = useRef115(session);
61706
+ const sessionRef = useRef116(session);
61610
61707
  sessionRef.current = session;
61611
61708
  const onToggle = useEnableKeyframes(
61612
61709
  sessionRef
@@ -61650,7 +61747,7 @@ function TimelineToolbar({ domEditSession, onSplitElement }) {
61650
61747
  enabled: STUDIO_KEYFRAMES_ENABLED && Boolean(onToggleKeyframe),
61651
61748
  onAddKeyframe: onToggleKeyframe
61652
61749
  });
61653
- useEffect91(() => {
61750
+ useEffect92(() => {
61654
61751
  const onKeyDown = (e) => {
61655
61752
  if (e.key !== "n" && e.key !== "N") return;
61656
61753
  if (e.metaKey || e.ctrlKey || e.altKey) return;
@@ -61908,20 +62005,20 @@ function TimelineToolbar({ domEditSession, onSplitElement }) {
61908
62005
  }
61909
62006
 
61910
62007
  // src/components/storyboard/StoryboardView.tsx
61911
- import { useState as useState117 } from "react";
62008
+ import { useState as useState118 } from "react";
61912
62009
  import { Copy as Copy2, Check as Check2 } from "@phosphor-icons/react";
61913
62010
 
61914
62011
  // src/hooks/useStoryboard.ts
61915
- import { useCallback as useCallback137, useEffect as useEffect92, useRef as useRef116, useState as useState111 } from "react";
62012
+ import { useCallback as useCallback138, useEffect as useEffect93, useRef as useRef117, useState as useState111 } from "react";
61916
62013
  function useStoryboard(projectId) {
61917
62014
  const [data, setData] = useState111(null);
61918
62015
  const [loading, setLoading] = useState111(true);
61919
62016
  const [error, setError] = useState111(null);
61920
62017
  const [reloadKey, setReloadKey] = useState111(0);
61921
- const hasDataRef = useRef116(false);
61922
- const lastProjectRef = useRef116(null);
61923
- const reload = useCallback137(() => setReloadKey((k) => k + 1), []);
61924
- useEffect92(() => {
62018
+ const hasDataRef = useRef117(false);
62019
+ const lastProjectRef = useRef117(null);
62020
+ const reload = useCallback138(() => setReloadKey((k) => k + 1), []);
62021
+ useEffect93(() => {
61925
62022
  if (!projectId) return;
61926
62023
  let cancelled = false;
61927
62024
  if (lastProjectRef.current !== projectId) {
@@ -61953,7 +62050,7 @@ function useStoryboard(projectId) {
61953
62050
  }
61954
62051
 
61955
62052
  // src/hooks/useProjectSignaturePoll.ts
61956
- import { useEffect as useEffect93, useRef as useRef117 } from "react";
62053
+ import { useEffect as useEffect94, useRef as useRef118 } from "react";
61957
62054
  var POLL_INTERVAL_MS = 2e3;
61958
62055
  async function fetchProjectSignature(projectId) {
61959
62056
  try {
@@ -61966,11 +62063,11 @@ async function fetchProjectSignature(projectId) {
61966
62063
  }
61967
62064
  }
61968
62065
  function useProjectSignaturePoll(projectId, currentSignature, onChange) {
61969
- const signatureRef = useRef117(currentSignature);
61970
- const onChangeRef = useRef117(onChange);
62066
+ const signatureRef = useRef118(currentSignature);
62067
+ const onChangeRef = useRef118(onChange);
61971
62068
  signatureRef.current = currentSignature;
61972
62069
  onChangeRef.current = onChange;
61973
- useEffect93(() => {
62070
+ useEffect94(() => {
61974
62071
  if (!projectId) return;
61975
62072
  let disposed = false;
61976
62073
  let inFlight = false;
@@ -61997,7 +62094,7 @@ function useProjectSignaturePoll(projectId, currentSignature, onChange) {
61997
62094
  }
61998
62095
 
61999
62096
  // src/components/storyboard/StoryboardLoaded.tsx
62000
- import { useEffect as useEffect98, useMemo as useMemo49, useState as useState116 } from "react";
62097
+ import { useEffect as useEffect100, useMemo as useMemo49, useState as useState117 } from "react";
62001
62098
 
62002
62099
  // src/components/storyboard/StoryboardDirection.tsx
62003
62100
  import { jsx as jsx146, jsxs as jsxs126 } from "react/jsx-runtime";
@@ -62020,7 +62117,7 @@ function StoryboardDirection({ globals, frameCount }) {
62020
62117
  }
62021
62118
 
62022
62119
  // src/components/storyboard/FramePoster.tsx
62023
- import { useEffect as useEffect94, useState as useState112 } from "react";
62120
+ import { useEffect as useEffect95, useState as useState112 } from "react";
62024
62121
  import { jsx as jsx147 } from "react/jsx-runtime";
62025
62122
  function FramePoster({
62026
62123
  projectId,
@@ -62031,7 +62128,7 @@ function FramePoster({
62031
62128
  posterVersion
62032
62129
  }) {
62033
62130
  const [failed2, setFailed] = useState112(false);
62034
- useEffect94(() => setFailed(false), [src, seconds, posterVersion]);
62131
+ useEffect95(() => setFailed(false), [src, seconds, posterVersion]);
62035
62132
  if (failed2) {
62036
62133
  return /* @__PURE__ */ jsx147("div", { className: "flex h-full w-full items-center justify-center text-[11px] text-neutral-600", children: "Preview unavailable" });
62037
62134
  }
@@ -62216,41 +62313,24 @@ function StoryboardGrid({
62216
62313
  )) });
62217
62314
  }
62218
62315
 
62219
- // src/components/storyboard/StoryboardStatusLegend.tsx
62220
- import { jsx as jsx150, jsxs as jsxs128 } from "react/jsx-runtime";
62221
- function StoryboardStatusLegend() {
62222
- return /* @__PURE__ */ jsxs128("div", { className: "flex flex-wrap items-center gap-x-5 gap-y-1.5 text-[11px] text-neutral-500", children: [
62223
- /* @__PURE__ */ jsx150("span", { className: "uppercase tracking-wider text-neutral-600", children: "Status" }),
62224
- FRAME_STATUS_ORDER.map((status, i) => /* @__PURE__ */ jsxs128("span", { className: "flex items-center gap-1.5", children: [
62225
- i > 0 && /* @__PURE__ */ jsx150("span", { className: "text-neutral-700", children: "\u2192" }),
62226
- /* @__PURE__ */ jsx150("span", { className: `h-2 w-2 rounded-full ${FRAME_STATUS_META[status].dotClass}` }),
62227
- /* @__PURE__ */ jsx150("span", { className: "font-medium text-neutral-300", children: FRAME_STATUS_META[status].label }),
62228
- /* @__PURE__ */ jsxs128("span", { className: "text-neutral-500", children: [
62229
- "\u2014 ",
62230
- FRAME_STATUS_META[status].description
62231
- ] })
62232
- ] }, status))
62233
- ] });
62234
- }
62235
-
62236
62316
  // src/components/storyboard/StoryboardScriptPanel.tsx
62237
- import { jsx as jsx151, jsxs as jsxs129 } from "react/jsx-runtime";
62317
+ import { jsx as jsx150, jsxs as jsxs128 } from "react/jsx-runtime";
62238
62318
  function StoryboardScriptPanel({ script }) {
62239
62319
  if (!script.exists) return null;
62240
- return /* @__PURE__ */ jsxs129("details", { className: "mt-10 rounded-lg border border-neutral-800 bg-neutral-900/50", children: [
62241
- /* @__PURE__ */ jsxs129("summary", { className: "cursor-pointer select-none px-4 py-3 text-sm font-medium text-neutral-300", children: [
62320
+ return /* @__PURE__ */ jsxs128("details", { className: "mt-10 rounded-lg border border-neutral-800 bg-neutral-900/50", children: [
62321
+ /* @__PURE__ */ jsxs128("summary", { className: "cursor-pointer select-none px-4 py-3 text-sm font-medium text-neutral-300", children: [
62242
62322
  "Narration script",
62243
- /* @__PURE__ */ jsx151("span", { className: "ml-2 font-normal text-neutral-500", children: script.path })
62323
+ /* @__PURE__ */ jsx150("span", { className: "ml-2 font-normal text-neutral-500", children: script.path })
62244
62324
  ] }),
62245
- /* @__PURE__ */ jsx151("pre", { className: "max-h-[420px] overflow-auto whitespace-pre-wrap border-t border-neutral-800 px-4 py-3 text-xs leading-relaxed text-neutral-400", children: script.content })
62325
+ /* @__PURE__ */ jsx150("pre", { className: "max-h-[420px] overflow-auto whitespace-pre-wrap border-t border-neutral-800 px-4 py-3 text-xs leading-relaxed text-neutral-400", children: script.content })
62246
62326
  ] });
62247
62327
  }
62248
62328
 
62249
62329
  // src/components/storyboard/StoryboardSourceEditor.tsx
62250
- import { useCallback as useCallback138, useEffect as useEffect95, useMemo as useMemo47, useRef as useRef118, useState as useState113 } from "react";
62330
+ import { useCallback as useCallback139, useEffect as useEffect96, useMemo as useMemo47, useRef as useRef119, useState as useState113 } from "react";
62251
62331
  import { marked } from "marked";
62252
62332
  import DOMPurify from "dompurify";
62253
- import { jsx as jsx152, jsxs as jsxs130 } from "react/jsx-runtime";
62333
+ import { jsx as jsx151, jsxs as jsxs129 } from "react/jsx-runtime";
62254
62334
  var DISCARD_PROMPT = "Discard unsaved markdown changes?";
62255
62335
  function useEditableFile(path, onSaved) {
62256
62336
  const { readProjectFile, writeProjectFile } = useFileManagerContext();
@@ -62259,7 +62339,7 @@ function useEditableFile(path, onSaved) {
62259
62339
  const [loading, setLoading] = useState113(true);
62260
62340
  const [saving, setSaving] = useState113(false);
62261
62341
  const [error, setError] = useState113(null);
62262
- useEffect95(() => {
62342
+ useEffect96(() => {
62263
62343
  if (!path) return;
62264
62344
  let cancelled = false;
62265
62345
  setLoading(true);
@@ -62277,7 +62357,7 @@ function useEditableFile(path, onSaved) {
62277
62357
  cancelled = true;
62278
62358
  };
62279
62359
  }, [path, readProjectFile]);
62280
- const save = useCallback138(() => {
62360
+ const save = useCallback139(() => {
62281
62361
  if (saving) return;
62282
62362
  setSaving(true);
62283
62363
  setError(null);
@@ -62296,8 +62376,8 @@ function hardenLinks(node) {
62296
62376
  }
62297
62377
  function useMarkdownPreview(source) {
62298
62378
  const [debounced, setDebounced] = useState113(source);
62299
- const primed = useRef118(false);
62300
- useEffect95(() => {
62379
+ const primed = useRef119(false);
62380
+ useEffect96(() => {
62301
62381
  if (!primed.current && source !== "") {
62302
62382
  primed.current = true;
62303
62383
  setDebounced(source);
@@ -62329,8 +62409,8 @@ function StoryboardSourceEditor({
62329
62409
  const activePath = files.some((f) => f.path === selected) ? selected : files[0]?.path ?? "";
62330
62410
  const file = useEditableFile(activePath, onSaved);
62331
62411
  const previewHtml = useMarkdownPreview(file.content);
62332
- useEffect95(() => onDirtyChange?.(file.dirty), [onDirtyChange, file.dirty]);
62333
- useEffect95(() => {
62412
+ useEffect96(() => onDirtyChange?.(file.dirty), [onDirtyChange, file.dirty]);
62413
+ useEffect96(() => {
62334
62414
  if (!file.dirty) return;
62335
62415
  const onBeforeUnload = (event) => {
62336
62416
  event.preventDefault();
@@ -62344,7 +62424,7 @@ function StoryboardSourceEditor({
62344
62424
  if (file.dirty && !window.confirm(DISCARD_PROMPT)) return;
62345
62425
  setSelected(path);
62346
62426
  };
62347
- return /* @__PURE__ */ jsxs130(
62427
+ return /* @__PURE__ */ jsxs129(
62348
62428
  "div",
62349
62429
  {
62350
62430
  className: "flex flex-1 min-h-0 flex-col",
@@ -62354,8 +62434,8 @@ function StoryboardSourceEditor({
62354
62434
  if (file.dirty && !file.saving) file.save();
62355
62435
  },
62356
62436
  children: [
62357
- /* @__PURE__ */ jsxs130("div", { className: "flex items-center gap-1 border-b border-neutral-800 px-4 py-2", children: [
62358
- files.map((f) => /* @__PURE__ */ jsx152(
62437
+ /* @__PURE__ */ jsxs129("div", { className: "flex items-center gap-1 border-b border-neutral-800 px-4 py-2", children: [
62438
+ files.map((f) => /* @__PURE__ */ jsx151(
62359
62439
  "button",
62360
62440
  {
62361
62441
  type: "button",
@@ -62365,10 +62445,10 @@ function StoryboardSourceEditor({
62365
62445
  },
62366
62446
  f.path
62367
62447
  )),
62368
- /* @__PURE__ */ jsxs130("div", { className: "ml-auto flex items-center gap-3", children: [
62369
- file.error && /* @__PURE__ */ jsx152("span", { className: "text-xs text-red-400", children: file.error }),
62370
- /* @__PURE__ */ jsx152("span", { className: "text-xs text-neutral-500", children: file.saving ? "Saving\u2026" : file.dirty ? "Unsaved changes" : "Saved" }),
62371
- /* @__PURE__ */ jsx152(
62448
+ /* @__PURE__ */ jsxs129("div", { className: "ml-auto flex items-center gap-3", children: [
62449
+ file.error && /* @__PURE__ */ jsx151("span", { className: "text-xs text-red-400", children: file.error }),
62450
+ /* @__PURE__ */ jsx151("span", { className: "text-xs text-neutral-500", children: file.saving ? "Saving\u2026" : file.dirty ? "Unsaved changes" : "Saved" }),
62451
+ /* @__PURE__ */ jsx151(
62372
62452
  "button",
62373
62453
  {
62374
62454
  type: "button",
@@ -62380,12 +62460,12 @@ function StoryboardSourceEditor({
62380
62460
  )
62381
62461
  ] })
62382
62462
  ] }),
62383
- /* @__PURE__ */ jsxs130("div", { className: "flex flex-1 min-h-0", children: [
62384
- /* @__PURE__ */ jsx152("div", { className: "w-1/2 min-w-0 border-r border-neutral-800", children: file.loading ? /* @__PURE__ */ jsxs130("div", { className: "p-4 text-sm text-neutral-500", children: [
62463
+ /* @__PURE__ */ jsxs129("div", { className: "flex flex-1 min-h-0", children: [
62464
+ /* @__PURE__ */ jsx151("div", { className: "w-1/2 min-w-0 border-r border-neutral-800", children: file.loading ? /* @__PURE__ */ jsxs129("div", { className: "p-4 text-sm text-neutral-500", children: [
62385
62465
  "Loading ",
62386
62466
  activePath,
62387
62467
  "\u2026"
62388
- ] }) : /* @__PURE__ */ jsx152(
62468
+ ] }) : /* @__PURE__ */ jsx151(
62389
62469
  SourceEditor,
62390
62470
  {
62391
62471
  content: file.content,
@@ -62394,7 +62474,7 @@ function StoryboardSourceEditor({
62394
62474
  onChange: file.setContent
62395
62475
  }
62396
62476
  ) }),
62397
- /* @__PURE__ */ jsx152("div", { className: "w-1/2 min-w-0 overflow-auto bg-neutral-950 px-6 py-4", children: /* @__PURE__ */ jsx152("div", { className: PREVIEW_PROSE, dangerouslySetInnerHTML: { __html: previewHtml } }) })
62477
+ /* @__PURE__ */ jsx151("div", { className: "w-1/2 min-w-0 overflow-auto bg-neutral-950 px-6 py-4", children: /* @__PURE__ */ jsx151("div", { className: PREVIEW_PROSE, dangerouslySetInnerHTML: { __html: previewHtml } }) })
62398
62478
  ] })
62399
62479
  ]
62400
62480
  }
@@ -62402,9 +62482,38 @@ function StoryboardSourceEditor({
62402
62482
  }
62403
62483
 
62404
62484
  // src/components/storyboard/StoryboardFrameFocus.tsx
62405
- import { useCallback as useCallback139, useEffect as useEffect96, useState as useState114 } from "react";
62406
- import { setFrameStatus, setFrameVoiceover } from "@hyperframes/core/storyboard";
62407
- import { jsx as jsx153, jsxs as jsxs131 } from "react/jsx-runtime";
62485
+ import { useCallback as useCallback140, useEffect as useEffect98, useState as useState115 } from "react";
62486
+ import { setFrameVoiceover } from "@hyperframes/core/storyboard";
62487
+
62488
+ // src/components/storyboard/AgentChatMessageButton.tsx
62489
+ import { useEffect as useEffect97, useState as useState114 } from "react";
62490
+ import { jsx as jsx152 } from "react/jsx-runtime";
62491
+ var APPLY_STORYBOARD_FEEDBACK_MESSAGE = "Read the storyboard feedback I saved in .hyperframes/frame-comments.json and revise the frames.";
62492
+ function AgentChatMessageButton({
62493
+ message,
62494
+ label = "Copy prompt for agent",
62495
+ onCopied
62496
+ }) {
62497
+ const [copyState, setCopyState] = useState114("idle");
62498
+ useEffect97(() => {
62499
+ if (copyState !== "copied") return;
62500
+ const timeout = window.setTimeout(() => setCopyState("again"), 3e3);
62501
+ return () => window.clearTimeout(timeout);
62502
+ }, [copyState]);
62503
+ const copyMessage = async () => {
62504
+ try {
62505
+ await navigator.clipboard.writeText(message);
62506
+ setCopyState("copied");
62507
+ onCopied?.();
62508
+ } catch {
62509
+ setCopyState("failed");
62510
+ }
62511
+ };
62512
+ return /* @__PURE__ */ jsx152(Button, { size: "sm", variant: "secondary", onClick: () => void copyMessage(), children: copyState === "copied" ? "Copied \u2014 paste in your agent chat" : copyState === "again" ? "Copy again" : copyState === "failed" ? "Copy failed" : label });
62513
+ }
62514
+
62515
+ // src/components/storyboard/StoryboardFrameFocus.tsx
62516
+ import { jsx as jsx153, jsxs as jsxs130 } from "react/jsx-runtime";
62408
62517
  function StoryboardFrameFocus({
62409
62518
  projectId,
62410
62519
  storyboardPath,
@@ -62414,24 +62523,38 @@ function StoryboardFrameFocus({
62414
62523
  onNavigate,
62415
62524
  onSaved,
62416
62525
  onSelectComposition,
62526
+ scriptExists,
62527
+ commentDraft,
62528
+ onCommentDraftChange,
62529
+ pendingComment,
62530
+ pendingCommentCount,
62531
+ commentDraftCount,
62532
+ commentsSubmitState,
62533
+ commentsSubmitError,
62534
+ feedbackMessageCopied,
62535
+ onFeedbackMessageCopied,
62536
+ onSaveFeedback,
62417
62537
  posterVersion
62418
62538
  }) {
62419
62539
  const { readProjectFile, writeProjectFile } = useFileManagerContext();
62420
- const { setViewMode } = useViewMode();
62421
- const [draft, setDraft] = useState114(frame.voiceover ?? "");
62422
- const [busy, setBusy] = useState114(false);
62423
- const [error, setError] = useState114(null);
62424
- const applyEdit = useCallback139(
62540
+ const { setViewMode, registerViewModeGuard } = useViewMode();
62541
+ const [draft, setDraft] = useState115(frame.voiceover ?? "");
62542
+ const [savedVoiceover, setSavedVoiceover] = useState115(frame.voiceover ?? "");
62543
+ const [busy, setBusy] = useState115(false);
62544
+ const [error, setError] = useState115(null);
62545
+ const applyEdit = useCallback140(
62425
62546
  async (edit) => {
62426
- if (busy) return;
62547
+ if (busy) return false;
62427
62548
  setBusy(true);
62428
62549
  setError(null);
62429
62550
  try {
62430
62551
  const source = await readProjectFile(storyboardPath);
62431
62552
  await writeProjectFile(storyboardPath, edit(source));
62432
62553
  onSaved();
62554
+ return true;
62433
62555
  } catch (err) {
62434
62556
  setError(err instanceof Error ? err.message : "failed to save");
62557
+ return false;
62435
62558
  } finally {
62436
62559
  setBusy(false);
62437
62560
  }
@@ -62439,12 +62562,13 @@ function StoryboardFrameFocus({
62439
62562
  [readProjectFile, writeProjectFile, storyboardPath, onSaved, busy]
62440
62563
  );
62441
62564
  const title = frame.title ?? `Frame ${frame.index}`;
62442
- const dirty = draft !== (frame.voiceover ?? "");
62443
- const canOpenPreview = frame.srcExists && Boolean(frame.src);
62444
- const saveVoiceover = useCallback139(() => {
62445
- return applyEdit((src) => setFrameVoiceover(src, frame.index, draft));
62565
+ const dirty = draft !== savedVoiceover;
62566
+ const canOpenPreview = frame.status !== "outline" && frame.srcExists && Boolean(frame.src);
62567
+ const saveVoiceover = useCallback140(async () => {
62568
+ const saved = await applyEdit((src) => setFrameVoiceover(src, frame.index, draft));
62569
+ if (saved) setSavedVoiceover(draft);
62446
62570
  }, [applyEdit, frame.index, draft]);
62447
- useEffect96(() => {
62571
+ useEffect98(() => {
62448
62572
  if (!dirty) return;
62449
62573
  const onBeforeUnload = (e) => {
62450
62574
  e.preventDefault();
@@ -62452,14 +62576,18 @@ function StoryboardFrameFocus({
62452
62576
  window.addEventListener("beforeunload", onBeforeUnload);
62453
62577
  return () => window.removeEventListener("beforeunload", onBeforeUnload);
62454
62578
  }, [dirty]);
62455
- const confirmLeave = () => !dirty || window.confirm("Discard unsaved voiceover changes?");
62579
+ const confirmLeave = useCallback140(
62580
+ () => !dirty || window.confirm("Discard unsaved voiceover changes?"),
62581
+ [dirty]
62582
+ );
62583
+ useEffect98(() => registerViewModeGuard(confirmLeave), [confirmLeave, registerViewModeGuard]);
62456
62584
  const handleBack = () => {
62457
62585
  if (confirmLeave()) onBack();
62458
62586
  };
62459
62587
  const handleNavigate = (delta) => {
62460
62588
  if (confirmLeave()) onNavigate(delta);
62461
62589
  };
62462
- useEffect96(() => {
62590
+ useEffect98(() => {
62463
62591
  const onKey = (e) => {
62464
62592
  const el = document.activeElement;
62465
62593
  if (el instanceof HTMLTextAreaElement || el instanceof HTMLInputElement) return;
@@ -62471,11 +62599,11 @@ function StoryboardFrameFocus({
62471
62599
  return () => window.removeEventListener("keydown", onKey);
62472
62600
  });
62473
62601
  const openInPreview = () => {
62602
+ if (!setViewMode("timeline")) return;
62474
62603
  if (frame.src) onSelectComposition(frame.src);
62475
- setViewMode("timeline");
62476
62604
  };
62477
- return /* @__PURE__ */ jsxs131("div", { className: "flex flex-1 min-h-0 flex-col bg-neutral-950 text-neutral-200", children: [
62478
- /* @__PURE__ */ jsxs131("div", { className: "flex items-center gap-3 border-b border-neutral-800 px-4 py-2", children: [
62605
+ return /* @__PURE__ */ jsxs130("div", { className: "flex w-full max-w-[100vw] flex-1 min-h-0 min-w-0 flex-col overflow-hidden bg-neutral-950 text-neutral-200", children: [
62606
+ /* @__PURE__ */ jsxs130("div", { className: "flex items-center gap-3 border-b border-neutral-800 px-4 py-2", children: [
62479
62607
  /* @__PURE__ */ jsx153(
62480
62608
  "button",
62481
62609
  {
@@ -62485,13 +62613,13 @@ function StoryboardFrameFocus({
62485
62613
  children: "\u2190 Board"
62486
62614
  }
62487
62615
  ),
62488
- /* @__PURE__ */ jsxs131("span", { className: "text-sm font-medium text-neutral-200", children: [
62616
+ /* @__PURE__ */ jsxs130("span", { className: "min-w-0 flex-1 truncate text-sm font-medium text-neutral-200", children: [
62489
62617
  "Frame ",
62490
62618
  frame.number ?? frame.index,
62491
62619
  " \u2014 ",
62492
62620
  title
62493
62621
  ] }),
62494
- /* @__PURE__ */ jsxs131("div", { className: "ml-auto flex items-center gap-1", children: [
62622
+ /* @__PURE__ */ jsxs130("div", { className: "flex shrink-0 items-center gap-1", children: [
62495
62623
  /* @__PURE__ */ jsx153(
62496
62624
  NavButton,
62497
62625
  {
@@ -62510,8 +62638,35 @@ function StoryboardFrameFocus({
62510
62638
  )
62511
62639
  ] })
62512
62640
  ] }),
62513
- /* @__PURE__ */ jsxs131("div", { className: "flex flex-1 min-h-0", children: [
62514
- /* @__PURE__ */ jsx153("div", { className: "flex w-3/5 min-w-0 items-center justify-center bg-neutral-900/40 p-8", children: /* @__PURE__ */ jsx153("div", { className: "aspect-video w-full max-w-[900px] overflow-hidden rounded-lg border border-neutral-800 bg-neutral-900", children: canOpenPreview && frame.src ? /* @__PURE__ */ jsx153(
62641
+ (commentDraftCount > 0 || pendingCommentCount > 0) && /* @__PURE__ */ jsxs130("div", { className: "flex flex-wrap items-center justify-between gap-2 border-b border-neutral-800 bg-neutral-900/80 px-4 py-2", children: [
62642
+ /* @__PURE__ */ jsxs130("div", { children: [
62643
+ /* @__PURE__ */ jsx153("div", { className: "text-xs font-medium text-neutral-200", children: commentDraftCount > 0 ? "Feedback ready to save" : "Feedback saved" }),
62644
+ /* @__PURE__ */ jsx153("div", { className: "text-[11px] text-neutral-500", children: commentDraftCount > 0 ? "Save this batch and copy the message for your agent." : feedbackMessageCopied ? "Message copied \u2014 paste it in your terminal or IDE agent chat." : "The agent has not been notified yet." })
62645
+ ] }),
62646
+ commentDraftCount > 0 ? /* @__PURE__ */ jsxs130(
62647
+ Button,
62648
+ {
62649
+ size: "sm",
62650
+ variant: "primary",
62651
+ onClick: onSaveFeedback,
62652
+ loading: commentsSubmitState === "saving",
62653
+ children: [
62654
+ "Save & copy message (",
62655
+ commentDraftCount,
62656
+ ")"
62657
+ ]
62658
+ }
62659
+ ) : /* @__PURE__ */ jsx153(
62660
+ AgentChatMessageButton,
62661
+ {
62662
+ message: APPLY_STORYBOARD_FEEDBACK_MESSAGE,
62663
+ label: feedbackMessageCopied ? "Copy again" : "Copy prompt for agent",
62664
+ onCopied: onFeedbackMessageCopied
62665
+ }
62666
+ )
62667
+ ] }),
62668
+ /* @__PURE__ */ jsxs130("div", { className: "flex flex-1 min-h-0 flex-col overflow-auto lg:flex-row lg:overflow-hidden", children: [
62669
+ /* @__PURE__ */ jsx153("div", { className: "flex w-full shrink-0 items-center justify-center bg-neutral-900/40 p-4 sm:p-8 lg:h-full lg:w-3/5", children: /* @__PURE__ */ jsx153("div", { className: "aspect-video w-full max-w-[900px] overflow-hidden rounded-lg border border-neutral-800 bg-neutral-900", children: canOpenPreview && frame.src ? /* @__PURE__ */ jsx153(
62515
62670
  FramePoster,
62516
62671
  {
62517
62672
  projectId,
@@ -62521,32 +62676,32 @@ function StoryboardFrameFocus({
62521
62676
  fit: "contain",
62522
62677
  posterVersion
62523
62678
  }
62524
- ) : /* @__PURE__ */ jsx153("div", { className: "flex h-full w-full items-center justify-center text-sm text-neutral-600", children: frame.status === "outline" ? "Not built yet" : "No preview" }) }) }),
62525
- /* @__PURE__ */ jsxs131("div", { className: "w-2/5 min-w-0 space-y-6 overflow-auto border-l border-neutral-800 px-6 py-5", children: [
62526
- /* @__PURE__ */ jsx153(
62527
- StatusRow,
62528
- {
62529
- status: frame.status,
62530
- busy,
62531
- onSet: (s) => applyEdit((src) => setFrameStatus(src, frame.index, s))
62532
- }
62533
- ),
62534
- /* @__PURE__ */ jsxs131("div", { className: "flex flex-wrap gap-x-6 gap-y-1 text-[11px] text-neutral-500", children: [
62535
- frame.duration && /* @__PURE__ */ jsxs131("span", { children: [
62679
+ ) : /* @__PURE__ */ jsx153(FramePlan, { frame }) }) }),
62680
+ /* @__PURE__ */ jsxs130("div", { className: "w-full shrink-0 space-y-6 border-t border-neutral-800 px-4 py-5 sm:px-6 lg:h-full lg:w-2/5 lg:overflow-auto lg:border-t-0 lg:border-l", children: [
62681
+ /* @__PURE__ */ jsx153(ReadOnlyStatus, { status: frame.status }),
62682
+ /* @__PURE__ */ jsxs130("div", { className: "flex flex-wrap gap-x-6 gap-y-1 text-[11px] text-neutral-500", children: [
62683
+ frame.duration && /* @__PURE__ */ jsxs130("span", { children: [
62536
62684
  "Duration ",
62537
62685
  frame.duration
62538
62686
  ] }),
62539
- frame.transitionIn && /* @__PURE__ */ jsxs131("span", { children: [
62687
+ frame.transitionIn && /* @__PURE__ */ jsxs130("span", { children: [
62540
62688
  "Transition ",
62541
62689
  frame.transitionIn
62542
62690
  ] })
62543
62691
  ] }),
62544
- /* @__PURE__ */ jsxs131("section", { children: [
62545
- /* @__PURE__ */ jsxs131("div", { className: "mb-1 flex items-center justify-between", children: [
62546
- /* @__PURE__ */ jsxs131("h3", { className: "text-xs font-semibold uppercase tracking-wider text-neutral-400", children: [
62547
- "\u{1F399} Voiceover ",
62548
- /* @__PURE__ */ jsx153("span", { className: "font-normal normal-case text-neutral-600", children: "guide" })
62549
- ] }),
62692
+ /* @__PURE__ */ jsxs130("section", { children: [
62693
+ /* @__PURE__ */ jsxs130("div", { className: "mb-1 flex items-center justify-between", children: [
62694
+ /* @__PURE__ */ jsxs130(
62695
+ "h3",
62696
+ {
62697
+ className: "text-xs font-semibold uppercase tracking-wider text-neutral-400",
62698
+ title: "Storyboard voiceover is a guide; SCRIPT.md is the final TTS source.",
62699
+ children: [
62700
+ "\u{1F399} Voiceover ",
62701
+ /* @__PURE__ */ jsx153("span", { className: "font-normal normal-case text-neutral-600", children: "guide" })
62702
+ ]
62703
+ }
62704
+ ),
62550
62705
  /* @__PURE__ */ jsx153(
62551
62706
  Button,
62552
62707
  {
@@ -62555,8 +62710,7 @@ function StoryboardFrameFocus({
62555
62710
  onClick: saveVoiceover,
62556
62711
  disabled: !dirty,
62557
62712
  loading: busy,
62558
- className: "bg-emerald-600 text-white enabled:hover:bg-emerald-500 shadow-none",
62559
- children: busy ? "Saving\u2026" : "Save"
62713
+ children: busy ? "Saving\u2026" : "Save voiceover"
62560
62714
  }
62561
62715
  )
62562
62716
  ] }),
@@ -62565,22 +62719,53 @@ function StoryboardFrameFocus({
62565
62719
  {
62566
62720
  value: draft,
62567
62721
  onChange: (e) => setDraft(e.target.value),
62568
- onBlur: () => {
62569
- if (dirty && !busy) void saveVoiceover();
62570
- },
62571
62722
  rows: 3,
62572
62723
  placeholder: "What the narrator says over this frame\u2026",
62573
62724
  className: "w-full resize-y rounded border border-neutral-800 bg-neutral-900 p-2 text-sm text-neutral-200 outline-none focus:border-neutral-600"
62574
62725
  }
62575
62726
  ),
62576
- /* @__PURE__ */ jsx153("p", { className: "mt-1 text-[11px] text-neutral-600", children: "A draft guide. SCRIPT.md locks the final narration that drives TTS." }),
62727
+ /* @__PURE__ */ jsx153("p", { className: "mt-1 text-[11px] text-neutral-600", children: dirty ? "Unsaved changes" : scriptExists ? "Saved. SCRIPT.md drives final TTS." : "Saved to STORYBOARD.md. This voiceover guides narration for the frame." }),
62577
62728
  error && /* @__PURE__ */ jsx153("p", { className: "mt-1 text-[11px] text-red-400", children: error })
62578
62729
  ] }),
62579
- frame.narrative && /* @__PURE__ */ jsxs131("section", { children: [
62730
+ /* @__PURE__ */ jsxs130("section", { children: [
62731
+ /* @__PURE__ */ jsx153("div", { className: "mb-1", children: /* @__PURE__ */ jsx153("h3", { className: "text-xs font-semibold uppercase tracking-wider text-neutral-400", children: "Frame feedback" }) }),
62732
+ /* @__PURE__ */ jsx153(
62733
+ "textarea",
62734
+ {
62735
+ value: commentDraft,
62736
+ onChange: (e) => onCommentDraftChange(e.target.value),
62737
+ rows: 3,
62738
+ placeholder: "Tell your agent what to change in this frame\u2026",
62739
+ "aria-label": `Comment on ${title}`,
62740
+ className: "w-full resize-y rounded border border-neutral-800 bg-neutral-900 p-2 text-sm text-neutral-200 placeholder:text-neutral-600 outline-none focus:border-sky-700"
62741
+ }
62742
+ ),
62743
+ pendingCommentCount > 0 ? /* @__PURE__ */ jsxs130("div", { className: "mt-2 flex flex-wrap items-center justify-between gap-2 rounded-md border border-sky-900/70 bg-sky-950/20 px-2.5 py-2", children: [
62744
+ /* @__PURE__ */ jsx153("p", { className: "text-[11px] text-sky-200", children: "Paste the agent prompt in your terminal or IDE chat." }),
62745
+ /* @__PURE__ */ jsx153(
62746
+ AgentChatMessageButton,
62747
+ {
62748
+ message: APPLY_STORYBOARD_FEEDBACK_MESSAGE,
62749
+ onCopied: onFeedbackMessageCopied
62750
+ }
62751
+ )
62752
+ ] }) : /* @__PURE__ */ jsx153("p", { className: "mt-1 text-[11px] text-neutral-600", children: commentDraftCount > 0 ? "Your change is ready. Save it using the review bar above." : "Add a change to prepare feedback for the agent." }),
62753
+ pendingComment && /* @__PURE__ */ jsxs130("p", { className: "mt-1 text-[11px] text-sky-400/90", children: [
62754
+ /* @__PURE__ */ jsx153("span", { className: "font-medium", children: "Pending:" }),
62755
+ " \u201C",
62756
+ pendingComment,
62757
+ "\u201D"
62758
+ ] }),
62759
+ commentsSubmitError && /* @__PURE__ */ jsxs130("p", { className: "mt-1 text-[11px] text-red-400", children: [
62760
+ "Couldn\u2019t submit: ",
62761
+ commentsSubmitError
62762
+ ] })
62763
+ ] }),
62764
+ frame.narrative && /* @__PURE__ */ jsxs130("section", { children: [
62580
62765
  /* @__PURE__ */ jsx153("h3", { className: "mb-1 text-xs font-semibold uppercase tracking-wider text-neutral-400", children: "Narrative" }),
62581
62766
  /* @__PURE__ */ jsx153("p", { className: "whitespace-pre-wrap text-sm text-neutral-300", children: frame.narrative })
62582
62767
  ] }),
62583
- /* @__PURE__ */ jsx153(Button, { size: "sm", variant: "secondary", onClick: openInPreview, disabled: !canOpenPreview, children: "Open in Preview \u2192" })
62768
+ canOpenPreview ? /* @__PURE__ */ jsx153(Button, { size: "sm", variant: "secondary", onClick: openInPreview, children: "Open in Preview \u2192" }) : /* @__PURE__ */ jsx153("div", { className: "rounded-md border border-neutral-800 bg-neutral-900/60 px-3 py-2 text-xs text-neutral-500", children: "Preview becomes available after your agent builds this frame." })
62584
62769
  ] })
62585
62770
  ] })
62586
62771
  ] });
@@ -62601,31 +62786,280 @@ function NavButton({
62601
62786
  }
62602
62787
  );
62603
62788
  }
62604
- function StatusRow({
62605
- status,
62606
- busy,
62607
- onSet
62608
- }) {
62609
- return /* @__PURE__ */ jsxs131("div", { className: "flex items-center gap-2", children: [
62789
+ function ReadOnlyStatus({ status }) {
62790
+ const meta = FRAME_STATUS_META[status];
62791
+ return /* @__PURE__ */ jsxs130("div", { className: "flex items-center gap-2", children: [
62610
62792
  /* @__PURE__ */ jsx153("span", { className: "text-xs font-semibold uppercase tracking-wider text-neutral-500", children: "Status" }),
62611
- /* @__PURE__ */ jsx153("div", { className: "flex items-center gap-0.5 rounded-md bg-neutral-900 p-0.5", children: FRAME_STATUS_ORDER.map((option) => /* @__PURE__ */ jsx153(
62612
- "button",
62793
+ /* @__PURE__ */ jsx153("span", { className: `rounded px-2 py-1 text-xs font-medium ${meta.chipClass}`, children: meta.label }),
62794
+ /* @__PURE__ */ jsx153("span", { className: "text-[11px] text-neutral-600", children: "Updated by your agent" })
62795
+ ] });
62796
+ }
62797
+ function FramePlan({ frame }) {
62798
+ const title = frame.title ?? `Frame ${frame.index}`;
62799
+ const isOutline = frame.status === "outline";
62800
+ return /* @__PURE__ */ jsx153("div", { className: "flex h-full w-full items-center justify-center bg-[radial-gradient(circle_at_center,_rgba(38,38,38,0.8),_rgba(10,10,10,1))] p-10", children: /* @__PURE__ */ jsxs130("div", { className: "max-w-xl text-center", children: [
62801
+ /* @__PURE__ */ jsx153("span", { className: "rounded-full border border-neutral-700 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-wider text-neutral-400", children: isOutline ? "Planned frame" : "Preview unavailable" }),
62802
+ /* @__PURE__ */ jsx153("h2", { className: "mt-4 text-2xl font-semibold text-neutral-100", children: title }),
62803
+ frame.scene && /* @__PURE__ */ jsx153("p", { className: "mt-3 text-base leading-relaxed text-neutral-300", children: frame.scene }),
62804
+ !frame.scene && frame.narrative && /* @__PURE__ */ jsx153("p", { className: "mt-3 line-clamp-4 text-sm leading-relaxed text-neutral-400", children: frame.narrative }),
62805
+ /* @__PURE__ */ jsx153("p", { className: "mt-5 text-xs text-neutral-600", children: isOutline ? "A visual preview will appear when your agent builds the sketch." : frame.src ? `Frame file not found: ${frame.src}` : "This frame does not link to a source file." })
62806
+ ] }) });
62807
+ }
62808
+
62809
+ // src/components/storyboard/storyboardReviewStage.ts
62810
+ function deriveStoryboardHandoffStep(draftCount, pendingCount) {
62811
+ if (draftCount > 0) return 2;
62812
+ if (pendingCount > 0) return 3;
62813
+ return 1;
62814
+ }
62815
+ function deriveStoryboardReviewStage(frames) {
62816
+ const counts = { outline: 0, built: 0, animated: 0 };
62817
+ for (const frame of frames) counts[frame.status] += 1;
62818
+ let stage;
62819
+ if (frames.length === 0) stage = "empty";
62820
+ else if (counts.outline === frames.length) stage = "plan-review";
62821
+ else if (counts.outline > 0) stage = "sketch-in-progress";
62822
+ else if (counts.built === frames.length) stage = "sketch-review";
62823
+ else if (counts.built > 0) stage = "animation-in-progress";
62824
+ else stage = "final-review";
62825
+ return { stage, counts, frameCount: frames.length };
62826
+ }
62827
+
62828
+ // src/components/storyboard/StoryboardReviewGuide.tsx
62829
+ import { jsx as jsx154, jsxs as jsxs131 } from "react/jsx-runtime";
62830
+ var GUIDE_COPY = {
62831
+ empty: {
62832
+ eyebrow: "Waiting for a plan",
62833
+ title: "The storyboard has no frames yet",
62834
+ body: "Ask your agent to draft the story plan. Frames will appear here automatically."
62835
+ },
62836
+ "plan-review": {
62837
+ eyebrow: "Ready for review",
62838
+ title: "Review the story plan",
62839
+ body: "Check the sequence, scene direction, and voiceover before visual work begins. Leave frame comments, save them, then reply in your terminal or IDE agent chat."
62840
+ },
62841
+ "sketch-in-progress": {
62842
+ eyebrow: "Build in progress",
62843
+ title: "Visual sketches are in progress",
62844
+ body: "New posters appear automatically as your agent builds them. You can comment now; wait until no frames remain in Outline before approving the layouts."
62845
+ },
62846
+ "sketch-review": {
62847
+ eyebrow: "Ready for review",
62848
+ title: "Review the visual direction",
62849
+ body: "Check composition, hierarchy, and copy. Save frame comments, then reply in your terminal or IDE agent chat."
62850
+ },
62851
+ "animation-in-progress": {
62852
+ eyebrow: "Build in progress",
62853
+ title: "Animation is in progress",
62854
+ body: "The board refreshes as frames advance. Review completed frames now; the final review is ready when every frame is Animated."
62855
+ },
62856
+ "final-review": {
62857
+ eyebrow: "Ready for review",
62858
+ title: "Review motion and timing",
62859
+ body: "Every frame is animated. Open a frame in Preview to review it in the timeline, or leave frame comments for another revision."
62860
+ }
62861
+ };
62862
+ var REVIEW_ACTION_COPY = {
62863
+ empty: { body: "Add comments as previews arrive." },
62864
+ "plan-review": {
62865
+ body: "Add comments where you want changes. If everything looks right, approve this pass in agent chat.",
62866
+ approvalMessage: "Approve this storyboard plan and continue to visual sketches."
62867
+ },
62868
+ "sketch-in-progress": {
62869
+ body: "Add comments as previews arrive. You\u2019ll be prompted to approve when this pass is ready."
62870
+ },
62871
+ "sketch-review": {
62872
+ body: "Add comments where you want changes. If everything looks right, approve this pass in agent chat.",
62873
+ approvalMessage: "Approve these storyboard sketches and continue to animation."
62874
+ },
62875
+ "animation-in-progress": {
62876
+ body: "Add comments as previews arrive. You\u2019ll be prompted to approve when this pass is ready."
62877
+ },
62878
+ "final-review": {
62879
+ body: "Add comments where you want changes. If everything looks right, approve this pass in agent chat.",
62880
+ approvalMessage: "Approve this final storyboard review and continue to rendering."
62881
+ }
62882
+ };
62883
+ var REVIEW_STEP_STATES = {
62884
+ [-1]: {
62885
+ textClass: "text-emerald-400",
62886
+ numberClass: "border-emerald-700 bg-emerald-500/10",
62887
+ ariaCurrent: void 0,
62888
+ marker: () => "\u2713"
62889
+ },
62890
+ [0]: {
62891
+ textClass: "text-sky-300",
62892
+ numberClass: "border-sky-500 bg-sky-500/15",
62893
+ ariaCurrent: "step",
62894
+ marker: (number) => number
62895
+ },
62896
+ [1]: {
62897
+ textClass: "text-neutral-600",
62898
+ numberClass: "border-neutral-700",
62899
+ ariaCurrent: void 0,
62900
+ marker: (number) => number
62901
+ }
62902
+ };
62903
+ var REVIEW_STEP_SEPARATOR_CLASS = {
62904
+ 1: "invisible",
62905
+ 2: "",
62906
+ 3: ""
62907
+ };
62908
+ function StoryboardReviewGuide({
62909
+ frames,
62910
+ draftCount,
62911
+ pendingCount,
62912
+ onFeedbackMessageCopied
62913
+ }) {
62914
+ const summary = deriveStoryboardReviewStage(frames);
62915
+ const copy = GUIDE_COPY[summary.stage];
62916
+ const handoffStep = deriveStoryboardHandoffStep(draftCount, pendingCount);
62917
+ const progress = progressLabel(summary);
62918
+ return /* @__PURE__ */ jsxs131("section", { className: "mt-5 rounded-lg border border-neutral-800 bg-neutral-900/60 px-4 py-3", children: [
62919
+ /* @__PURE__ */ jsxs131("div", { className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between", children: [
62920
+ /* @__PURE__ */ jsxs131("div", { className: "max-w-3xl", children: [
62921
+ /* @__PURE__ */ jsx154("div", { className: "text-[10px] font-semibold uppercase tracking-wider text-sky-400", children: copy.eyebrow }),
62922
+ /* @__PURE__ */ jsx154("h2", { className: "mt-0.5 text-sm font-semibold text-neutral-100", children: copy.title }),
62923
+ /* @__PURE__ */ jsx154("p", { className: "mt-1 text-xs leading-relaxed text-neutral-400", children: copy.body })
62924
+ ] }),
62925
+ summary.frameCount > 0 && /* @__PURE__ */ jsxs131("div", { className: "shrink-0", children: [
62926
+ /* @__PURE__ */ jsx154("div", { className: "mb-1.5 text-right text-[11px] font-medium text-neutral-300", children: progress }),
62927
+ /* @__PURE__ */ jsx154("div", { className: "flex flex-wrap justify-end gap-1.5", "aria-label": "Frame status summary", children: FRAME_STATUS_ORDER.map((status) => /* @__PURE__ */ jsxs131(
62928
+ "span",
62929
+ {
62930
+ className: `rounded px-2 py-1 text-[10px] font-medium ${FRAME_STATUS_META[status].chipClass}`,
62931
+ children: [
62932
+ summary.counts[status],
62933
+ " ",
62934
+ FRAME_STATUS_META[status].label
62935
+ ]
62936
+ },
62937
+ status
62938
+ )) })
62939
+ ] })
62940
+ ] }),
62941
+ summary.frameCount > 0 && /* @__PURE__ */ jsxs131("div", { className: "mt-3 border-t border-neutral-800 pt-3", children: [
62942
+ /* @__PURE__ */ jsx154(ReviewSteps, { current: handoffStep }),
62943
+ /* @__PURE__ */ jsx154(
62944
+ NextAction,
62945
+ {
62946
+ stage: summary.stage,
62947
+ step: handoffStep,
62948
+ draftCount,
62949
+ onFeedbackMessageCopied
62950
+ }
62951
+ )
62952
+ ] })
62953
+ ] });
62954
+ }
62955
+ function ReviewSteps({ current }) {
62956
+ const steps = ["Review frames", "Save feedback", "Reply in agent chat"];
62957
+ return /* @__PURE__ */ jsx154("ol", { className: "hidden items-center gap-2 sm:flex", "aria-label": "Storyboard review workflow", children: steps.map((label, index) => /* @__PURE__ */ jsx154(
62958
+ ReviewStep,
62959
+ {
62960
+ label,
62961
+ number: index + 1,
62962
+ current
62963
+ },
62964
+ label
62965
+ )) });
62966
+ }
62967
+ function ReviewStep({
62968
+ label,
62969
+ number,
62970
+ current
62971
+ }) {
62972
+ const offset = Math.sign(number - current);
62973
+ const state = REVIEW_STEP_STATES[offset];
62974
+ return /* @__PURE__ */ jsxs131("li", { className: "flex items-center gap-2", children: [
62975
+ /* @__PURE__ */ jsx154(
62976
+ "span",
62613
62977
  {
62614
- type: "button",
62615
- disabled: busy,
62616
- "aria-pressed": status === option,
62617
- title: FRAME_STATUS_META[option].tooltip,
62618
- onClick: () => onSet(option),
62619
- className: `rounded px-2.5 py-1 text-xs font-medium transition-colors disabled:opacity-50 ${status === option ? "bg-neutral-700 text-neutral-100" : "text-neutral-400 hover:text-neutral-200"}`,
62620
- children: FRAME_STATUS_META[option].label
62621
- },
62622
- option
62623
- )) })
62978
+ className: `text-neutral-700 ${REVIEW_STEP_SEPARATOR_CLASS[number]}`,
62979
+ "aria-hidden": "true",
62980
+ children: "\u2192"
62981
+ }
62982
+ ),
62983
+ /* @__PURE__ */ jsxs131(
62984
+ "span",
62985
+ {
62986
+ "aria-current": state.ariaCurrent,
62987
+ className: `flex items-center gap-1.5 text-[11px] font-medium ${state.textClass}`,
62988
+ children: [
62989
+ /* @__PURE__ */ jsx154(
62990
+ "span",
62991
+ {
62992
+ className: `flex h-5 w-5 items-center justify-center rounded-full border text-[10px] ${state.numberClass}`,
62993
+ children: state.marker(number)
62994
+ }
62995
+ ),
62996
+ label
62997
+ ]
62998
+ }
62999
+ )
63000
+ ] });
63001
+ }
63002
+ function NextAction({
63003
+ stage,
63004
+ step,
63005
+ draftCount,
63006
+ onFeedbackMessageCopied
63007
+ }) {
63008
+ if (step === 3) {
63009
+ return /* @__PURE__ */ jsx154(AgentHandoffAction, { onFeedbackMessageCopied });
63010
+ }
63011
+ if (step === 2) return /* @__PURE__ */ jsx154(SaveFeedbackAction, { draftCount });
63012
+ return /* @__PURE__ */ jsx154(ReviewFramesAction, { stage });
63013
+ }
63014
+ function AgentHandoffAction({ onFeedbackMessageCopied }) {
63015
+ return /* @__PURE__ */ jsxs131("div", { className: "mt-3 flex flex-col gap-3 rounded-md border border-sky-900/70 bg-sky-950/20 px-3 py-2.5 sm:flex-row sm:items-center sm:justify-between", children: [
63016
+ /* @__PURE__ */ jsxs131("div", { className: "max-w-3xl", children: [
63017
+ /* @__PURE__ */ jsx154("div", { className: "text-xs font-semibold text-sky-200", children: "Next: return to your agent chat" }),
63018
+ /* @__PURE__ */ jsx154("p", { className: "mt-0.5 text-[11px] text-neutral-400", children: "Feedback is saved, but the agent has not been notified. Paste this prompt in your terminal or IDE agent chat." })
63019
+ ] }),
63020
+ /* @__PURE__ */ jsx154(
63021
+ AgentChatMessageButton,
63022
+ {
63023
+ message: APPLY_STORYBOARD_FEEDBACK_MESSAGE,
63024
+ onCopied: onFeedbackMessageCopied
63025
+ }
63026
+ )
63027
+ ] });
63028
+ }
63029
+ function SaveFeedbackAction({ draftCount }) {
63030
+ return /* @__PURE__ */ jsx154("div", { className: "mt-3 rounded-md border border-neutral-800 bg-neutral-950/40 px-3 py-2.5", children: /* @__PURE__ */ jsxs131("div", { children: [
63031
+ /* @__PURE__ */ jsx154("div", { className: "text-xs font-semibold text-neutral-200", children: "Next: save your feedback" }),
63032
+ /* @__PURE__ */ jsxs131("p", { className: "mt-0.5 text-[11px] text-neutral-500", children: [
63033
+ draftCount,
63034
+ " frame",
63035
+ draftCount === 1 ? " has" : "s have",
63036
+ " feedback ready. Use Save & copy message above to prepare this batch for your agent."
63037
+ ] })
63038
+ ] }) });
63039
+ }
63040
+ function ReviewFramesAction({ stage }) {
63041
+ const copy = REVIEW_ACTION_COPY[stage];
63042
+ return /* @__PURE__ */ jsxs131("div", { className: "mt-3 flex flex-col gap-3 rounded-md border border-neutral-800 bg-neutral-950/40 px-3 py-2.5 sm:flex-row sm:items-center sm:justify-between", children: [
63043
+ /* @__PURE__ */ jsxs131("div", { children: [
63044
+ /* @__PURE__ */ jsx154("div", { className: "text-xs font-semibold text-neutral-200", children: "Next: review the frames" }),
63045
+ /* @__PURE__ */ jsx154("p", { className: "mt-0.5 text-[11px] text-neutral-500", children: copy.body })
63046
+ ] }),
63047
+ copy.approvalMessage && /* @__PURE__ */ jsx154(AgentChatMessageButton, { message: copy.approvalMessage, label: "Copy approval message" })
62624
63048
  ] });
62625
63049
  }
63050
+ function progressLabel(summary) {
63051
+ if (summary.stage === "sketch-in-progress" || summary.stage === "sketch-review") {
63052
+ const ready = summary.frameCount - summary.counts.outline;
63053
+ return `${ready} of ${summary.frameCount} visual sketches ready`;
63054
+ }
63055
+ if (summary.stage === "animation-in-progress" || summary.stage === "final-review") {
63056
+ return `${summary.counts.animated} of ${summary.frameCount} animations ready`;
63057
+ }
63058
+ return `${summary.frameCount} plan frame${summary.frameCount === 1 ? "" : "s"} ready`;
63059
+ }
62626
63060
 
62627
63061
  // src/components/storyboard/useFrameComments.ts
62628
- import { useCallback as useCallback140, useEffect as useEffect97, useMemo as useMemo48, useState as useState115 } from "react";
63062
+ import { useCallback as useCallback141, useEffect as useEffect99, useMemo as useMemo48, useState as useState116 } from "react";
62629
63063
 
62630
63064
  // src/components/storyboard/frameComments.ts
62631
63065
  var FRAME_COMMENTS_PATH = ".hyperframes/frame-comments.json";
@@ -62687,32 +63121,34 @@ function buildCommentsFile(frames, drafts, previous, submittedAt) {
62687
63121
  // src/components/storyboard/useFrameComments.ts
62688
63122
  function useFrameComments(frames) {
62689
63123
  const { writeProjectFile, readOptionalProjectFile } = useFileManagerContext();
62690
- const [drafts, setDrafts] = useState115({});
62691
- const [submitState, setSubmitState] = useState115("idle");
62692
- const [pending, setPending] = useState115(null);
62693
- const refreshPending = useCallback140(async () => {
63124
+ const [drafts, setDrafts] = useState116({});
63125
+ const [submitState, setSubmitState] = useState116("idle");
63126
+ const [submitError, setSubmitError] = useState116(null);
63127
+ const [pending, setPending] = useState116(null);
63128
+ const refreshPending = useCallback141(async () => {
62694
63129
  try {
62695
63130
  const parsed = parseCommentsFile(await readOptionalProjectFile(FRAME_COMMENTS_PATH));
62696
63131
  setPending(parsed && parsed.comments.length > 0 ? parsed.comments : null);
62697
63132
  } catch {
62698
63133
  }
62699
63134
  }, [readOptionalProjectFile]);
62700
- useEffect97(() => {
63135
+ useEffect99(() => {
62701
63136
  void refreshPending();
62702
63137
  const onFocus = () => void refreshPending();
62703
63138
  window.addEventListener("focus", onFocus);
62704
63139
  return () => window.removeEventListener("focus", onFocus);
62705
63140
  }, [refreshPending]);
62706
- const setDraft = useCallback140((index, text) => {
63141
+ const setDraft = useCallback141((index, text) => {
62707
63142
  setDrafts((prev) => ({ ...prev, [index]: text }));
62708
63143
  }, []);
62709
63144
  const draftCount = useMemo48(
62710
63145
  () => Object.values(drafts).filter((text) => text.trim().length > 0).length,
62711
63146
  [drafts]
62712
63147
  );
62713
- const submit = useCallback140(async () => {
62714
- if (draftCount === 0 || submitState === "saving") return;
63148
+ const submit = useCallback141(async () => {
63149
+ if (draftCount === 0 || submitState === "saving") return false;
62715
63150
  setSubmitState("saving");
63151
+ setSubmitError(null);
62716
63152
  try {
62717
63153
  const previous = parseCommentsFile(await readOptionalProjectFile(FRAME_COMMENTS_PATH));
62718
63154
  const file = buildCommentsFile(frames, drafts, previous, (/* @__PURE__ */ new Date()).toISOString());
@@ -62720,16 +63156,28 @@ function useFrameComments(frames) {
62720
63156
  `);
62721
63157
  setDrafts({});
62722
63158
  setPending(file.comments);
62723
- } catch {
63159
+ return true;
63160
+ } catch (err) {
63161
+ setSubmitError(err instanceof Error ? err.message : "Failed to submit comments");
63162
+ return false;
62724
63163
  } finally {
62725
63164
  setSubmitState("idle");
62726
63165
  }
62727
63166
  }, [draftCount, submitState, frames, drafts, readOptionalProjectFile, writeProjectFile]);
62728
- return { drafts, setDraft, draftCount, submitState, submit, pending, refreshPending };
63167
+ return {
63168
+ drafts,
63169
+ setDraft,
63170
+ draftCount,
63171
+ submitState,
63172
+ submitError,
63173
+ submit,
63174
+ pending,
63175
+ refreshPending
63176
+ };
62729
63177
  }
62730
63178
 
62731
63179
  // src/components/storyboard/StoryboardLoaded.tsx
62732
- import { jsx as jsx154, jsxs as jsxs132 } from "react/jsx-runtime";
63180
+ import { Fragment as Fragment46, jsx as jsx155, jsxs as jsxs132 } from "react/jsx-runtime";
62733
63181
  function clampIndex(index, count) {
62734
63182
  return Math.max(1, Math.min(count, index));
62735
63183
  }
@@ -62739,14 +63187,28 @@ function StoryboardLoaded({
62739
63187
  reload,
62740
63188
  onSelectComposition
62741
63189
  }) {
62742
- const [subView, setSubView] = useState116("board");
62743
- const [sourceDirty, setSourceDirty] = useState116(false);
62744
- const [focusedIndex, setFocusedIndex] = useState116(null);
63190
+ const [subView, setSubView] = useState117("board");
63191
+ const [sourceDirty, setSourceDirty] = useState117(false);
63192
+ const [focusedIndex, setFocusedIndex] = useState117(null);
63193
+ const [feedbackMessageCopied, setFeedbackMessageCopied] = useState117(false);
62745
63194
  const comments = useFrameComments(data.frames);
62746
63195
  const { refreshPending } = comments;
62747
- useEffect98(() => {
63196
+ useEffect100(() => {
62748
63197
  void refreshPending();
62749
63198
  }, [data.signature, refreshPending]);
63199
+ useEffect100(() => {
63200
+ if (comments.draftCount > 0) setFeedbackMessageCopied(false);
63201
+ }, [comments.draftCount]);
63202
+ const saveFeedbackAndCopyMessage = async () => {
63203
+ const saved = await comments.submit();
63204
+ if (!saved) return;
63205
+ try {
63206
+ await navigator.clipboard.writeText(APPLY_STORYBOARD_FEEDBACK_MESSAGE);
63207
+ setFeedbackMessageCopied(true);
63208
+ } catch {
63209
+ setFeedbackMessageCopied(false);
63210
+ }
63211
+ };
62750
63212
  const sourceFiles = useMemo49(() => {
62751
63213
  const files = [{ path: data.path, label: data.path }];
62752
63214
  if (data.script?.exists) files.push({ path: data.script.path, label: data.script.path });
@@ -62761,7 +63223,7 @@ function StoryboardLoaded({
62761
63223
  };
62762
63224
  const focusedFrame = focusedIndex != null ? data.frames.find((f) => f.index === focusedIndex) ?? null : null;
62763
63225
  if (focusedFrame) {
62764
- return /* @__PURE__ */ jsx154(
63226
+ return /* @__PURE__ */ jsx155(
62765
63227
  StoryboardFrameFocus,
62766
63228
  {
62767
63229
  projectId,
@@ -62772,28 +63234,57 @@ function StoryboardLoaded({
62772
63234
  onNavigate: (delta) => setFocusedIndex(clampIndex(focusedFrame.index + delta, data.frames.length)),
62773
63235
  onSaved: reload,
62774
63236
  onSelectComposition,
63237
+ scriptExists: Boolean(data.script?.exists),
63238
+ commentDraft: comments.drafts[focusedFrame.index] ?? "",
63239
+ onCommentDraftChange: (text) => comments.setDraft(focusedFrame.index, text),
63240
+ pendingComment: comments.pending?.find((entry) => entry.frame === focusedFrame.index)?.text ?? null,
63241
+ pendingCommentCount: comments.pending?.length ?? 0,
63242
+ commentDraftCount: comments.draftCount,
63243
+ commentsSubmitState: comments.submitState,
63244
+ commentsSubmitError: comments.submitError,
63245
+ feedbackMessageCopied,
63246
+ onFeedbackMessageCopied: () => setFeedbackMessageCopied(true),
63247
+ onSaveFeedback: () => void saveFeedbackAndCopyMessage(),
62775
63248
  posterVersion: data.signature
62776
63249
  },
62777
63250
  focusedFrame.index
62778
63251
  );
62779
63252
  }
62780
- return /* @__PURE__ */ jsxs132("div", { className: "flex flex-1 min-h-0 flex-col bg-neutral-950 text-neutral-200", children: [
62781
- /* @__PURE__ */ jsxs132("div", { className: "flex items-center gap-3 border-b border-neutral-800 px-4 py-2", children: [
62782
- /* @__PURE__ */ jsx154(SubViewToggle, { value: subView, onChange: changeSubView }),
62783
- subView === "board" && /* @__PURE__ */ jsx154(
63253
+ return /* @__PURE__ */ jsxs132("div", { className: "flex w-full max-w-[100vw] flex-1 min-h-0 min-w-0 flex-col overflow-hidden bg-neutral-950 text-neutral-200", children: [
63254
+ /* @__PURE__ */ jsxs132("div", { className: "flex flex-wrap items-center gap-3 border-b border-neutral-800 px-4 py-2", children: [
63255
+ /* @__PURE__ */ jsx155(SubViewToggle, { value: subView, onChange: changeSubView }),
63256
+ subView === "board" && /* @__PURE__ */ jsx155(
62784
63257
  CommentsSubmitBar,
62785
63258
  {
62786
63259
  draftCount: comments.draftCount,
62787
63260
  pendingCount: comments.pending?.length ?? 0,
62788
63261
  submitState: comments.submitState,
62789
- onSubmit: () => void comments.submit()
63262
+ submitError: comments.submitError,
63263
+ messageCopied: feedbackMessageCopied,
63264
+ onSave: () => void saveFeedbackAndCopyMessage(),
63265
+ onMessageCopied: () => setFeedbackMessageCopied(true)
62790
63266
  }
62791
63267
  )
62792
63268
  ] }),
62793
- subView === "board" ? /* @__PURE__ */ jsx154("div", { className: "flex-1 min-h-0 overflow-auto", children: /* @__PURE__ */ jsxs132("div", { className: "mx-auto max-w-[1400px] px-8 py-8", children: [
62794
- /* @__PURE__ */ jsx154(StoryboardDirection, { globals: data.globals, frameCount: data.frames.length }),
62795
- /* @__PURE__ */ jsx154("div", { className: "mt-5", children: /* @__PURE__ */ jsx154(StoryboardStatusLegend, {}) }),
62796
- /* @__PURE__ */ jsx154(
63269
+ subView === "board" ? /* @__PURE__ */ jsx155("div", { className: "flex-1 min-h-0 overflow-auto", children: /* @__PURE__ */ jsxs132("div", { className: "mx-auto max-w-[1400px] px-4 py-5 sm:px-8 sm:py-8", children: [
63270
+ /* @__PURE__ */ jsx155(StoryboardDirection, { globals: data.globals, frameCount: data.frames.length }),
63271
+ /* @__PURE__ */ jsx155(
63272
+ StoryboardReviewGuide,
63273
+ {
63274
+ frames: data.frames,
63275
+ draftCount: comments.draftCount,
63276
+ pendingCount: comments.pending?.length ?? 0,
63277
+ onFeedbackMessageCopied: () => setFeedbackMessageCopied(true)
63278
+ }
63279
+ ),
63280
+ /* @__PURE__ */ jsx155(
63281
+ StoryboardWarnings,
63282
+ {
63283
+ warnings: data.warnings,
63284
+ onOpenSource: () => changeSubView("source")
63285
+ }
63286
+ ),
63287
+ /* @__PURE__ */ jsx155(
62797
63288
  StoryboardGrid,
62798
63289
  {
62799
63290
  projectId,
@@ -62805,8 +63296,8 @@ function StoryboardLoaded({
62805
63296
  posterVersion: data.signature
62806
63297
  }
62807
63298
  ),
62808
- data.script && /* @__PURE__ */ jsx154(StoryboardScriptPanel, { script: data.script })
62809
- ] }) }) : /* @__PURE__ */ jsx154(
63299
+ data.script && /* @__PURE__ */ jsx155(StoryboardScriptPanel, { script: data.script })
63300
+ ] }) }) : /* @__PURE__ */ jsx155(
62810
63301
  StoryboardSourceEditor,
62811
63302
  {
62812
63303
  files: sourceFiles,
@@ -62820,24 +63311,67 @@ function CommentsSubmitBar({
62820
63311
  draftCount,
62821
63312
  pendingCount,
62822
63313
  submitState,
62823
- onSubmit
62824
- }) {
62825
- return /* @__PURE__ */ jsxs132("div", { className: "ml-auto flex items-center gap-3", children: [
62826
- pendingCount > 0 && /* @__PURE__ */ jsxs132("span", { className: "text-xs text-sky-300", children: [
62827
- pendingCount,
62828
- " comment",
62829
- pendingCount > 1 ? "s" : "",
62830
- " pending \u2014 reply anything in your agent chat and it will apply them."
63314
+ submitError,
63315
+ messageCopied,
63316
+ onSave,
63317
+ onMessageCopied
63318
+ }) {
63319
+ return /* @__PURE__ */ jsxs132("div", { className: "ml-auto flex min-w-0 flex-1 flex-wrap items-center justify-end gap-2 sm:flex-none", children: [
63320
+ pendingCount > 0 && /* @__PURE__ */ jsxs132(Fragment46, { children: [
63321
+ /* @__PURE__ */ jsx155("span", { className: "text-xs text-sky-300", children: messageCopied ? "Feedback saved \xB7 Message copied \u2014 paste it in your terminal or IDE agent chat." : "Feedback saved \xB7 Agent not notified." }),
63322
+ /* @__PURE__ */ jsx155(
63323
+ AgentChatMessageButton,
63324
+ {
63325
+ message: APPLY_STORYBOARD_FEEDBACK_MESSAGE,
63326
+ label: messageCopied ? "Copy again" : "Copy prompt for agent",
63327
+ onCopied: onMessageCopied
63328
+ }
63329
+ )
62831
63330
  ] }),
62832
- /* @__PURE__ */ jsx154(
63331
+ pendingCount === 0 && draftCount === 0 && /* @__PURE__ */ jsx155("span", { className: "text-xs text-neutral-500", children: "Add frame comments to request changes." }),
63332
+ submitError && /* @__PURE__ */ jsxs132("span", { className: "max-w-64 truncate text-xs text-red-400", title: submitError, children: [
63333
+ "Couldn\u2019t submit: ",
63334
+ submitError
63335
+ ] }),
63336
+ draftCount > 0 && /* @__PURE__ */ jsxs132(
62833
63337
  Button,
62834
63338
  {
62835
63339
  variant: "primary",
62836
63340
  size: "sm",
62837
63341
  loading: submitState === "saving",
62838
- disabled: draftCount === 0 || submitState === "saving",
62839
- onClick: onSubmit,
62840
- children: draftCount > 0 ? `Submit comments (${draftCount})` : "Submit comments"
63342
+ disabled: submitState === "saving",
63343
+ onClick: onSave,
63344
+ children: [
63345
+ "Save & copy message (",
63346
+ draftCount,
63347
+ ")"
63348
+ ]
63349
+ }
63350
+ )
63351
+ ] });
63352
+ }
63353
+ function StoryboardWarnings({
63354
+ warnings,
63355
+ onOpenSource
63356
+ }) {
63357
+ if (warnings.length === 0) return null;
63358
+ return /* @__PURE__ */ jsxs132("details", { className: "mt-3 rounded-lg border border-amber-900/60 bg-amber-950/20 px-4 py-2 text-xs text-amber-200", children: [
63359
+ /* @__PURE__ */ jsxs132("summary", { className: "cursor-pointer font-medium", children: [
63360
+ warnings.length,
63361
+ " storyboard warning",
63362
+ warnings.length === 1 ? "" : "s"
63363
+ ] }),
63364
+ /* @__PURE__ */ jsx155("ul", { className: "mt-2 space-y-1 text-amber-200/80", children: warnings.map((warning, index) => /* @__PURE__ */ jsxs132("li", { children: [
63365
+ warning.line ? `Line ${warning.line}: ` : "",
63366
+ warning.message
63367
+ ] }, `${warning.line ?? "unknown"}-${index}`)) }),
63368
+ /* @__PURE__ */ jsx155(
63369
+ "button",
63370
+ {
63371
+ type: "button",
63372
+ onClick: onOpenSource,
63373
+ className: "mt-2 rounded text-amber-100 underline underline-offset-2 hover:text-white",
63374
+ children: "Open source to fix"
62841
63375
  }
62842
63376
  )
62843
63377
  ] });
@@ -62855,14 +63389,14 @@ function SubViewToggle({ value, onChange }) {
62855
63389
  const next = SUB_VIEWS[(currentIndex + delta + SUB_VIEWS.length) % SUB_VIEWS.length];
62856
63390
  if (next) onChange(next.value);
62857
63391
  };
62858
- return /* @__PURE__ */ jsx154(
63392
+ return /* @__PURE__ */ jsx155(
62859
63393
  "div",
62860
63394
  {
62861
63395
  className: "flex items-center gap-0.5 rounded-md bg-neutral-900 p-0.5",
62862
63396
  role: "tablist",
62863
63397
  "aria-label": "Storyboard view",
62864
63398
  onKeyDown: handleKeyDown,
62865
- children: SUB_VIEWS.map((option) => /* @__PURE__ */ jsx154(
63399
+ children: SUB_VIEWS.map((option) => /* @__PURE__ */ jsx155(
62866
63400
  "button",
62867
63401
  {
62868
63402
  type: "button",
@@ -62880,25 +63414,25 @@ function SubViewToggle({ value, onChange }) {
62880
63414
  }
62881
63415
 
62882
63416
  // src/components/storyboard/StoryboardView.tsx
62883
- import { jsx as jsx155, jsxs as jsxs133 } from "react/jsx-runtime";
63417
+ import { jsx as jsx156, jsxs as jsxs133 } from "react/jsx-runtime";
62884
63418
  function StoryboardView({ projectId, onSelectComposition }) {
62885
63419
  const { data, loading, error, reload } = useStoryboard(projectId);
62886
63420
  useProjectSignaturePoll(projectId, data?.signature, reload);
62887
- if (loading) return /* @__PURE__ */ jsx155(StoryboardFrame, { children: /* @__PURE__ */ jsx155(Message, { children: "Loading storyboard\u2026" }) });
63421
+ if (loading) return /* @__PURE__ */ jsx156(StoryboardFrame, { children: /* @__PURE__ */ jsx156(Message, { children: "Loading storyboard\u2026" }) });
62888
63422
  if (error) {
62889
63423
  return /* @__PURE__ */ jsxs133(StoryboardFrame, { children: [
62890
63424
  /* @__PURE__ */ jsxs133(Message, { tone: "error", children: [
62891
63425
  "Couldn\u2019t load the storyboard: ",
62892
63426
  error
62893
63427
  ] }),
62894
- /* @__PURE__ */ jsx155("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx155(Button, { size: "sm", variant: "secondary", onClick: reload, children: "Retry" }) })
63428
+ /* @__PURE__ */ jsx156("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx156(Button, { size: "sm", variant: "secondary", onClick: reload, children: "Retry" }) })
62895
63429
  ] });
62896
63430
  }
62897
- if (!data) return /* @__PURE__ */ jsx155(StoryboardFrame, { children: null });
63431
+ if (!data) return /* @__PURE__ */ jsx156(StoryboardFrame, { children: null });
62898
63432
  if (!data.exists) {
62899
- return /* @__PURE__ */ jsx155(StoryboardFrame, { children: /* @__PURE__ */ jsx155(EmptyState, { path: data.path }) });
63433
+ return /* @__PURE__ */ jsx156(StoryboardFrame, { children: /* @__PURE__ */ jsx156(EmptyState, { path: data.path }) });
62900
63434
  }
62901
- return /* @__PURE__ */ jsx155(
63435
+ return /* @__PURE__ */ jsx156(
62902
63436
  StoryboardLoaded,
62903
63437
  {
62904
63438
  projectId,
@@ -62909,10 +63443,10 @@ function StoryboardView({ projectId, onSelectComposition }) {
62909
63443
  );
62910
63444
  }
62911
63445
  function StoryboardFrame({ children }) {
62912
- return /* @__PURE__ */ jsx155("div", { className: "flex-1 min-h-0 overflow-auto bg-neutral-950 text-neutral-200", children: /* @__PURE__ */ jsx155("div", { className: "mx-auto max-w-[1400px] px-8 py-8", children }) });
63446
+ return /* @__PURE__ */ jsx156("div", { className: "flex-1 min-h-0 overflow-auto bg-neutral-950 text-neutral-200", children: /* @__PURE__ */ jsx156("div", { className: "mx-auto max-w-[1400px] px-8 py-8", children }) });
62913
63447
  }
62914
63448
  function Message({ children, tone = "muted" }) {
62915
- return /* @__PURE__ */ jsx155(
63449
+ return /* @__PURE__ */ jsx156(
62916
63450
  "div",
62917
63451
  {
62918
63452
  className: `px-6 py-12 text-center text-sm ${tone === "error" ? "text-red-400" : "text-neutral-500"}`,
@@ -62945,7 +63479,7 @@ Add one \`## Frame N\` section per beat. Keep the arc tight.
62945
63479
  Then run the review loop from the hyperframes-core skill (references/review-loop.md): present the plan as a proposal, offer wireframe sketches on this board, and build on the confirmed layouts.`;
62946
63480
  }
62947
63481
  function EmptyState({ path }) {
62948
- const [copied, setCopied] = useState117(false);
63482
+ const [copied, setCopied] = useState118(false);
62949
63483
  const prompt = handoffPrompt(path);
62950
63484
  const onCopy = async () => {
62951
63485
  if (await copyTextToClipboard(prompt)) {
@@ -62955,67 +63489,67 @@ function EmptyState({ path }) {
62955
63489
  };
62956
63490
  return /* @__PURE__ */ jsxs133("div", { children: [
62957
63491
  /* @__PURE__ */ jsxs133("div", { className: "rounded-lg border border-dashed border-neutral-800 px-6 py-10 text-center", children: [
62958
- /* @__PURE__ */ jsx155("h2", { className: "text-base font-semibold text-neutral-300", children: "No storyboard yet" }),
63492
+ /* @__PURE__ */ jsx156("h2", { className: "text-base font-semibold text-neutral-300", children: "No storyboard yet" }),
62959
63493
  /* @__PURE__ */ jsxs133("p", { className: "mx-auto mt-2 max-w-md text-sm text-neutral-500", children: [
62960
63494
  "Add a ",
62961
- /* @__PURE__ */ jsx155("code", { className: "rounded bg-neutral-900 px-1 py-0.5 text-neutral-400", children: path }),
63495
+ /* @__PURE__ */ jsx156("code", { className: "rounded bg-neutral-900 px-1 py-0.5 text-neutral-400", children: path }),
62962
63496
  " ",
62963
63497
  "at the project root to plan this video frame by frame. Hand this prompt to your coding agent to scaffold it."
62964
63498
  ] }),
62965
63499
  /* @__PURE__ */ jsxs133("div", { className: "mx-auto mt-6 max-w-2xl overflow-hidden rounded-lg border border-neutral-800 bg-neutral-900 text-left", children: [
62966
63500
  /* @__PURE__ */ jsxs133("div", { className: "flex items-center justify-between border-b border-neutral-800 px-3 py-2", children: [
62967
- /* @__PURE__ */ jsx155("span", { className: "font-mono text-xs text-neutral-500", children: "Prompt for your agent" }),
62968
- /* @__PURE__ */ jsx155(
63501
+ /* @__PURE__ */ jsx156("span", { className: "font-mono text-xs text-neutral-500", children: "Prompt for your agent" }),
63502
+ /* @__PURE__ */ jsx156(
62969
63503
  Button,
62970
63504
  {
62971
63505
  size: "sm",
62972
63506
  variant: "secondary",
62973
63507
  onClick: onCopy,
62974
- icon: copied ? /* @__PURE__ */ jsx155(Check2, { size: 14 }) : /* @__PURE__ */ jsx155(Copy2, { size: 14 }),
63508
+ icon: copied ? /* @__PURE__ */ jsx156(Check2, { size: 14 }) : /* @__PURE__ */ jsx156(Copy2, { size: 14 }),
62975
63509
  children: copied ? "Copied" : "Copy prompt"
62976
63510
  }
62977
63511
  )
62978
63512
  ] }),
62979
- /* @__PURE__ */ jsx155("pre", { className: "max-h-64 overflow-auto px-4 py-3 font-mono text-xs leading-relaxed text-neutral-400 whitespace-pre-wrap", children: prompt })
63513
+ /* @__PURE__ */ jsx156("pre", { className: "max-h-64 overflow-auto px-4 py-3 font-mono text-xs leading-relaxed text-neutral-400 whitespace-pre-wrap", children: prompt })
62980
63514
  ] })
62981
63515
  ] }),
62982
- /* @__PURE__ */ jsx155(SkeletonPreview, {})
63516
+ /* @__PURE__ */ jsx156(SkeletonPreview, {})
62983
63517
  ] });
62984
63518
  }
62985
63519
  function SkeletonPreview() {
62986
63520
  return /* @__PURE__ */ jsxs133("div", { "aria-hidden": "true", className: "mt-10 select-none opacity-40", children: [
62987
- /* @__PURE__ */ jsx155("div", { className: "mb-4 text-center text-xs uppercase tracking-wide text-neutral-600", children: "Preview" }),
62988
- /* @__PURE__ */ jsx155("div", { className: "grid gap-x-6 gap-y-8 [grid-template-columns:repeat(auto-fill,minmax(300px,1fr))]", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsxs133("div", { className: "rounded-lg border border-neutral-800 bg-neutral-900/40 p-3", children: [
62989
- /* @__PURE__ */ jsx155("div", { className: "aspect-video w-full rounded bg-neutral-800/60" }),
62990
- /* @__PURE__ */ jsx155("div", { className: "mt-3 h-3 w-2/3 rounded bg-neutral-800/60" }),
62991
- /* @__PURE__ */ jsx155("div", { className: "mt-2 h-2.5 w-full rounded bg-neutral-800/40" }),
62992
- /* @__PURE__ */ jsx155("div", { className: "mt-1.5 h-2.5 w-4/5 rounded bg-neutral-800/40" })
63521
+ /* @__PURE__ */ jsx156("div", { className: "mb-4 text-center text-xs uppercase tracking-wide text-neutral-600", children: "Preview" }),
63522
+ /* @__PURE__ */ jsx156("div", { className: "grid gap-x-6 gap-y-8 [grid-template-columns:repeat(auto-fill,minmax(300px,1fr))]", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsxs133("div", { className: "rounded-lg border border-neutral-800 bg-neutral-900/40 p-3", children: [
63523
+ /* @__PURE__ */ jsx156("div", { className: "aspect-video w-full rounded bg-neutral-800/60" }),
63524
+ /* @__PURE__ */ jsx156("div", { className: "mt-3 h-3 w-2/3 rounded bg-neutral-800/60" }),
63525
+ /* @__PURE__ */ jsx156("div", { className: "mt-2 h-2.5 w-full rounded bg-neutral-800/40" }),
63526
+ /* @__PURE__ */ jsx156("div", { className: "mt-1.5 h-2.5 w-4/5 rounded bg-neutral-800/40" })
62993
63527
  ] }, i)) })
62994
63528
  ] });
62995
63529
  }
62996
63530
 
62997
63531
  // src/components/StudioSplash.tsx
62998
- import { jsx as jsx156, jsxs as jsxs134 } from "react/jsx-runtime";
63532
+ import { jsx as jsx157, jsxs as jsxs134 } from "react/jsx-runtime";
62999
63533
  function StudioSplash({ waiting }) {
63000
- return /* @__PURE__ */ jsx156("div", { className: "h-full w-full bg-neutral-950 flex items-center justify-center", children: waiting ? /* @__PURE__ */ jsxs134("div", { className: "flex flex-col items-center gap-3 text-center px-6", role: "status", children: [
63001
- /* @__PURE__ */ jsx156("div", { className: "w-4 h-4 rounded-full border-2 border-neutral-700 border-t-neutral-500 animate-spin motion-reduce:animate-none" }),
63534
+ return /* @__PURE__ */ jsx157("div", { className: "h-full w-full bg-neutral-950 flex items-center justify-center", children: waiting ? /* @__PURE__ */ jsxs134("div", { className: "flex flex-col items-center gap-3 text-center px-6", role: "status", children: [
63535
+ /* @__PURE__ */ jsx157("div", { className: "w-4 h-4 rounded-full border-2 border-neutral-700 border-t-neutral-500 animate-spin motion-reduce:animate-none" }),
63002
63536
  /* @__PURE__ */ jsxs134("p", { className: "text-xs text-neutral-600", children: [
63003
63537
  "Waiting for preview server\u2026 run",
63004
63538
  " ",
63005
- /* @__PURE__ */ jsx156("code", { className: "text-neutral-500 font-mono", children: "npm run dev" })
63539
+ /* @__PURE__ */ jsx157("code", { className: "text-neutral-500 font-mono", children: "npm run dev" })
63006
63540
  ] })
63007
63541
  ] }) : /* @__PURE__ */ jsxs134("div", { className: "flex flex-col items-center gap-3 text-center px-6", role: "status", children: [
63008
- /* @__PURE__ */ jsx156("div", { className: "w-4 h-4 rounded-full bg-studio-accent animate-pulse motion-reduce:animate-none" }),
63009
- /* @__PURE__ */ jsx156("p", { className: "text-xs text-neutral-600", children: "Connecting to project\u2026" })
63542
+ /* @__PURE__ */ jsx157("div", { className: "w-4 h-4 rounded-full bg-studio-accent animate-pulse motion-reduce:animate-none" }),
63543
+ /* @__PURE__ */ jsx157("p", { className: "text-xs text-neutral-600", children: "Connecting to project\u2026" })
63010
63544
  ] }) });
63011
63545
  }
63012
63546
 
63013
63547
  // src/hooks/useServerConnection.ts
63014
- import { useEffect as useEffect99, useState as useState118 } from "react";
63548
+ import { useEffect as useEffect101, useState as useState119 } from "react";
63015
63549
  function useServerConnection() {
63016
- const [projectId, setProjectId] = useState118(null);
63017
- const [resolving, setResolving] = useState118(true);
63018
- const [waitingForServer, setWaitingForServer] = useState118(false);
63550
+ const [projectId, setProjectId] = useState119(null);
63551
+ const [resolving, setResolving] = useState119(true);
63552
+ const [waitingForServer, setWaitingForServer] = useState119(false);
63019
63553
  useMountEffect(() => {
63020
63554
  const hashProjectId = parseProjectIdFromHash(window.location.hash);
63021
63555
  let cancelled = false;
@@ -63052,7 +63586,7 @@ function useServerConnection() {
63052
63586
  if (retryTimer !== null) clearTimeout(retryTimer);
63053
63587
  };
63054
63588
  });
63055
- useEffect99(() => {
63589
+ useEffect101(() => {
63056
63590
  const onHashChange = () => {
63057
63591
  const next = parseProjectIdFromHash(window.location.hash);
63058
63592
  if (next && next !== projectId) setProjectId(next);
@@ -63064,56 +63598,56 @@ function useServerConnection() {
63064
63598
  }
63065
63599
 
63066
63600
  // src/App.tsx
63067
- import { jsx as jsx157, jsxs as jsxs135 } from "react/jsx-runtime";
63601
+ import { jsx as jsx158, jsxs as jsxs135 } from "react/jsx-runtime";
63068
63602
  function StudioApp() {
63069
63603
  const { projectId, resolving, waitingForServer } = useServerConnection();
63070
- const initialUrlStateRef = useRef119(readStudioUrlStateFromWindow());
63604
+ const initialUrlStateRef = useRef120(readStudioUrlStateFromWindow());
63071
63605
  const viewModeValue = useViewModeState();
63072
- useEffect100(() => {
63606
+ useEffect102(() => {
63073
63607
  if (resolving || waitingForServer) return;
63074
63608
  if (hasFiredSessionStart()) return;
63075
63609
  markSessionStartFired();
63076
63610
  trackStudioSessionStart({ has_project: projectId != null });
63077
63611
  }, [projectId, resolving, waitingForServer]);
63078
- const [activeCompPath, setActiveCompPath] = useState119(null);
63079
- const [activeCompPathHydrated, setActiveCompPathHydrated] = useState119(
63612
+ const [activeCompPath, setActiveCompPath] = useState120(null);
63613
+ const [activeCompPathHydrated, setActiveCompPathHydrated] = useState120(
63080
63614
  () => initialUrlStateRef.current.activeCompPath == null
63081
63615
  );
63082
- const [compIdToSrc, setCompIdToSrc] = useState119(/* @__PURE__ */ new Map());
63083
- const [previewIframe, setPreviewIframe] = useState119(null);
63084
- const [compositionLoading, setCompositionLoading] = useState119(true);
63085
- const [refreshKey, setRefreshKey] = useState119(0);
63616
+ const [compIdToSrc, setCompIdToSrc] = useState120(/* @__PURE__ */ new Map());
63617
+ const [previewIframe, setPreviewIframe] = useState120(null);
63618
+ const [compositionLoading, setCompositionLoading] = useState120(true);
63619
+ const [refreshKey, setRefreshKey] = useState120(0);
63086
63620
  const [previewDocumentVersion, refreshPreviewDocumentVersion] = usePreviewDocumentVersion();
63087
- const [blockPreview, setBlockPreview] = useState119(null);
63088
- const previewIframeRef = useRef119(null);
63089
- const activeCompPathRef = useRef119(activeCompPath);
63621
+ const [blockPreview, setBlockPreview] = useState120(null);
63622
+ const previewIframeRef = useRef120(null);
63623
+ const activeCompPathRef = useRef120(activeCompPath);
63090
63624
  activeCompPathRef.current = activeCompPath;
63091
- const leftSidebarRef = useRef119(null);
63625
+ const leftSidebarRef = useRef120(null);
63092
63626
  const renderQueue = useRenderQueue(projectId);
63093
63627
  const captionEditMode = useCaptionStore((s) => s.isEditMode);
63094
63628
  const captionHasSelection = useCaptionStore((s) => s.selectedSegmentIds.size > 0);
63095
63629
  const captionSync = useCaptionSync(projectId);
63096
63630
  const timelineElements = usePlayerStore((s) => s.elements);
63097
63631
  const setSelectedTimelineElementId = usePlayerStore((s) => s.setSelectedElementId);
63098
- const timelineDuration = usePlayerStore((s) => s.duration);
63632
+ const timelineDuration2 = usePlayerStore((s) => s.duration);
63099
63633
  const isPlaying = usePlayerStore((s) => s.isPlaying);
63100
63634
  const isMasterView = !activeCompPath || activeCompPath === "index.html";
63101
63635
  const activePreviewUrl = activeCompPath ? `/api/projects/${projectId}/preview/comp/${activeCompPath}` : null;
63102
63636
  const effectiveTimelineDuration = useMemo50(() => {
63103
63637
  const maxEnd = timelineElements.length > 0 ? Math.max(...timelineElements.map((el) => el.start + el.duration)) : 0;
63104
- return Math.max(timelineDuration, maxEnd);
63105
- }, [timelineDuration, timelineElements]);
63638
+ return Math.max(timelineDuration2, maxEnd);
63639
+ }, [timelineDuration2, timelineElements]);
63106
63640
  const { toasts, showToast, dismissToast } = useToast();
63107
63641
  const panelLayout = usePanelLayout({
63108
63642
  rightCollapsed: initialUrlStateRef.current.rightCollapsed,
63109
63643
  rightPanelTab: initialUrlStateRef.current.rightPanelTab
63110
63644
  });
63111
63645
  const editHistory = usePersistentEditHistory({ projectId });
63112
- const domEditSaveTimestampRef = useRef119(0);
63113
- const handleDomZIndexReorderCommitRef = useRef119(null);
63114
- const pendingTimelineEditPathRef = useRef119(/* @__PURE__ */ new Set());
63115
- const isGestureRecordingRef = useRef119(false);
63116
- const reloadPreview = useCallback141(() => setRefreshKey((k) => k + 1), []);
63646
+ const domEditSaveTimestampRef = useRef120(0);
63647
+ const handleDomZIndexReorderCommitRef = useRef120(null);
63648
+ const pendingTimelineEditPathRef = useRef120(/* @__PURE__ */ new Set());
63649
+ const isGestureRecordingRef = useRef120(false);
63650
+ const reloadPreview = useCallback142(() => setRefreshKey((k) => k + 1), []);
63117
63651
  const fileManager = useFileManager({
63118
63652
  projectId,
63119
63653
  showToast,
@@ -63131,7 +63665,7 @@ function StudioApp() {
63131
63665
  domEditSaveTimestampRef,
63132
63666
  masterCompPath
63133
63667
  );
63134
- useEffect100(() => {
63668
+ useEffect102(() => {
63135
63669
  if (activeCompPathHydrated) return;
63136
63670
  if (!fileManager.fileTreeLoaded) return;
63137
63671
  const nextCompPath = normalizeStudioCompositionPath(
@@ -63172,20 +63706,14 @@ function StudioApp() {
63172
63706
  forceReloadSdkSession: sdkHandle.forceReload,
63173
63707
  handleDomZIndexReorderCommitRef
63174
63708
  });
63175
- const handleTimelineElementsMove = useCallback141(
63709
+ const handleTimelineElementsMove = useCallback142(
63176
63710
  async (edits, coalesceKey, operation = "timing", coalesceMs) => {
63177
63711
  const deps = { handleTimelineGroupMove: timelineEditing.handleTimelineGroupMove };
63178
63712
  await persistTimelineMoveEditsAtomically(edits, coalesceKey, operation, deps, coalesceMs);
63179
63713
  },
63180
63714
  [timelineEditing.handleTimelineGroupMove]
63181
63715
  );
63182
- const handleAddAssetAtPlayhead = useCallback141(
63183
- (assetPath) => timelineEditing.handleTimelineAssetDrop(assetPath, {
63184
- start: usePlayerStore.getState().currentTime,
63185
- track: 0
63186
- }),
63187
- [timelineEditing]
63188
- );
63716
+ const handleAddAssetAtPlayhead = useAddAssetAtPlayhead(timelineEditing.handleTimelineAssetDrop);
63189
63717
  const {
63190
63718
  activeBlockParams,
63191
63719
  setActiveBlockParams,
@@ -63208,18 +63736,18 @@ function StudioApp() {
63208
63736
  setRightCollapsed: panelLayout.setRightCollapsed,
63209
63737
  setRightPanelTab: panelLayout.setRightPanelTab
63210
63738
  });
63211
- const clearDomSelectionRef = useRef119(() => {
63739
+ const clearDomSelectionRef = useRef120(() => {
63212
63740
  });
63213
- const domEditSelectionBridgeRef = useRef119(null);
63214
- const handleDomEditElementDeleteRef = useRef119(
63741
+ const domEditSelectionBridgeRef = useRef120(null);
63742
+ const handleDomEditElementDeleteRef = useRef120(
63215
63743
  async () => {
63216
63744
  }
63217
63745
  );
63218
63746
  const domEditDeleteBridge = (s) => handleDomEditElementDeleteRef.current(s);
63219
- const resetKeyframesRef = useRef119(() => false);
63220
- const deleteSelectedKeyframesRef = useRef119(() => {
63747
+ const resetKeyframesRef = useRef120(() => false);
63748
+ const deleteSelectedKeyframesRef = useRef120(() => {
63221
63749
  });
63222
- const invalidateGsapCacheRef = useRef119(() => {
63750
+ const invalidateGsapCacheRef = useRef120(() => {
63223
63751
  });
63224
63752
  const { handleCopy, handlePaste, handleCut } = useClipboard({
63225
63753
  projectId,
@@ -63261,7 +63789,7 @@ function StudioApp() {
63261
63789
  forceReloadSdkSession: sdkHandle.forceReload,
63262
63790
  onToggleRecording: STUDIO_KEYFRAMES_ENABLED ? () => handleToggleRecordingRef.current() : void 0
63263
63791
  });
63264
- const sidebarTabRef = useRef119({
63792
+ const sidebarTabRef = useRef120({
63265
63793
  select: (t) => leftSidebarRef.current?.selectTab(t),
63266
63794
  get: () => leftSidebarRef.current?.getTab() ?? "compositions"
63267
63795
  });
@@ -63349,9 +63877,9 @@ function StudioApp() {
63349
63877
  resetErrors: resetConsoleErrors
63350
63878
  } = useConsoleErrorCapture(previewIframe);
63351
63879
  const dragOverlay = useGlobalFileDrop(timelineEditing.handleTimelineFileDrop);
63352
- const handleToggleRecordingRef = useRef119(() => {
63880
+ const handleToggleRecordingRef = useRef120(() => {
63353
63881
  });
63354
- const domEditSessionRef = useRef119(domEditSession);
63882
+ const domEditSessionRef = useRef120(domEditSession);
63355
63883
  domEditSessionRef.current = domEditSession;
63356
63884
  const { gestureState, gestureRecording, handleToggleRecording } = useGestureCommit({
63357
63885
  domEditSessionRef,
@@ -63361,7 +63889,7 @@ function StudioApp() {
63361
63889
  });
63362
63890
  handleToggleRecordingRef.current = handleToggleRecording;
63363
63891
  const recordingToggle = STUDIO_KEYFRAMES_ENABLED ? handleToggleRecording : void 0;
63364
- const canvasRectRef = useRef119(null);
63892
+ const canvasRectRef = useRef120(null);
63365
63893
  useLayoutEffect4(() => {
63366
63894
  if (gestureState !== "recording" || !previewIframe) {
63367
63895
  canvasRectRef.current = null;
@@ -63370,7 +63898,7 @@ function StudioApp() {
63370
63898
  const r = previewIframe.getBoundingClientRect();
63371
63899
  canvasRectRef.current = { left: r.left, top: r.top, width: r.width, height: r.height };
63372
63900
  }, [gestureState, previewIframe]);
63373
- const handlePreviewIframeRef = useCallback141(
63901
+ const handlePreviewIframeRef = useCallback142(
63374
63902
  (iframe) => {
63375
63903
  previewIframeRef.current = iframe;
63376
63904
  setPreviewIframe(iframe);
@@ -63439,7 +63967,7 @@ function StudioApp() {
63439
63967
  refreshPreviewDocumentVersion
63440
63968
  });
63441
63969
  const timelineToolbar = useMemo50(
63442
- () => /* @__PURE__ */ jsx157(
63970
+ () => /* @__PURE__ */ jsx158(
63443
63971
  TimelineToolbar,
63444
63972
  {
63445
63973
  domEditSession,
@@ -63449,8 +63977,8 @@ function StudioApp() {
63449
63977
  [domEditSession, timelineEditing.handleTimelineElementSplit]
63450
63978
  );
63451
63979
  if (resolving || waitingForServer || !projectId)
63452
- return /* @__PURE__ */ jsx157(StudioSplash, { waiting: waitingForServer });
63453
- return /* @__PURE__ */ jsx157(StudioShellProvider, { value: studioCtxValue, children: /* @__PURE__ */ jsx157(StudioPlaybackProvider, { value: studioCtxValue, children: /* @__PURE__ */ jsx157(ViewModeProvider, { value: viewModeValue, children: /* @__PURE__ */ jsx157(PanelLayoutProvider, { value: panelLayout, children: /* @__PURE__ */ jsx157(FileManagerProvider, { value: fileManager, children: /* @__PURE__ */ jsx157(DomEditProvider, { value: domEditSession, children: /* @__PURE__ */ jsxs135(
63980
+ return /* @__PURE__ */ jsx158(StudioSplash, { waiting: waitingForServer });
63981
+ return /* @__PURE__ */ jsx158(StudioShellProvider, { value: studioCtxValue, children: /* @__PURE__ */ jsx158(StudioPlaybackProvider, { value: studioCtxValue, children: /* @__PURE__ */ jsx158(ViewModeProvider, { value: viewModeValue, children: /* @__PURE__ */ jsx158(PanelLayoutProvider, { value: panelLayout, children: /* @__PURE__ */ jsx158(FileManagerProvider, { value: fileManager, children: /* @__PURE__ */ jsx158(DomEditProvider, { value: domEditSession, children: /* @__PURE__ */ jsxs135(
63454
63982
  "div",
63455
63983
  {
63456
63984
  className: "flex flex-col h-full w-full bg-neutral-950 relative",
@@ -63459,7 +63987,7 @@ function StudioApp() {
63459
63987
  onDragLeave: dragOverlay.onDragLeave,
63460
63988
  onDrop: dragOverlay.onDrop,
63461
63989
  children: [
63462
- /* @__PURE__ */ jsx157(
63990
+ /* @__PURE__ */ jsx158(
63463
63991
  StudioHeader,
63464
63992
  {
63465
63993
  captureFrameHref: frameCapture.captureFrameHref,
@@ -63477,25 +64005,25 @@ function StudioApp() {
63477
64005
  }
63478
64006
  }
63479
64007
  ),
63480
- previewPersistence.domEditSaveQueuePaused && /* @__PURE__ */ jsx157(
64008
+ previewPersistence.domEditSaveQueuePaused && /* @__PURE__ */ jsx158(
63481
64009
  SaveQueuePausedBanner,
63482
64010
  {
63483
64011
  message: previewPersistence.domEditSaveQueuePaused,
63484
64012
  onRetry: previewPersistence.resetDomEditSaveQueueBreaker
63485
64013
  }
63486
64014
  ),
63487
- viewModeValue.viewMode === "storyboard" && /* @__PURE__ */ jsx157(
64015
+ viewModeValue.viewMode === "storyboard" && /* @__PURE__ */ jsx158(
63488
64016
  StoryboardView,
63489
64017
  {
63490
64018
  projectId,
63491
64019
  onSelectComposition: handleSelectComposition
63492
64020
  }
63493
64021
  ),
63494
- /* @__PURE__ */ jsx157(
64022
+ /* @__PURE__ */ jsx158(
63495
64023
  EditorShell,
63496
64024
  {
63497
64025
  hidden: viewModeValue.viewMode === "storyboard",
63498
- left: /* @__PURE__ */ jsx157(
64026
+ left: /* @__PURE__ */ jsx158(
63499
64027
  StudioLeftSidebar,
63500
64028
  {
63501
64029
  leftSidebarRef,
@@ -63509,7 +64037,7 @@ function StudioApp() {
63509
64037
  onAddAssetToTimeline: handleAddAssetAtPlayhead
63510
64038
  }
63511
64039
  ),
63512
- right: panelLayout.rightCollapsed ? null : /* @__PURE__ */ jsx157(
64040
+ right: panelLayout.rightCollapsed ? null : /* @__PURE__ */ jsx158(
63513
64041
  StudioRightPanel,
63514
64042
  {
63515
64043
  designPanelActive,
@@ -63523,6 +64051,7 @@ function StudioApp() {
63523
64051
  onToggleRecording: recordingToggle,
63524
64052
  sdkSession: sdkHandle.session,
63525
64053
  publishSdkSession: sdkHandle.publish,
64054
+ forceReloadSdkSession: sdkHandle.forceReload,
63526
64055
  reloadPreview,
63527
64056
  domEditSaveTimestampRef,
63528
64057
  recordEdit: editHistory.recordEdit,
@@ -63552,7 +64081,7 @@ function StudioApp() {
63552
64081
  recordingState: gestureState,
63553
64082
  onToggleRecording: recordingToggle,
63554
64083
  blockPreview,
63555
- gestureOverlay: gestureState === "recording" && previewIframe ? /* @__PURE__ */ jsx157(
64084
+ gestureOverlay: gestureState === "recording" && previewIframe ? /* @__PURE__ */ jsx158(
63556
64085
  GestureTrailOverlay,
63557
64086
  {
63558
64087
  samples: gestureRecording.samplesRef.current,
@@ -63565,7 +64094,7 @@ function StudioApp() {
63565
64094
  ) : void 0
63566
64095
  }
63567
64096
  ),
63568
- /* @__PURE__ */ jsx157(
64097
+ /* @__PURE__ */ jsx158(
63569
64098
  StudioOverlays,
63570
64099
  {
63571
64100
  projectId,
@@ -63587,32 +64116,32 @@ function StudioApp() {
63587
64116
  }
63588
64117
 
63589
64118
  // src/hooks/useElementPicker.ts
63590
- import { useState as useState120, useCallback as useCallback142, useRef as useRef120 } from "react";
64119
+ import { useState as useState121, useCallback as useCallback143, useRef as useRef121 } from "react";
63591
64120
  function useElementPicker(iframeRef, options) {
63592
- const [isPickMode, setIsPickMode] = useState120(false);
63593
- const [pickedElement, setPickedElement] = useState120(null);
63594
- const activeOverrideRef = useRef120(null);
63595
- const getActiveIframe = useCallback142(() => {
64121
+ const [isPickMode, setIsPickMode] = useState121(false);
64122
+ const [pickedElement, setPickedElement] = useState121(null);
64123
+ const activeOverrideRef = useRef121(null);
64124
+ const getActiveIframe = useCallback143(() => {
63596
64125
  return activeOverrideRef.current ?? iframeRef.current;
63597
64126
  }, [iframeRef]);
63598
- const setActiveIframe = useCallback142((el) => {
64127
+ const setActiveIframe = useCallback143((el) => {
63599
64128
  activeOverrideRef.current = el;
63600
64129
  }, []);
63601
- const enablePick = useCallback142(() => {
64130
+ const enablePick = useCallback143(() => {
63602
64131
  try {
63603
64132
  postRuntimeControlMessage(getActiveIframe()?.contentWindow, "enable-pick-mode");
63604
64133
  setIsPickMode(true);
63605
64134
  } catch {
63606
64135
  }
63607
64136
  }, [getActiveIframe]);
63608
- const disablePick = useCallback142(() => {
64137
+ const disablePick = useCallback143(() => {
63609
64138
  try {
63610
64139
  postRuntimeControlMessage(getActiveIframe()?.contentWindow, "disable-pick-mode");
63611
64140
  } catch {
63612
64141
  }
63613
64142
  setIsPickMode(false);
63614
64143
  }, [getActiveIframe]);
63615
- const clearPick = useCallback142(() => {
64144
+ const clearPick = useCallback143(() => {
63616
64145
  setPickedElement(null);
63617
64146
  }, []);
63618
64147
  useMountEffect(() => {
@@ -63665,9 +64194,9 @@ function useElementPicker(iframeRef, options) {
63665
64194
  window.addEventListener("message", handleMessage);
63666
64195
  return () => window.removeEventListener("message", handleMessage);
63667
64196
  });
63668
- const optionsRef = useRef120(options);
64197
+ const optionsRef = useRef121(options);
63669
64198
  optionsRef.current = options;
63670
- const syncToSource = useCallback142(
64199
+ const syncToSource = useCallback143(
63671
64200
  (elementId, selector, op) => {
63672
64201
  const opts = optionsRef.current;
63673
64202
  if (!opts?.workspaceFiles || !opts.onSyncFiles || !elementId) return;
@@ -63683,7 +64212,7 @@ function useElementPicker(iframeRef, options) {
63683
64212
  },
63684
64213
  []
63685
64214
  );
63686
- const setStyle = useCallback142(
64215
+ const setStyle = useCallback143(
63687
64216
  (prop, value) => {
63688
64217
  const activeIframe = getActiveIframe();
63689
64218
  if (!pickedElement?.selector || !activeIframe) return;
@@ -63725,7 +64254,7 @@ function useElementPicker(iframeRef, options) {
63725
64254
  },
63726
64255
  [pickedElement, getActiveIframe, syncToSource]
63727
64256
  );
63728
- const setDataAttr = useCallback142(
64257
+ const setDataAttr = useCallback143(
63729
64258
  (attr, value) => {
63730
64259
  const activeIframe = getActiveIframe();
63731
64260
  if (!pickedElement?.selector || !activeIframe) return;
@@ -63753,7 +64282,7 @@ function useElementPicker(iframeRef, options) {
63753
64282
  },
63754
64283
  [pickedElement, getActiveIframe, syncToSource]
63755
64284
  );
63756
- const setTextContent = useCallback142(
64285
+ const setTextContent = useCallback143(
63757
64286
  (text) => {
63758
64287
  const activeIframe = getActiveIframe();
63759
64288
  if (!pickedElement?.selector || !activeIframe) return;
@@ -63776,7 +64305,7 @@ function useElementPicker(iframeRef, options) {
63776
64305
  },
63777
64306
  [pickedElement, getActiveIframe, syncToSource]
63778
64307
  );
63779
- const activeIframeRef = useRef120(null);
64308
+ const activeIframeRef = useRef121(null);
63780
64309
  activeIframeRef.current = getActiveIframe();
63781
64310
  return {
63782
64311
  isPickMode,