@hyperframes/studio 0.7.107 → 0.7.108

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.
Files changed (126) hide show
  1. package/dist/assets/{hyperframes-player-BLrfwq5o.js → hyperframes-player-QqavTWRK.js} +1 -1
  2. package/dist/assets/{index-BA9yhzfR.js → index-BQjXA1pz.js} +1 -1
  3. package/dist/assets/{index-BSBk5srs.js → index-BhHHMprP.js} +1 -1
  4. package/dist/assets/index-DNkh9mbV.css +1 -0
  5. package/dist/assets/index-GqONLcOl.js +428 -0
  6. package/dist/index.d.ts +85 -2
  7. package/dist/index.html +2 -2
  8. package/dist/index.js +17696 -12512
  9. package/dist/index.js.map +1 -1
  10. package/package.json +7 -7
  11. package/src/components/StudioRightPanel.tsx +18 -1
  12. package/src/components/TimelineToolbar.test.tsx +46 -0
  13. package/src/components/TimelineToolbar.tsx +17 -0
  14. package/src/components/editor/PropertyPanel.test.tsx +5 -1
  15. package/src/components/editor/PropertyPanel.tsx +7 -23
  16. package/src/components/editor/PropertyPanelFlat.tsx +25 -0
  17. package/src/components/editor/audioFxSummary.test.ts +75 -0
  18. package/src/components/editor/audioFxSummary.ts +36 -0
  19. package/src/components/editor/audioFxTelemetry.test.ts +129 -0
  20. package/src/components/editor/audioFxTelemetry.ts +230 -0
  21. package/src/components/editor/propertyPanelAudioFxGroup.test.tsx +1830 -0
  22. package/src/components/editor/propertyPanelAudioFxGroup.tsx +299 -0
  23. package/src/components/editor/propertyPanelAudioFxGroupUtils.ts +21 -0
  24. package/src/components/editor/propertyPanelAutomation.test.ts +60 -0
  25. package/src/components/editor/propertyPanelAutomation.ts +97 -0
  26. package/src/components/editor/propertyPanelFlatMediaSection.tsx +37 -9
  27. package/src/components/editor/propertyPanelFlatProps.ts +1 -0
  28. package/src/components/editor/propertyPanelFxAddMenu.tsx +209 -0
  29. package/src/components/editor/propertyPanelFxBandRuler.tsx +72 -0
  30. package/src/components/editor/propertyPanelFxCarveModule.tsx +382 -0
  31. package/src/components/editor/propertyPanelFxControls.test.tsx +115 -0
  32. package/src/components/editor/propertyPanelFxControls.tsx +356 -0
  33. package/src/components/editor/propertyPanelFxEqModule.tsx +196 -0
  34. package/src/components/editor/propertyPanelFxFamily.ts +83 -0
  35. package/src/components/editor/propertyPanelFxNodeOpenBody.tsx +269 -0
  36. package/src/components/editor/propertyPanelFxNodeParams.tsx +112 -0
  37. package/src/components/editor/propertyPanelFxNodeRow.tsx +339 -0
  38. package/src/components/editor/propertyPanelFxPresetMenu.tsx +159 -0
  39. package/src/components/editor/propertyPanelFxPresetRun.tsx +215 -0
  40. package/src/components/editor/propertyPanelFxPresetStyle.test.ts +142 -0
  41. package/src/components/editor/propertyPanelFxPresetStyle.ts +216 -0
  42. package/src/components/editor/propertyPanelFxRackChain.tsx +202 -0
  43. package/src/components/editor/propertyPanelFxSection.test.tsx +1747 -0
  44. package/src/components/editor/propertyPanelFxSection.tsx +550 -0
  45. package/src/components/editor/propertyPanelFxSectionTypes.ts +91 -0
  46. package/src/components/editor/propertyPanelTypes.ts +5 -0
  47. package/src/components/editor/useFxAudition.ts +95 -0
  48. package/src/components/editor/useFxCarve.ts +0 -0
  49. package/src/components/editor/useFxChainObserved.ts +83 -0
  50. package/src/components/editor/useFxLevelling.ts +235 -0
  51. package/src/components/editor/useVolumeAutomation.test.tsx +122 -0
  52. package/src/components/editor/useVolumeAutomation.ts +54 -0
  53. package/src/contexts/DomEditContext.tsx +11 -0
  54. package/src/hooks/useAppHotkeys.test.ts +198 -0
  55. package/src/hooks/useAppHotkeys.ts +30 -3
  56. package/src/hooks/useAutomationSelectionKeyboard.test.tsx +429 -0
  57. package/src/hooks/useAutomationSelectionKeyboard.ts +371 -0
  58. package/src/hooks/useDomEditAttributeCommits.ts +90 -10
  59. package/src/hooks/useDomEditCommits.test.tsx +38 -0
  60. package/src/hooks/useDomEditCommits.ts +2 -0
  61. package/src/hooks/useDomEditSession.ts +2 -0
  62. package/src/hooks/useDomEditTextCommits.ts +2 -0
  63. package/src/hooks/useElementLifecycleOps.ts +3 -0
  64. package/src/hooks/useLivePlayheadTime.ts +49 -0
  65. package/src/hooks/usePreviewPersistence.ts +7 -0
  66. package/src/hooks/useTimelineEditing.ts +3 -0
  67. package/src/player/components/AutomationSelectionMenu.tsx +76 -0
  68. package/src/player/components/AutomationValueInput.tsx +51 -0
  69. package/src/player/components/LayerDisclosureRow.tsx +5 -4
  70. package/src/player/components/Timeline.test.ts +84 -0
  71. package/src/player/components/TimelineAutomationLane.test.tsx +1720 -0
  72. package/src/player/components/TimelineAutomationLane.tsx +674 -0
  73. package/src/player/components/TimelineAutomationLaneSlot.test.tsx +193 -0
  74. package/src/player/components/TimelineLanes.test.tsx +24 -5
  75. package/src/player/components/TimelineLanes.tsx +142 -154
  76. package/src/player/components/TimelineTrackHeader.test.tsx +212 -1
  77. package/src/player/components/TimelineTrackHeader.tsx +166 -6
  78. package/src/player/components/TimelineTrackRow.tsx +8 -1
  79. package/src/player/components/automationClipboard.test.ts +94 -0
  80. package/src/player/components/automationClipboard.ts +136 -0
  81. package/src/player/components/automationGestureKeys.test.ts +49 -0
  82. package/src/player/components/automationGestureKeys.ts +49 -0
  83. package/src/player/components/automationLaneData.test.ts +270 -0
  84. package/src/player/components/automationLaneData.ts +258 -0
  85. package/src/player/components/automationLaneDragMath.ts +194 -0
  86. package/src/player/components/automationLaneGeometry.test.ts +239 -0
  87. package/src/player/components/automationLaneGeometry.ts +287 -0
  88. package/src/player/components/automationLaneHeight.ts +15 -0
  89. package/src/player/components/automationLanePointer.ts +20 -0
  90. package/src/player/components/automationLaneSelection.test.ts +183 -0
  91. package/src/player/components/automationLaneSelection.ts +135 -0
  92. package/src/player/components/automationShapes.test.ts +82 -0
  93. package/src/player/components/automationShapes.ts +70 -0
  94. package/src/player/components/automationSimplify.test.ts +48 -0
  95. package/src/player/components/automationSimplify.ts +51 -0
  96. package/src/player/components/timelineClipGestureHandlers.ts +223 -0
  97. package/src/player/components/timelineLaneProps.ts +19 -0
  98. package/src/player/components/timelineLayout.ts +11 -5
  99. package/src/player/components/useAutomationEdgeStretch.test.ts +27 -0
  100. package/src/player/components/useAutomationEdgeStretch.ts +269 -0
  101. package/src/player/components/useAutomationLaneGestures.ts +537 -0
  102. package/src/player/components/useAutomationLanes.test.tsx +112 -0
  103. package/src/player/components/useAutomationLanes.ts +170 -0
  104. package/src/player/components/useAutomationRangeDrag.ts +114 -0
  105. package/src/player/components/useTimelineTrackLayout.test.ts +107 -1
  106. package/src/player/components/useTimelineTrackLayout.ts +83 -7
  107. package/src/player/hooks/useExpandedTimelineElements.ts +7 -0
  108. package/src/player/hooks/useTimelinePlayer.ts +13 -1
  109. package/src/player/lib/automationStoreSync.test.ts +83 -0
  110. package/src/player/lib/automationStoreSync.ts +56 -0
  111. package/src/player/lib/timelineDOM.test.ts +33 -0
  112. package/src/player/lib/timelineDOM.ts +12 -0
  113. package/src/player/store/automationSelectionSlice.test.ts +30 -0
  114. package/src/player/store/automationSelectionSlice.ts +54 -0
  115. package/src/player/store/playerStore.test.ts +15 -0
  116. package/src/player/store/playerStore.ts +54 -65
  117. package/src/player/store/timelineElement.ts +78 -0
  118. package/src/styles/studio.css +89 -0
  119. package/src/telemetry/agentRuntime.test.ts +45 -0
  120. package/src/telemetry/agentRuntime.ts +62 -0
  121. package/src/telemetry/system.ts +8 -0
  122. package/src/utils/studioFileHistory.test.ts +49 -0
  123. package/src/utils/studioFileHistory.ts +17 -1
  124. package/src/utils/studioTelemetry.ts +16 -10
  125. package/dist/assets/index-dF-CmLZu.js +0 -428
  126. package/dist/assets/index-zQ4JFwwB.css +0 -1
