@hyperframes/studio 0.8.59 → 0.8.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.
- package/dist/assets/{hyperframes-player-BBub3ZdI.js → hyperframes-player-nmVkPQza.js} +1 -1
- package/dist/assets/{index-CI7WON-E.js → index-2-HsQB2C.js} +1 -1
- package/dist/assets/{index-D1KNPijK.js → index-BK36b0XY.js} +162 -162
- package/dist/assets/{index-BxZ5Cw5w.js → index-COG0K-CQ.js} +1 -1
- package/dist/assets/{index-DZBHG9TW.css → index-DS0kLbuM.css} +1 -1
- package/dist/index.d.ts +5 -6
- package/dist/index.html +2 -2
- package/dist/index.js +4977 -4273
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +7 -7
- package/src/App.tsx +2 -0
- package/src/components/StudioHeader.tsx +2 -13
- package/src/components/TimelineToolbar.test.tsx +42 -0
- package/src/components/TimelineToolbar.tsx +5 -10
- package/src/components/editor/propertyPanelFill.tsx +3 -2
- package/src/components/icons/AudioMetersIcon.tsx +16 -0
- package/src/components/icons/InspectorIcon.tsx +18 -0
- package/src/components/icons/ReverseGradientIcon.tsx +23 -0
- package/src/components/icons/RippleEditIcon.tsx +19 -0
- package/src/components/nle/AudioMeterStrip.test.tsx +278 -7
- package/src/components/nle/AudioMeterStrip.tsx +264 -47
- package/src/components/ui/Menu.test.tsx +3 -4
- package/src/components/ui/Menu.tsx +18 -5
- package/src/components/ui/Select.tsx +7 -1
- package/src/components/ui/Tooltip.tsx +6 -1
- package/src/hooks/useExternalFileChangeCoordinator.test.tsx +56 -0
- package/src/hooks/useExternalFileChangeCoordinator.ts +13 -0
- package/src/hooks/useRenderClipContent.test.ts +33 -4
- package/src/hooks/useRenderClipContent.ts +7 -3
- package/src/hooks/useSdkSession.lifecycle.test.tsx +179 -0
- package/src/hooks/useSdkSession.ts +84 -14
- package/src/hooks/useStudioSdkSessions.ts +8 -1
- package/src/player/components/AudioWaveform.test.tsx +53 -1
- package/src/player/components/AudioWaveform.tsx +124 -65
- package/src/player/components/ImageThumbnail.tsx +3 -5
- package/src/player/components/LayerDisclosureRow.tsx +9 -4
- package/src/player/components/PlayheadIndicator.tsx +3 -3
- package/src/player/components/TimelineAutomationLane.test.tsx +1 -1
- package/src/player/components/TimelineAutomationLane.tsx +5 -5
- package/src/player/components/TimelineCanvas.tsx +18 -15
- package/src/player/components/TimelineClip.test.tsx +41 -4
- package/src/player/components/TimelineClip.tsx +11 -4
- package/src/player/components/TimelineClipDiamonds.test.tsx +3 -1
- package/src/player/components/TimelineClipDiamonds.tsx +2 -2
- package/src/player/components/TimelineClipFades.tsx +64 -31
- package/src/player/components/TimelineDiamondConnectors.tsx +2 -2
- package/src/player/components/TimelineEmptyState.tsx +1 -1
- package/src/player/components/TimelineFxButton.tsx +10 -8
- package/src/player/components/TimelineGroupHeader.tsx +18 -9
- package/src/player/components/TimelineGroupLaneLabels.tsx +12 -5
- package/src/player/components/TimelineGroupRow.tsx +1 -1
- package/src/player/components/TimelineLanes.test.tsx +14 -0
- package/src/player/components/TimelineLanes.tsx +15 -8
- package/src/player/components/TimelinePropertyLanes.test.tsx +1 -1
- package/src/player/components/TimelineRazorInteraction.tsx +1 -1
- package/src/player/components/TimelineRuler.tsx +3 -2
- package/src/player/components/TimelineShortcutHint.tsx +2 -2
- package/src/player/components/TimelineTrackHeader.test.tsx +1 -1
- package/src/player/components/TimelineTrackHeader.tsx +4 -6
- package/src/player/components/TimelineTrackPlainHeader.tsx +10 -3
- package/src/player/components/TimelineTransitionBadge.tsx +59 -0
- package/src/player/components/TimelineTransitionOverlays.tsx +30 -0
- package/src/player/components/VideoThumbnail.test.tsx +23 -12
- package/src/player/components/VideoThumbnail.tsx +86 -32
- package/src/player/components/audioClipLink.test.ts +34 -0
- package/src/player/components/audioClipLink.ts +33 -0
- package/src/player/components/audioWaveformPeaks.test.ts +32 -0
- package/src/player/components/audioWaveformPeaks.ts +46 -0
- package/src/player/components/thumbnailUtils.test.ts +17 -0
- package/src/player/components/thumbnailUtils.ts +7 -0
- package/src/player/components/timelineMotionStyles.test.ts +96 -30
- package/src/player/components/timelineTheme.test.ts +14 -0
- package/src/player/components/timelineTheme.ts +18 -11
- package/src/player/components/timelineTransitionSeams.test.ts +48 -0
- package/src/player/components/timelineTransitionSeams.ts +36 -0
- package/src/player/components/timelineZones.test.ts +18 -0
- package/src/player/components/timelineZones.ts +4 -1
- package/src/player/lib/playbackTypes.ts +2 -0
- package/src/player/lib/timelineDOM.test.ts +28 -0
- package/src/player/lib/timelineDOM.ts +24 -2
- package/src/player/lib/timelineIframeHelpers.ts +7 -0
- package/src/player/lib/timelineTransitionMetadata.ts +52 -0
- package/src/player/lib/timelineViewportBudgets.ts +2 -1
- package/src/player/store/timelineElement.ts +1 -0
- package/src/styles/hex-baseline.json +3 -22
- package/src/styles/studio.css +126 -46
- package/src/styles/theme.css +113 -1
- package/src/styles/theme.test.ts +9 -0
- package/src/utils/audioMeterVisibility.ts +2 -2
- package/src/utils/sdkCutover.test.ts +102 -1
- package/src/utils/sdkCutover.ts +16 -21
- package/src/utils/sdkEditTransaction.ts +50 -1
- package/src/utils/studioUiPreferences.ts +1 -1
package/src/styles/studio.css
CHANGED
|
@@ -202,7 +202,76 @@ body {
|
|
|
202
202
|
.timeline-clip {
|
|
203
203
|
background-color: var(--clip-bg);
|
|
204
204
|
border: 1px solid var(--clip-border);
|
|
205
|
+
border-radius: var(--timeline-clip-radius);
|
|
206
|
+
box-shadow:
|
|
207
|
+
inset 0 1px 0 var(--timeline-clip-highlight),
|
|
208
|
+
var(--timeline-clip-shadow);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.timeline-transition {
|
|
212
|
+
position: absolute;
|
|
213
|
+
width: 0;
|
|
214
|
+
height: 0;
|
|
215
|
+
pointer-events: none;
|
|
216
|
+
z-index: 30;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.timeline-transition__zone {
|
|
220
|
+
position: absolute;
|
|
221
|
+
left: calc(var(--timeline-transition-zone-width) / -2);
|
|
222
|
+
top: -21px;
|
|
223
|
+
width: var(--timeline-transition-zone-width);
|
|
224
|
+
height: 42px;
|
|
205
225
|
border-radius: 8px;
|
|
226
|
+
overflow: hidden;
|
|
227
|
+
background: var(--timeline-transition-zone);
|
|
228
|
+
filter: blur(2.5px);
|
|
229
|
+
opacity: 0.95;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.timeline-transition__image {
|
|
233
|
+
position: absolute;
|
|
234
|
+
inset: -4px;
|
|
235
|
+
background-position: center;
|
|
236
|
+
background-size: cover;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.timeline-transition__image--out {
|
|
240
|
+
mask-image: linear-gradient(90deg, black 0%, transparent 100%);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.timeline-transition__image--in {
|
|
244
|
+
mask-image: linear-gradient(90deg, transparent 0%, black 100%);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.timeline-transition__badge {
|
|
248
|
+
position: absolute;
|
|
249
|
+
left: -14px;
|
|
250
|
+
top: -14px;
|
|
251
|
+
display: grid;
|
|
252
|
+
width: 28px;
|
|
253
|
+
height: 28px;
|
|
254
|
+
place-items: center;
|
|
255
|
+
border: 1px solid color-mix(in srgb, var(--timeline-transition-badge-ink) 22%, transparent);
|
|
256
|
+
border-radius: 999px;
|
|
257
|
+
background: var(--timeline-transition-badge);
|
|
258
|
+
box-shadow: 0 2px 5px var(--timeline-playhead-shadow);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.timeline-transition__badge svg {
|
|
262
|
+
width: 17px;
|
|
263
|
+
height: 17px;
|
|
264
|
+
}
|
|
265
|
+
.timeline-transition__frame,
|
|
266
|
+
.timeline-transition__frame--out,
|
|
267
|
+
.timeline-transition__frame--in {
|
|
268
|
+
fill: none;
|
|
269
|
+
stroke: var(--timeline-transition-badge-ink);
|
|
270
|
+
stroke-width: 1.5;
|
|
271
|
+
}
|
|
272
|
+
.timeline-transition__intersection {
|
|
273
|
+
fill: var(--timeline-transition-badge-ink);
|
|
274
|
+
opacity: 0.7;
|
|
206
275
|
}
|
|
207
276
|
|
|
208
277
|
.timeline-clip::before {
|
|
@@ -227,16 +296,37 @@ body {
|
|
|
227
296
|
accented visual clips — so the audio zone at the bottom is visually separate
|
|
228
297
|
at a glance (the timeline's kinds: visual on top, audio below). */
|
|
229
298
|
.timeline-clip.is-audio {
|
|
230
|
-
background-color:
|
|
231
|
-
border-color:
|
|
299
|
+
background-color: var(--timeline-clip-audio-bg);
|
|
300
|
+
border-color: var(--timeline-clip-audio-border);
|
|
301
|
+
border-radius: var(--timeline-clip-audio-radius);
|
|
232
302
|
}
|
|
233
303
|
|
|
234
304
|
.timeline-clip.is-audio.is-hovered {
|
|
235
|
-
background-color:
|
|
305
|
+
background-color: var(--timeline-clip-audio-bg-hover);
|
|
236
306
|
}
|
|
237
307
|
|
|
238
308
|
.timeline-clip.is-audio.is-dragging {
|
|
239
|
-
|
|
309
|
+
box-shadow:
|
|
310
|
+
inset 0 0 0 999px var(--timeline-clip-audio-bg-dragging),
|
|
311
|
+
0 8px 24px var(--timeline-clip-shadow-dragging);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/* Mute greys the pill in place. Hot path: no transition. */
|
|
315
|
+
.timeline-clip.is-audio[data-clip-hidden="true"],
|
|
316
|
+
.timeline-clip.is-audio[data-audio-muted="true"] {
|
|
317
|
+
background-color: var(--timeline-clip-audio-muted-bg);
|
|
318
|
+
transition: none;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.timeline-audio-link {
|
|
322
|
+
position: absolute;
|
|
323
|
+
z-index: 4;
|
|
324
|
+
top: -9px;
|
|
325
|
+
left: 50%;
|
|
326
|
+
width: 1px;
|
|
327
|
+
height: 10px;
|
|
328
|
+
background: var(--timeline-audio-link);
|
|
329
|
+
pointer-events: none;
|
|
240
330
|
}
|
|
241
331
|
|
|
242
332
|
.timeline-clip.is-hovered {
|
|
@@ -248,53 +338,56 @@ body {
|
|
|
248
338
|
}
|
|
249
339
|
|
|
250
340
|
.timeline-clip.is-selected {
|
|
251
|
-
|
|
341
|
+
outline: 1.5px solid var(--timeline-clip-selection);
|
|
342
|
+
outline-offset: -1.5px;
|
|
252
343
|
}
|
|
253
344
|
|
|
254
|
-
.timeline-clip
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
|
345
|
+
.timeline-clip.is-selected::after {
|
|
346
|
+
content: "";
|
|
347
|
+
position: absolute;
|
|
348
|
+
inset: 0;
|
|
349
|
+
z-index: 4;
|
|
350
|
+
border-radius: inherit;
|
|
351
|
+
box-shadow: inset 0 0 0 1px var(--timeline-clip-selection-highlight);
|
|
352
|
+
pointer-events: none;
|
|
263
353
|
}
|
|
264
354
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
box-shadow:
|
|
269
|
-
0 0 0 1.5px rgba(255, 255, 255, 0.85),
|
|
270
|
-
0 8px 24px rgba(0, 0, 0, 0.4);
|
|
355
|
+
.timeline-clip[data-active].is-selected {
|
|
356
|
+
outline: 1.5px solid var(--timeline-clip-selection);
|
|
357
|
+
outline-offset: -1.5px;
|
|
271
358
|
}
|
|
272
359
|
|
|
273
|
-
.timeline-clip
|
|
360
|
+
.timeline-clip.is-dragging {
|
|
361
|
+
/* Veil is a shadow, not background-color, so drop does not fade the hover fill. */
|
|
274
362
|
box-shadow:
|
|
275
|
-
0 0 0
|
|
276
|
-
0 8px 24px
|
|
363
|
+
inset 0 0 0 999px var(--clip-bg-dragging),
|
|
364
|
+
0 8px 24px var(--timeline-clip-shadow-dragging);
|
|
277
365
|
}
|
|
278
366
|
|
|
279
367
|
.timeline-clip__label {
|
|
280
368
|
position: absolute;
|
|
281
|
-
top:
|
|
282
|
-
left:
|
|
283
|
-
right: 10px;
|
|
369
|
+
top: 5px;
|
|
370
|
+
left: 6px;
|
|
284
371
|
z-index: 6;
|
|
372
|
+
width: fit-content;
|
|
373
|
+
max-width: calc(100% - 22px);
|
|
285
374
|
overflow: hidden;
|
|
286
|
-
|
|
375
|
+
padding: 1px 5px;
|
|
376
|
+
border-radius: 4px;
|
|
377
|
+
background: var(--timeline-clip-chip-bg);
|
|
378
|
+
color: var(--timeline-clip-chip-text);
|
|
287
379
|
font-size: 10px;
|
|
288
380
|
font-weight: 500;
|
|
289
|
-
line-height:
|
|
381
|
+
line-height: 16px;
|
|
290
382
|
pointer-events: none;
|
|
291
|
-
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.85);
|
|
292
383
|
text-overflow: ellipsis;
|
|
293
384
|
white-space: nowrap;
|
|
294
385
|
}
|
|
295
386
|
|
|
296
|
-
.timeline-clip
|
|
297
|
-
|
|
387
|
+
.timeline-clip.is-audio .timeline-clip__label {
|
|
388
|
+
top: 11px;
|
|
389
|
+
left: 9px;
|
|
390
|
+
background: var(--timeline-clip-chip-audio-bg);
|
|
298
391
|
}
|
|
299
392
|
|
|
300
393
|
.timeline-clip__timecode {
|
|
@@ -302,7 +395,7 @@ body {
|
|
|
302
395
|
bottom: 6px;
|
|
303
396
|
left: 12px;
|
|
304
397
|
overflow: hidden;
|
|
305
|
-
color:
|
|
398
|
+
color: var(--timeline-tick-text);
|
|
306
399
|
font-family: var(--font-mono);
|
|
307
400
|
font-size: 9px;
|
|
308
401
|
font-variant-numeric: tabular-nums;
|
|
@@ -313,20 +406,7 @@ body {
|
|
|
313
406
|
|
|
314
407
|
@media (prefers-reduced-motion: no-preference) {
|
|
315
408
|
.timeline-clip {
|
|
316
|
-
transition:
|
|
317
|
-
background-color 200ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
318
|
-
border-color 200ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
319
|
-
box-shadow 200ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
320
|
-
color 200ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
321
|
-
opacity 200ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
.timeline-clip::before {
|
|
325
|
-
transition: opacity 200ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
.timeline-clip__label {
|
|
329
|
-
transition: color 200ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
409
|
+
transition: background-color 120ms ease-out;
|
|
330
410
|
}
|
|
331
411
|
|
|
332
412
|
.timeline-clip__handle-bar {
|
package/src/styles/theme.css
CHANGED
|
@@ -62,6 +62,46 @@
|
|
|
62
62
|
--timeline-tick-major: rgba(255, 255, 255, 0.1);
|
|
63
63
|
--timeline-tick-minor: rgba(255, 255, 255, 0.06);
|
|
64
64
|
--timeline-clip-bg: rgba(255, 255, 255, 0.12);
|
|
65
|
+
--timeline-clip-label-active: rgba(232, 255, 247, 0.95);
|
|
66
|
+
--timeline-clip-label-shadow: 0 1px 2px rgba(0, 0, 0, 0.85);
|
|
67
|
+
--timeline-clip-audio-bg: rgba(167, 139, 250, 0.16);
|
|
68
|
+
--timeline-clip-audio-border: rgba(167, 139, 250, 0.4);
|
|
69
|
+
--timeline-clip-audio-bg-hover: rgba(167, 139, 250, 0.24);
|
|
70
|
+
--timeline-clip-audio-bg-dragging: rgba(60, 52, 84, 0.96);
|
|
71
|
+
--timeline-clip-audio-muted-bg: rgb(72, 74, 78);
|
|
72
|
+
--timeline-audio-link: #a0aaa4;
|
|
73
|
+
--timeline-clip-selection: rgba(255, 255, 255, 0.85);
|
|
74
|
+
--timeline-clip-selection-highlight: rgb(255 255 255 / 0.55);
|
|
75
|
+
--timeline-clip-radius: 8px;
|
|
76
|
+
--timeline-clip-audio-radius: 21px;
|
|
77
|
+
--timeline-clip-highlight: rgb(255 255 255 / 0.08);
|
|
78
|
+
--timeline-clip-shadow: 0 2px 6px rgb(0 0 0 / 0.3);
|
|
79
|
+
--timeline-clip-chip-bg: rgba(15, 22, 18, 0.73);
|
|
80
|
+
--timeline-clip-chip-audio-bg: rgba(25, 16, 38, 0.7);
|
|
81
|
+
--timeline-clip-chip-text: #ffffff;
|
|
82
|
+
--timeline-shortcut-bg: rgba(17, 23, 35, 0.84);
|
|
83
|
+
--timeline-thumbnail-shimmer: linear-gradient(
|
|
84
|
+
90deg,
|
|
85
|
+
rgba(255, 255, 255, 0.02) 0%,
|
|
86
|
+
rgba(255, 255, 255, 0.05) 50%,
|
|
87
|
+
rgba(255, 255, 255, 0.02) 100%
|
|
88
|
+
);
|
|
89
|
+
--timeline-thumbnail-label-gradient: linear-gradient(
|
|
90
|
+
to top,
|
|
91
|
+
rgba(0, 0, 0, 0.85) 0%,
|
|
92
|
+
rgba(0, 0, 0, 0.4) 60%,
|
|
93
|
+
transparent 100%
|
|
94
|
+
);
|
|
95
|
+
--timeline-thumbnail-label-shadow: 0 1px 2px rgba(0, 0, 0, 0.9);
|
|
96
|
+
--timeline-waveform-bar-rgb: 245, 237, 255;
|
|
97
|
+
--timeline-waveform-muted-rgb: 236, 238, 242;
|
|
98
|
+
--timeline-waveform-baseline-rgb: 226, 216, 238;
|
|
99
|
+
--timeline-waveform-error: repeating-linear-gradient(
|
|
100
|
+
90deg,
|
|
101
|
+
rgba(245, 237, 255, 0.35) 0 2px,
|
|
102
|
+
transparent 2px 5px
|
|
103
|
+
);
|
|
104
|
+
--timeline-waveform-label-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
|
|
65
105
|
--timeline-clip-bg-active: rgba(60, 230, 172, 0.2);
|
|
66
106
|
--timeline-clip-bg-hover: rgba(255, 255, 255, 0.16);
|
|
67
107
|
--timeline-clip-bg-dragging: rgba(38, 42, 52, 0.96);
|
|
@@ -78,6 +118,43 @@
|
|
|
78
118
|
--timeline-track-clip-fill: rgba(255, 255, 255, 0.055);
|
|
79
119
|
--timeline-track-clip-active: rgba(60, 230, 172, 0.16);
|
|
80
120
|
--timeline-track-label: rgba(255, 255, 255, 0.5);
|
|
121
|
+
--timeline-border-strong: rgba(255, 255, 255, 0.2);
|
|
122
|
+
--timeline-group-member-tint: rgba(255, 255, 255, 0.035);
|
|
123
|
+
--timeline-overlay-text: rgba(255, 255, 255, 0.8);
|
|
124
|
+
--timeline-accent: #3ce6ac;
|
|
125
|
+
--timeline-accent-soft: rgba(60, 230, 172, 0.18);
|
|
126
|
+
--timeline-accent-faint: rgba(60, 230, 172, 0.055);
|
|
127
|
+
--timeline-accent-fill: rgba(60, 230, 172, 0.1);
|
|
128
|
+
--timeline-accent-glow: rgba(60, 230, 172, 0.5);
|
|
129
|
+
--timeline-accent-border: rgba(60, 230, 172, 0.7);
|
|
130
|
+
--timeline-accent-rail: rgba(60, 230, 172, 0.35);
|
|
131
|
+
--timeline-accent-hover: rgba(60, 230, 172, 0.25);
|
|
132
|
+
--timeline-accent-button-bg: rgba(60, 230, 172, 0.15);
|
|
133
|
+
--timeline-danger: rgba(239, 68, 68, 0.7);
|
|
134
|
+
--timeline-info-bg: rgba(59, 130, 246, 0.12);
|
|
135
|
+
--timeline-info-border: rgba(59, 130, 246, 0.4);
|
|
136
|
+
--timeline-dialog-bg: #1b1b1f;
|
|
137
|
+
--timeline-diamond-bg: #171717;
|
|
138
|
+
--timeline-diamond-border: rgba(255, 255, 255, 0.14);
|
|
139
|
+
--timeline-text-solid: #ffffff;
|
|
140
|
+
--timeline-text-faint: rgba(255, 255, 255, 0.35);
|
|
141
|
+
--timeline-text-dim: rgba(255, 255, 255, 0.4);
|
|
142
|
+
--timeline-text-soft: rgba(255, 255, 255, 0.75);
|
|
143
|
+
--timeline-text-bright: rgba(255, 255, 255, 0.7);
|
|
144
|
+
--timeline-snap-guide: rgba(255, 255, 255, 0.6);
|
|
145
|
+
--timeline-overlay-bg: rgba(0, 0, 0, 0.8);
|
|
146
|
+
--timeline-overlay-bg-strong: rgba(0, 0, 0, 0.85);
|
|
147
|
+
--timeline-input-bg: rgba(0, 0, 0, 0.3);
|
|
148
|
+
--timeline-automation-rail: rgba(255, 255, 255, 0.08);
|
|
149
|
+
--timeline-automation-point-muted: rgba(0, 0, 0, 0.5);
|
|
150
|
+
--timeline-diamond-muted: #a3a3a3;
|
|
151
|
+
--timeline-beat: rgb(34, 197, 94);
|
|
152
|
+
--timeline-clip-shadow-dragging: rgba(0, 0, 0, 0.4);
|
|
153
|
+
--timeline-playhead-glow: rgba(60, 230, 172, 0.14);
|
|
154
|
+
--timeline-playhead-shadow: rgba(0, 0, 0, 0.55);
|
|
155
|
+
--timeline-transition-zone: rgba(255, 255, 255, 0.2);
|
|
156
|
+
--timeline-transition-badge: rgba(237, 244, 239, 0.98);
|
|
157
|
+
--timeline-transition-badge-ink: rgba(36, 56, 45, 0.96);
|
|
81
158
|
|
|
82
159
|
/* -------------------------------------------------------------- geometry */
|
|
83
160
|
|
|
@@ -146,7 +223,14 @@
|
|
|
146
223
|
--duration-press: 120ms;
|
|
147
224
|
--duration-hover: 150ms;
|
|
148
225
|
--duration-expand: 160ms;
|
|
149
|
-
|
|
226
|
+
/* Menus, popovers, and dropdowns. Open is ease-out; close is shorter and ease-in. */
|
|
227
|
+
--duration-open: 120ms;
|
|
228
|
+
--duration-close: 90ms;
|
|
229
|
+
/* Tooltips use the same enter shape on a shorter open. */
|
|
230
|
+
--duration-tooltip: 100ms;
|
|
231
|
+
/* Visible on the first frame. Never enter from scale 0 or opacity 0. */
|
|
232
|
+
--popup-enter-opacity: 0.8;
|
|
233
|
+
--popup-enter-scale: 0.97;
|
|
150
234
|
|
|
151
235
|
/* ------------------------------------------------------------------ icon */
|
|
152
236
|
|
|
@@ -264,6 +348,34 @@
|
|
|
264
348
|
--color-white: #fff;
|
|
265
349
|
}
|
|
266
350
|
|
|
351
|
+
/* One paper block. A host sets data-theme="paper" on the document element. */
|
|
352
|
+
:root[data-theme="paper"] {
|
|
353
|
+
--timeline-shell-bg: #f4f6f3;
|
|
354
|
+
--timeline-row-bg: #edf0ed;
|
|
355
|
+
--timeline-clip-bg: #c9d1cb;
|
|
356
|
+
--timeline-clip-shadow: 0 2px 6px rgb(0 0 0 / 0.12);
|
|
357
|
+
--timeline-clip-highlight: rgb(255 255 255 / 0.08);
|
|
358
|
+
--timeline-clip-selection: #0f766e;
|
|
359
|
+
--timeline-clip-selection-highlight: rgb(255 255 255 / 0.55);
|
|
360
|
+
--timeline-clip-chip-bg: rgba(15, 22, 18, 0.73);
|
|
361
|
+
--timeline-clip-chip-audio-bg: rgba(25, 16, 38, 0.7);
|
|
362
|
+
--timeline-clip-chip-text: #ffffff;
|
|
363
|
+
--timeline-clip-audio-bg: rgb(150, 128, 180);
|
|
364
|
+
--timeline-clip-audio-bg-hover: rgb(135, 109, 170);
|
|
365
|
+
--timeline-clip-audio-bg-dragging: rgb(232, 224, 242);
|
|
366
|
+
--timeline-clip-audio-border: rgba(113, 80, 155, 0.45);
|
|
367
|
+
--timeline-clip-audio-muted-bg: rgb(156, 161, 157);
|
|
368
|
+
--timeline-audio-link: #626d66;
|
|
369
|
+
--timeline-waveform-bar-rgb: 20, 12, 32;
|
|
370
|
+
--timeline-waveform-muted-rgb: 24, 28, 26;
|
|
371
|
+
--timeline-waveform-baseline-rgb: 76, 62, 88;
|
|
372
|
+
--timeline-waveform-error: repeating-linear-gradient(
|
|
373
|
+
90deg,
|
|
374
|
+
rgba(20, 12, 32, 0.35) 0 2px,
|
|
375
|
+
transparent 2px 5px
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
267
379
|
/* Motion durations as utilities, since Tailwind has no `--duration-*`
|
|
268
380
|
* namespace. Each one zeroes itself under `prefers-reduced-motion`, so a
|
|
269
381
|
* caller cannot forget the reduced-motion case. */
|
package/src/styles/theme.test.ts
CHANGED
|
@@ -109,6 +109,15 @@ describe("studio theme", () => {
|
|
|
109
109
|
expect(vars.get("--text-lg")).toBe("1.125rem");
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
+
it("opens menus from a visible shape and closes them faster", () => {
|
|
113
|
+
expect(declaredValue("--duration-open")).toBe("120ms");
|
|
114
|
+
expect(declaredValue("--duration-close")).toBe("90ms");
|
|
115
|
+
expect(declaredValue("--duration-tooltip")).toBe("100ms");
|
|
116
|
+
expect(declaredValue("--popup-enter-opacity")).toBe("0.8");
|
|
117
|
+
expect(declaredValue("--popup-enter-scale")).toBe("0.97");
|
|
118
|
+
expect(declaredValue("--duration-hover")).toBe("150ms");
|
|
119
|
+
});
|
|
120
|
+
|
|
112
121
|
it("compiles a motion-duration utility with a reduced-motion variant", async () => {
|
|
113
122
|
const css = await build("studio.css", ["duration-press", "duration-open"]);
|
|
114
123
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { create } from "zustand";
|
|
2
2
|
import { readStudioUiPreferences, writeStudioUiPreferences } from "./studioUiPreferences";
|
|
3
3
|
|
|
4
|
-
/** Whether the audio meter strip is shown; persisted,
|
|
4
|
+
/** Whether the audio meter strip is shown; persisted, off unless the user showed it. */
|
|
5
5
|
export const useAudioMetersVisible = create<{
|
|
6
6
|
visible: boolean;
|
|
7
7
|
setVisible: (visible: boolean) => void;
|
|
8
8
|
}>((set) => ({
|
|
9
|
-
visible: readStudioUiPreferences().audioMetersVisible ??
|
|
9
|
+
visible: readStudioUiPreferences().audioMetersVisible ?? false,
|
|
10
10
|
setVisible: (visible) => {
|
|
11
11
|
writeStudioUiPreferences({ audioMetersVisible: visible });
|
|
12
12
|
set({ visible });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
import {
|
|
3
3
|
shouldUseSdkCutover,
|
|
4
4
|
sdkCutoverPersist,
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import { openComposition } from "@hyperframes/sdk";
|
|
15
15
|
import { createMemoryAdapter } from "@hyperframes/sdk/adapters/memory";
|
|
16
16
|
import type { PatchOperation } from "./sourcePatcher";
|
|
17
|
+
import { StudioSaveNetworkError } from "./studioSaveDiagnostics";
|
|
17
18
|
|
|
18
19
|
vi.mock("../components/editor/manualEditingAvailability", () => ({
|
|
19
20
|
STUDIO_SDK_CUTOVER_ENABLED: true,
|
|
@@ -1404,3 +1405,103 @@ gsap.timeline().to('[data-hf-id="hf-layer"]', { duration: 1, x: 100 });
|
|
|
1404
1405
|
expect(written).toContain("gsap.timeline()");
|
|
1405
1406
|
});
|
|
1406
1407
|
});
|
|
1408
|
+
|
|
1409
|
+
describe("sdk_cutover_failed classification (error_kind)", () => {
|
|
1410
|
+
const makeDeps = (overrides: Partial<Parameters<typeof sdkCutoverPersist>[5]> = {}) => ({
|
|
1411
|
+
editHistory: { recordEdit: vi.fn().mockResolvedValue(undefined) },
|
|
1412
|
+
writeProjectFile: vi.fn().mockResolvedValue(undefined),
|
|
1413
|
+
reloadPreview: vi.fn(),
|
|
1414
|
+
...candidateTestDeps(),
|
|
1415
|
+
...overrides,
|
|
1416
|
+
});
|
|
1417
|
+
|
|
1418
|
+
const makeSession = () =>
|
|
1419
|
+
({
|
|
1420
|
+
getElement: vi.fn().mockReturnValue({ inlineStyles: {} }),
|
|
1421
|
+
dispatch: vi.fn(),
|
|
1422
|
+
serialize: vi
|
|
1423
|
+
.fn()
|
|
1424
|
+
.mockReturnValueOnce("<html>before</html>")
|
|
1425
|
+
.mockReturnValue("<html></html>"),
|
|
1426
|
+
batch: vi.fn((fn: () => void) => fn()),
|
|
1427
|
+
}) as unknown as Parameters<typeof sdkCutoverPersist>[4];
|
|
1428
|
+
|
|
1429
|
+
beforeEach(() => {
|
|
1430
|
+
vi.mocked(trackStudioEvent).mockClear();
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1433
|
+
// The production gate event this fix exists for: a bare `TypeError` from an
|
|
1434
|
+
// UNWRAPPED raw fetch (`readProjectFile`'s implementation in
|
|
1435
|
+
// useProjectFileWriter.ts, or writeProjectFile's own preflight — neither
|
|
1436
|
+
// wraps as StudioSaveNetworkError). Without the message-based fallback in
|
|
1437
|
+
// `cutoverErrorKind`, this files as `error_kind: "sdk"` and trips the
|
|
1438
|
+
// cutover-failure rollback gate on a network blip the SDK never owned.
|
|
1439
|
+
it("classifies a bare fetch TypeError from readProjectFile as network", async () => {
|
|
1440
|
+
const deps = makeDeps({
|
|
1441
|
+
readProjectFile: vi.fn().mockRejectedValue(new TypeError("Failed to fetch")),
|
|
1442
|
+
});
|
|
1443
|
+
const session = makeSession();
|
|
1444
|
+
const sel = { hfId: "hf-abc" } as never;
|
|
1445
|
+
|
|
1446
|
+
const result = await sdkCutoverPersist(
|
|
1447
|
+
sel,
|
|
1448
|
+
[styleOp("color", "red")],
|
|
1449
|
+
"before",
|
|
1450
|
+
"/path.html",
|
|
1451
|
+
session,
|
|
1452
|
+
deps,
|
|
1453
|
+
);
|
|
1454
|
+
|
|
1455
|
+
expect(result.status).toBe("failed");
|
|
1456
|
+
expect(trackStudioEvent).toHaveBeenCalledWith(
|
|
1457
|
+
"sdk_cutover_failed",
|
|
1458
|
+
expect.objectContaining({ family: "dom", error_kind: "network" }),
|
|
1459
|
+
);
|
|
1460
|
+
});
|
|
1461
|
+
|
|
1462
|
+
it("classifies StudioSaveNetworkError from writeProjectFile as network", async () => {
|
|
1463
|
+
const deps = makeDeps({
|
|
1464
|
+
writeProjectFile: vi.fn().mockRejectedValue(new StudioSaveNetworkError("Failed to save")),
|
|
1465
|
+
});
|
|
1466
|
+
const session = makeSession();
|
|
1467
|
+
const sel = { hfId: "hf-abc" } as never;
|
|
1468
|
+
|
|
1469
|
+
const result = await sdkCutoverPersist(
|
|
1470
|
+
sel,
|
|
1471
|
+
[styleOp("color", "red")],
|
|
1472
|
+
"before",
|
|
1473
|
+
"/path.html",
|
|
1474
|
+
session,
|
|
1475
|
+
deps,
|
|
1476
|
+
);
|
|
1477
|
+
|
|
1478
|
+
expect(result.status).toBe("failed");
|
|
1479
|
+
expect(trackStudioEvent).toHaveBeenCalledWith(
|
|
1480
|
+
"sdk_cutover_failed",
|
|
1481
|
+
expect.objectContaining({ family: "dom", error_kind: "network" }),
|
|
1482
|
+
);
|
|
1483
|
+
});
|
|
1484
|
+
|
|
1485
|
+
it("classifies a plain error as an sdk defect, not network", async () => {
|
|
1486
|
+
const deps = makeDeps({
|
|
1487
|
+
writeProjectFile: vi.fn().mockRejectedValue(new Error("disk full")),
|
|
1488
|
+
});
|
|
1489
|
+
const session = makeSession();
|
|
1490
|
+
const sel = { hfId: "hf-abc" } as never;
|
|
1491
|
+
|
|
1492
|
+
const result = await sdkCutoverPersist(
|
|
1493
|
+
sel,
|
|
1494
|
+
[styleOp("color", "red")],
|
|
1495
|
+
"before",
|
|
1496
|
+
"/path.html",
|
|
1497
|
+
session,
|
|
1498
|
+
deps,
|
|
1499
|
+
);
|
|
1500
|
+
|
|
1501
|
+
expect(result.status).toBe("failed");
|
|
1502
|
+
expect(trackStudioEvent).toHaveBeenCalledWith(
|
|
1503
|
+
"sdk_cutover_failed",
|
|
1504
|
+
expect.objectContaining({ family: "dom", error_kind: "sdk" }),
|
|
1505
|
+
);
|
|
1506
|
+
});
|
|
1507
|
+
});
|
package/src/utils/sdkCutover.ts
CHANGED
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
shouldDeclineTextCutoverForTarget,
|
|
12
12
|
} from "./sdkCutoverEligibility";
|
|
13
13
|
import {
|
|
14
|
-
asCutoverError,
|
|
15
14
|
declinedCutover,
|
|
15
|
+
failedCutover,
|
|
16
16
|
persistSdkCandidateMutation,
|
|
17
17
|
type CutoverDeps,
|
|
18
18
|
type CutoverOptions,
|
|
@@ -45,12 +45,13 @@ function sdkFamilyEnabled(family: StudioSdkOperationFamily): boolean {
|
|
|
45
45
|
|
|
46
46
|
function trackCutoverResult(
|
|
47
47
|
result: CutoverResult,
|
|
48
|
+
family: StudioSdkOperationFamily,
|
|
48
49
|
context: { hfId?: string | null; opCount: number },
|
|
49
50
|
): void {
|
|
50
51
|
if (result.status === "committed") {
|
|
51
|
-
trackStudioEvent("sdk_cutover_success", context);
|
|
52
|
+
trackStudioEvent("sdk_cutover_success", { ...context, family });
|
|
52
53
|
} else if (result.status === "failed") {
|
|
53
|
-
|
|
54
|
+
failedCutover(result.error, family, context);
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
|
|
@@ -106,7 +107,7 @@ export async function sdkCutoverPersist(
|
|
|
106
107
|
},
|
|
107
108
|
options,
|
|
108
109
|
);
|
|
109
|
-
trackCutoverResult(result, { hfId, opCount: ops.length });
|
|
110
|
+
trackCutoverResult(result, "dom", { hfId, opCount: ops.length });
|
|
110
111
|
return result;
|
|
111
112
|
}
|
|
112
113
|
|
|
@@ -148,12 +149,10 @@ export async function sdkTimingPersist(
|
|
|
148
149
|
options,
|
|
149
150
|
serializedBefore,
|
|
150
151
|
);
|
|
151
|
-
trackCutoverResult(result, { hfId, opCount: 1 });
|
|
152
|
+
trackCutoverResult(result, "timing", { hfId, opCount: 1 });
|
|
152
153
|
return result;
|
|
153
154
|
} catch (error) {
|
|
154
|
-
|
|
155
|
-
trackStudioEvent("sdk_cutover_failed", { hfId, error: failed.error.message });
|
|
156
|
-
return failed;
|
|
155
|
+
return failedCutover(error, "timing", { hfId, opCount: 1 });
|
|
157
156
|
}
|
|
158
157
|
}
|
|
159
158
|
|
|
@@ -202,24 +201,22 @@ export async function sdkTimingBatchPersist(
|
|
|
202
201
|
serializedBefore,
|
|
203
202
|
);
|
|
204
203
|
if (result.status === "failed") {
|
|
205
|
-
|
|
204
|
+
return failedCutover(result.error, "timing", {
|
|
206
205
|
hfId: changes[0]?.hfId ?? null,
|
|
207
|
-
|
|
206
|
+
opCount: changes.length,
|
|
208
207
|
});
|
|
209
|
-
return result;
|
|
210
208
|
}
|
|
211
209
|
trackStudioEvent("sdk_cutover_success", {
|
|
212
210
|
hfId: changes[0]?.hfId ?? null,
|
|
213
211
|
opCount: changes.length,
|
|
212
|
+
family: "timing",
|
|
214
213
|
});
|
|
215
214
|
return result;
|
|
216
215
|
} catch (error) {
|
|
217
|
-
|
|
218
|
-
trackStudioEvent("sdk_cutover_failed", {
|
|
216
|
+
return failedCutover(error, "timing", {
|
|
219
217
|
hfId: changes[0]?.hfId ?? null,
|
|
220
|
-
|
|
218
|
+
opCount: changes.length,
|
|
221
219
|
});
|
|
222
|
-
return failed;
|
|
223
220
|
}
|
|
224
221
|
}
|
|
225
222
|
|
|
@@ -321,15 +318,13 @@ async function dispatchGsapOpAndPersist(
|
|
|
321
318
|
serializedBefore,
|
|
322
319
|
);
|
|
323
320
|
if (result.status === "committed") {
|
|
324
|
-
trackStudioEvent("sdk_cutover_success", { opCount: 1 });
|
|
321
|
+
trackStudioEvent("sdk_cutover_success", { opCount: 1, family });
|
|
325
322
|
} else if (result.status === "failed") {
|
|
326
|
-
|
|
323
|
+
return failedCutover(result.error, family, { opCount: 1 });
|
|
327
324
|
}
|
|
328
325
|
return result;
|
|
329
326
|
} catch (error) {
|
|
330
|
-
|
|
331
|
-
trackStudioEvent("sdk_cutover_failed", { error: failed.error.message });
|
|
332
|
-
return failed;
|
|
327
|
+
return failedCutover(error, family, { opCount: 1 });
|
|
333
328
|
}
|
|
334
329
|
}
|
|
335
330
|
|
|
@@ -595,6 +590,6 @@ export async function sdkDeletePersist(
|
|
|
595
590
|
(session) => session.removeElement(hfId),
|
|
596
591
|
{ label: "Delete element" },
|
|
597
592
|
);
|
|
598
|
-
trackCutoverResult(result, { hfId, opCount: 1 });
|
|
593
|
+
trackCutoverResult(result, "lifecycle", { hfId, opCount: 1 });
|
|
599
594
|
return result;
|
|
600
595
|
}
|