@hyperframes/studio 0.8.56 → 0.8.58
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-zGyOcwwh.js +464 -0
- package/dist/assets/{index-DFlILHJe.js → index-BcE2OSVA.js} +1 -1
- package/dist/assets/{index-ZmBtVrEm.css → index-DKJpHYYd.css} +1 -1
- package/dist/assets/{index-CxR7F2YQ.js → index-ZJaRCDYV.js} +1 -1
- package/dist/assets/{index-BlkJ_Axe.js → index-d8ferjRc.js} +194 -194
- package/dist/index.d.ts +124 -9
- package/dist/index.html +2 -2
- package/dist/index.js +15686 -14882
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +7 -7
- package/src/App.tsx +4 -0
- package/src/components/EditorShell.tsx +3 -1
- package/src/components/ProjectUnreachableBanner.test.tsx +113 -0
- package/src/components/ProjectUnreachableBanner.tsx +89 -0
- package/src/components/editor/DomEditOverlay.tsx +0 -15
- package/src/components/editor/GridOverlay.tsx +12 -23
- package/src/components/editor/MotionPathOverlay.test.tsx +65 -0
- package/src/components/editor/MotionPathOverlay.tsx +3 -1
- package/src/components/editor/PreviewGuides.test.tsx +6 -1
- package/src/components/editor/PreviewGuides.tsx +8 -10
- package/src/components/editor/PreviewOverlayProvider.test.tsx +90 -0
- package/src/components/editor/PreviewOverlayProvider.tsx +86 -0
- package/src/components/editor/SnapToolbar.test.tsx +25 -12
- package/src/components/editor/SnapToolbar.tsx +30 -59
- package/src/components/editor/propertyPanelFlatMediaSection.test.tsx +2 -2
- package/src/components/editor/propertyPanelFlatMediaSection.tsx +46 -12
- package/src/components/editor/usePreviewCompositionRect.test.tsx +19 -13
- package/src/components/editor/usePreviewCompositionRect.ts +12 -12
- package/src/components/editor/useVolumeAutomation.test.tsx +68 -0
- package/src/components/editor/useVolumeAutomation.ts +57 -16
- package/src/components/nle/NLEContext.tsx +48 -6
- package/src/components/nle/NLEPreview.test.ts +67 -1
- package/src/components/nle/NLEPreview.tsx +88 -44
- package/src/components/nle/PreviewOverlays.tsx +4 -16
- package/src/components/nle/PreviewPane.tsx +17 -2
- package/src/components/nle/previewReadOnly.test.tsx +1 -0
- package/src/components/nle/useCompositionStack.ts +1 -1
- package/src/hooks/useCompositionDimensions.test.tsx +37 -0
- package/src/hooks/useCompositionDimensions.ts +17 -1
- package/src/hooks/useDomEditAttributeCommits.test.ts +23 -0
- package/src/hooks/useDomEditAttributeCommits.ts +14 -3
- package/src/hooks/useFileTree.test.tsx +56 -0
- package/src/hooks/useFileTree.ts +8 -1
- package/src/hooks/useSdkSession.lifecycle.test.tsx +108 -0
- package/src/hooks/useSdkSession.ts +77 -14
- package/src/hooks/useServerConnection.hashProject.test.tsx +303 -0
- package/src/hooks/useServerConnection.ts +81 -8
- package/src/index.ts +21 -1
- package/src/player/components/LayerDisclosureRow.tsx +3 -3
- package/src/player/components/Player.test.ts +175 -13
- package/src/player/components/Player.tsx +58 -2
- package/src/player/components/ShortcutsPanel.test.tsx +4 -10
- package/src/player/components/Timeline.tsx +23 -572
- package/src/player/components/TimelineCanvas.tsx +9 -30
- package/src/player/components/TimelineGroupHeader.tsx +4 -6
- package/src/player/components/TimelineOverlays.test.ts +67 -32
- package/src/player/components/TimelineOverlays.tsx +46 -107
- package/src/player/components/TimelineProvider.tsx +191 -0
- package/src/player/components/TimelineTrackPlainHeader.test.tsx +10 -0
- package/src/player/components/TimelineTrackPlainHeader.tsx +4 -7
- package/src/player/components/testRootHarness.ts +18 -0
- package/src/player/components/timelineProviderStateBuilders.test.ts +109 -0
- package/src/player/components/timelineProviderStateBuilders.ts +83 -0
- package/src/player/components/useTimelineClipContextMenu.ts +1 -1
- package/src/player/components/useTimelineOverlaysState.ts +100 -0
- package/src/player/components/useTimelineProviderState.tsx +583 -0
- package/src/player/hooks/playbackAdapterResolution.test.ts +41 -0
- package/src/player/hooks/playbackAdapterResolution.ts +98 -0
- package/src/player/hooks/timelinePlayerTestHarness.ts +120 -0
- package/src/player/hooks/usePlayerHandle.test.tsx +152 -0
- package/src/player/hooks/usePlayerHandle.ts +94 -0
- package/src/player/hooks/usePreviewFirstFrameTelemetry.ts +51 -0
- package/src/player/hooks/useShadowPreviewReload.ts +183 -0
- package/src/player/hooks/useTimelinePlayer.seek.test.ts +8 -88
- package/src/player/hooks/useTimelinePlayer.shadowReload.test.ts +457 -0
- package/src/player/hooks/useTimelinePlayer.ts +65 -99
- package/src/player/hooks/useTimelineSyncCallbacks.ts +159 -88
- package/src/player/index.ts +7 -0
- package/src/telemetry/events.test.ts +18 -0
- package/src/telemetry/events.ts +10 -0
- package/src/telemetry/system.test.ts +49 -0
- package/src/telemetry/system.ts +6 -0
- package/src/telemetry/tabId.test.ts +43 -0
- package/src/telemetry/tabId.ts +44 -0
- package/src/utils/previewCompositionSize.ts +26 -0
- package/src/utils/reloadDebug.ts +2 -4
- package/src/utils/sourcePatcher.ts +1 -1
- package/src/utils/studioTelemetry.test.ts +14 -0
- package/src/utils/studioTelemetry.ts +4 -0
- package/dist/assets/hyperframes-player-k8LzMtXX.js +0 -464
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { resetTabIdForTests, resolveTabId, tabIdProperty } from "./tabId";
|
|
3
|
+
|
|
4
|
+
afterEach(() => {
|
|
5
|
+
resetTabIdForTests();
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
describe("tab id", () => {
|
|
9
|
+
it("is stable for the life of the page", () => {
|
|
10
|
+
// The whole value of the property: every event from one page load must
|
|
11
|
+
// carry the SAME id, or a burst still reads as several sources.
|
|
12
|
+
const first = resolveTabId();
|
|
13
|
+
expect(resolveTabId()).toBe(first);
|
|
14
|
+
expect(tabIdProperty()).toBe(first);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("is not empty, since an empty value would read as an absent one", () => {
|
|
18
|
+
expect(resolveTabId().length).toBeGreaterThan(0);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("mints a new id after a fresh page load", () => {
|
|
22
|
+
// resetTabIdForTests stands in for a reload: the memo is all that survives
|
|
23
|
+
// a page, so clearing it is exactly what a reload does.
|
|
24
|
+
const first = resolveTabId();
|
|
25
|
+
resetTabIdForTests();
|
|
26
|
+
expect(resolveTabId()).not.toBe(first);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("never touches storage", () => {
|
|
30
|
+
// Deliberate: a stored id would drift into a tab-session id, silently
|
|
31
|
+
// changing what every query means. This asserts the intent directly rather
|
|
32
|
+
// than inferring it, so adding a read OR a write fails here.
|
|
33
|
+
const seen: string[] = [];
|
|
34
|
+
vi.stubGlobal("localStorage", {
|
|
35
|
+
getItem: (k: string) => (seen.push(`get:${k}`), null),
|
|
36
|
+
setItem: (k: string) => void seen.push(`set:${k}`),
|
|
37
|
+
removeItem: (k: string) => void seen.push(`remove:${k}`),
|
|
38
|
+
});
|
|
39
|
+
resolveTabId();
|
|
40
|
+
expect(seen).toEqual([]);
|
|
41
|
+
vi.unstubAllGlobals();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A per-page-load identifier, attached to every Studio event from BOTH
|
|
3
|
+
* transports (`studio:*` via utils/studioTelemetry.ts, `studio_*` via
|
|
4
|
+
* telemetry/system.ts).
|
|
5
|
+
*
|
|
6
|
+
* Why it exists: `distinct_id` identifies a browser, not a page. Several
|
|
7
|
+
* investigations have stalled on "two tabs or one tab switching?" — a burst of
|
|
8
|
+
* events alternating between two project hashes is indistinguishable from a
|
|
9
|
+
* user moving between projects, and `session_start` fires once per page LOAD
|
|
10
|
+
* (main.tsx), so counting it cannot tell them apart either. Nothing in the
|
|
11
|
+
* payload named the page. This does.
|
|
12
|
+
*
|
|
13
|
+
* Semantics, stated plainly because the name invites the wrong reading: one id
|
|
14
|
+
* per PAGE LOAD. A hard reload mints a new one. It is not "the same tab across
|
|
15
|
+
* reloads" — that is a different, harder question, and nobody has needed it yet.
|
|
16
|
+
*
|
|
17
|
+
* Deliberately not persisted. Storage would survive a reload and turn this into
|
|
18
|
+
* a tab-session id, silently changing what every query means; and a blocked or
|
|
19
|
+
* cleared storage would make the property intermittently absent, which reads as
|
|
20
|
+
* "old client" rather than "storage unavailable".
|
|
21
|
+
*
|
|
22
|
+
* Memoized like agentRuntime.ts and distinctId.ts: fixed for the life of the
|
|
23
|
+
* page, so a per-event caller does not pay for generation on every emit.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { generateId } from "../utils/generateId";
|
|
27
|
+
|
|
28
|
+
let cached: string | null = null;
|
|
29
|
+
|
|
30
|
+
/** The page's id. Generated once, on first read. */
|
|
31
|
+
export function resolveTabId(): string {
|
|
32
|
+
if (cached === null) cached = generateId();
|
|
33
|
+
return cached;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The same value as a telemetry property, for the `studio_*` transport's meta. */
|
|
37
|
+
export function tabIdProperty(): string {
|
|
38
|
+
return resolveTabId();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Test seam: the memo would otherwise leak between cases in one module load. */
|
|
42
|
+
export function resetTabIdForTests(): void {
|
|
43
|
+
cached = null;
|
|
44
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface PreviewCompositionSize {
|
|
2
|
+
width: number;
|
|
3
|
+
height: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/** The root composition's authored size, read from the preview document itself. */
|
|
7
|
+
// fallow-ignore-next-line complexity
|
|
8
|
+
export function readPreviewCompositionSize(
|
|
9
|
+
iframe: HTMLIFrameElement | null,
|
|
10
|
+
): PreviewCompositionSize | null {
|
|
11
|
+
try {
|
|
12
|
+
const doc = iframe?.contentDocument;
|
|
13
|
+
const root =
|
|
14
|
+
doc?.querySelector("[data-composition-id][data-width][data-height]") ??
|
|
15
|
+
doc?.querySelector("[data-width][data-height]");
|
|
16
|
+
if (!root) return null;
|
|
17
|
+
const width = Number.parseInt(root.getAttribute("data-width") ?? "", 10);
|
|
18
|
+
const height = Number.parseInt(root.getAttribute("data-height") ?? "", 10);
|
|
19
|
+
if (!Number.isFinite(width) || width <= 0 || !Number.isFinite(height) || height <= 0) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return { width, height };
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/utils/reloadDebug.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
// Preview full-reload diagnostics — grep [hf-reload]. Off by default; opt in per
|
|
2
2
|
// session with `localStorage.setItem("hf-reload-debug", "1")` (then reload).
|
|
3
3
|
//
|
|
4
|
-
// A full reload blanks the stage
|
|
5
|
-
//
|
|
6
|
-
// when one appears: who asked for it, and why the write that triggered it was
|
|
7
|
-
// not recognised as Studio's own.
|
|
4
|
+
// A full reload no longer blanks the stage (see useShadowPreviewReload.ts). These lines answer
|
|
5
|
+
// who asked for a reload and why the write was not recognised as Studio's own.
|
|
8
6
|
import { makeStudioDebugLogger } from "./studioDebug";
|
|
9
7
|
|
|
10
8
|
export const logReload = makeStudioDebugLogger("reload");
|
|
@@ -66,6 +66,20 @@ describe("studioTelemetry — shared opt-out and canary properties", () => {
|
|
|
66
66
|
});
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
+
// The property is useless if the transport does not attach it: the module
|
|
70
|
+
// has its own test, but nothing there fails if this line is deleted.
|
|
71
|
+
it("stamps every event with a tab id that is stable within the page", async () => {
|
|
72
|
+
trackStudioEvent("first");
|
|
73
|
+
trackStudioEvent("second");
|
|
74
|
+
const events = await sentEvents();
|
|
75
|
+
expect(events).toHaveLength(2);
|
|
76
|
+
const tabIds = events.map((e) => (e["properties"] as Record<string, unknown>)["tab_id"]);
|
|
77
|
+
expect(typeof tabIds[0]).toBe("string");
|
|
78
|
+
expect((tabIds[0] as string).length).toBeGreaterThan(0);
|
|
79
|
+
// Same page, same id — otherwise a burst still reads as several sources.
|
|
80
|
+
expect(tabIds[1]).toBe(tabIds[0]);
|
|
81
|
+
});
|
|
82
|
+
|
|
69
83
|
it("lets an explicit property win over the canary mixin", async () => {
|
|
70
84
|
trackStudioEvent("thing_happened", { "$feature/canary-test-one": "false" });
|
|
71
85
|
const events = await sentEvents();
|
|
@@ -2,6 +2,7 @@ import { resolveStudioDistinctId } from "../telemetry/distinctId";
|
|
|
2
2
|
import { browserTelemetryAllowed } from "../telemetry/policy";
|
|
3
3
|
import { canaryEventProperties } from "../telemetry/canary";
|
|
4
4
|
import { agentRuntimeProperty } from "../telemetry/agentRuntime";
|
|
5
|
+
import { tabIdProperty } from "../telemetry/tabId";
|
|
5
6
|
|
|
6
7
|
// PostHog public ingest key — write-only, safe to ship in the client bundle
|
|
7
8
|
const POSTHOG_API_KEY = "phc_zjjbX0PnWxERXrMHhkEJWj9A9BhGVLRReICgsfTMmpx";
|
|
@@ -48,6 +49,9 @@ function getSessionProperties(): EventProperties {
|
|
|
48
49
|
// and a property that only some events carry cannot answer it — the
|
|
49
50
|
// breakdown silently reads as though the agent never used the rest.
|
|
50
51
|
agent_runtime: agentRuntimeProperty(),
|
|
52
|
+
// Which page load this came from — distinct_id identifies the browser, not
|
|
53
|
+
// the page, so two tabs are otherwise one indistinguishable stream.
|
|
54
|
+
tab_id: tabIdProperty(),
|
|
51
55
|
screen_width: window.screen?.width,
|
|
52
56
|
screen_height: window.screen?.height,
|
|
53
57
|
viewport_width: window.innerWidth,
|