@@ -4,6 +4,8 @@
4
4
  // No PII — only environment characteristics useful for product analytics.
5
5
  // ---------------------------------------------------------------------------
6
6
 
7
+ import { agentRuntimeProperty } from "./agentRuntime";
8
+
7
9
  export interface BrowserSystemMeta {
8
10
  user_agent: string;
9
11
  language: string;
@@ -13,6 +15,8 @@ export interface BrowserSystemMeta {
13
15
  timezone_offset_minutes: number;
14
16
  is_mobile: boolean;
15
17
  studio_version: string;
18
+ /** Which coding agent is driving this Studio, or null when a person is. */
19
+ agent_runtime: string;
16
20
  }
17
21
 
18
22
  const EMPTY_META: BrowserSystemMeta = {
@@ -24,6 +28,7 @@ const EMPTY_META: BrowserSystemMeta = {
24
28
  timezone_offset_minutes: 0,
25
29
  is_mobile: false,
26
30
  studio_version: "dev",
31
+ agent_runtime: "none",
27
32
  };
28
33
 
29
34
  let cached: BrowserSystemMeta | null = null;
@@ -46,6 +51,9 @@ export function getBrowserSystemMeta(): BrowserSystemMeta {
46
51
  timezone_offset_minutes: new Date().getTimezoneOffset(),
47
52
  is_mobile: /Android|iPhone|iPad/i.test(ua),
48
53
  studio_version: typeof __STUDIO_VERSION__ !== "undefined" ? __STUDIO_VERSION__ : "dev",
54
+ // Same value the `studio:*` transport attaches, from the same accessor —
55
+ // two families that disagreed here would split every agent breakdown.
56
+ agent_runtime: agentRuntimeProperty(),
49
57
  };
50
58
  return cached;
51
59
  }
@@ -29,6 +29,55 @@ describe("saveProjectFilesWithHistory", () => {
29
29
  });
30
30
  });
31
31
 
32
+ /**
33
+ * Deleting a clip POSTs `remove-element`, which rewrites the file server-side,
34
+ * and only then saves the duration shrink. Expecting the content read before
35
+ * the mutation made the server refuse that write as a conflict: the save queue
36
+ * paused on the 409 and the clip stayed on the timeline until a reload.
37
+ */
38
+ it("expects what is on disk, not the undo baseline, when they differ", async () => {
39
+ const expectations: Record<string, string | undefined> = {};
40
+ const recordEdit = vi.fn();
41
+
42
+ await saveProjectFilesWithHistory({
43
+ projectId: "project-1",
44
+ label: "Delete timeline clip",
45
+ kind: "timeline",
46
+ files: { "index.html": "removed+shrunk" },
47
+ readFile: async () => "original",
48
+ diskContent: { "index.html": "removed" },
49
+ writeFile: async (path, _content, expectedContent) => {
50
+ expectations[path] = expectedContent;
51
+ },
52
+ recordEdit,
53
+ });
54
+
55
+ expect(expectations["index.html"]).toBe("removed");
56
+ // Undo still goes all the way back, which is the whole reason the two are
57
+ // allowed to differ.
58
+ expect(recordEdit).toHaveBeenCalledWith(
59
+ expect.objectContaining({
60
+ files: { "index.html": { before: "original", after: "removed+shrunk" } },
61
+ }),
62
+ );
63
+ });
64
+
65
+ it("still expects the undo baseline when nothing says otherwise", async () => {
66
+ const expectations: Record<string, string | undefined> = {};
67
+ await saveProjectFilesWithHistory({
68
+ projectId: "project-1",
69
+ label: "Move layer",
70
+ kind: "manual",
71
+ files: { "index.html": "after" },
72
+ readFile: async () => "before",
73
+ writeFile: async (path, _content, expectedContent) => {
74
+ expectations[path] = expectedContent;
75
+ },
76
+ recordEdit: vi.fn(),
77
+ });
78
+ expect(expectations["index.html"]).toBe("before");
79
+ });
80
+
32
81
  it("skips writes and history for unchanged content", async () => {
33
82
  const writeFile = vi.fn();
34
83
  const recordEdit = vi.fn();
@@ -34,6 +34,21 @@ interface SaveProjectFilesWithHistoryInput {
34
34
  readFile: (path: string) => Promise<string>;
35
35
  writeFile: ProjectFileWriter;
36
36
  recordEdit: (entry: RecordEditInput) => Promise<void>;
37
+ /**
38
+ * What a path holds ON DISK right now, when that is not the same as the
39
+ * history's "before".
40
+ *
41
+ * The two are normally one value, so the write's optimistic-concurrency
42
+ * expectation was taken straight from the undo baseline. They come apart when
43
+ * a server-side mutation has already written part of the edit: deleting a clip
44
+ * POSTs `remove-element`, which rewrites the file, and only then saves the
45
+ * duration shrink — expecting the pre-delete content it read at the start. The
46
+ * server had moved the file on, so the write was refused as a conflict, the
47
+ * save queue paused, and the clip stayed on the timeline until a reload.
48
+ *
49
+ * Undo still restores `before`; this only says what to expect on disk.
50
+ */
51
+ diskContent?: Record<string, string>;
37
52
  }
38
53
 
39
54
  export async function readProjectFileContent(pid: string, path: string): Promise<string> {
@@ -57,6 +72,7 @@ export async function saveProjectFilesWithHistory({
57
72
  readFile,
58
73
  writeFile,
59
74
  recordEdit,
75
+ diskContent,
60
76
  }: SaveProjectFilesWithHistoryInput): Promise<string[]> {
61
77
  return serializeStudioFileMutations(writeFile, Object.keys(files), async () => {
62
78
  const snapshots: Record<string, { before: string; after: string }> = {};
@@ -73,7 +89,7 @@ export async function saveProjectFilesWithHistory({
73
89
  const writtenPaths: string[] = [];
74
90
  try {
75
91
  for (const path of changedPaths) {
76
- await writeFile(path, snapshots[path].after, snapshots[path].before);
92
+ await writeFile(path, snapshots[path].after, diskContent?.[path] ?? snapshots[path].before);
77
93
  writtenPaths.push(path);
78
94
  }
79
95
 
@@ -1,6 +1,7 @@
1
1
  import { resolveStudioDistinctId } from "../telemetry/distinctId";
2
2
  import { browserTelemetryAllowed } from "../telemetry/policy";
3
3
  import { canaryEventProperties } from "../telemetry/canary";
4
+ import { agentRuntimeProperty } from "../telemetry/agentRuntime";
4
5
 
5
6
  // PostHog public ingest key — write-only, safe to ship in the client bundle
6
7
  const POSTHOG_API_KEY = "phc_zjjbX0PnWxERXrMHhkEJWj9A9BhGVLRReICgsfTMmpx";
@@ -42,6 +43,11 @@ function isEnabled(): boolean {
42
43
  function getSessionProperties(): EventProperties {
43
44
  return {
44
45
  studio_version: typeof __STUDIO_VERSION__ !== "undefined" ? __STUDIO_VERSION__ : "dev",
46
+ // On EVERY event, not just the audio ones. "Which of these sessions was a
47
+ // person and which was an agent" is a question worth asking of any feature,
48
+ // and a property that only some events carry cannot answer it — the
49
+ // breakdown silently reads as though the agent never used the rest.
50
+ agent_runtime: agentRuntimeProperty(),
45
51
  screen_width: window.screen?.width,
46
52
  screen_height: window.screen?.height,
47
53
  viewport_width: window.innerWidth,
@@ -73,9 +79,8 @@ export function trackStudioEvent(event: string, properties: EventProperties = {}
73
79
  }
74
80
  }
75
81
 
76
- async function flushEvents(): Promise<void> {
77
- if (queue.length === 0) return;
78
-
82
+ /** The queue, shaped for PostHog's batch endpoint — shared by both drain paths. */
83
+ function drainBatch() {
79
84
  const batch = queue.map((e) => ({
80
85
  event: e.event,
81
86
  properties: { ...e.properties, $ip: null },
@@ -83,6 +88,13 @@ async function flushEvents(): Promise<void> {
83
88
  timestamp: e.timestamp,
84
89
  }));
85
90
  queue = [];
91
+ return batch;
92
+ }
93
+
94
+ async function flushEvents(): Promise<void> {
95
+ if (queue.length === 0) return;
96
+
97
+ const batch = drainBatch();
86
98
 
87
99
  const controller = new AbortController();
88
100
  const timeout = setTimeout(() => controller.abort(), FLUSH_TIMEOUT_MS);
@@ -112,13 +124,7 @@ export function flushViaBeacon(): void {
112
124
  flushTimer = null;
113
125
  }
114
126
  if (queue.length === 0) return;
115
- const batch = queue.map((e) => ({
116
- event: e.event,
117
- properties: { ...e.properties, $ip: null },
118
- distinct_id: getDistinctId(),
119
- timestamp: e.timestamp,
120
- }));
121
- queue = [];
127
+ const batch = drainBatch();
122
128
  const body = JSON.stringify({ api_key: POSTHOG_API_KEY, batch });
123
129
  try {
124
130
  navigator.sendBeacon(`${POSTHOG_HOST}/batch/`, body);