@hyperframes/studio 0.6.79 → 0.6.80

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/index.html CHANGED
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
6
6
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
7
7
  <title>HyperFrames Studio</title>
8
- <script type="module" crossorigin src="/assets/index-BpS6tww3.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-D8oim9P5.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-DcyZuBcU.css">
10
10
  </head>
11
11
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/studio",
3
- "version": "0.6.79",
3
+ "version": "0.6.80",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,8 +31,8 @@
31
31
  "@codemirror/view": "6.40.0",
32
32
  "@phosphor-icons/react": "^2.1.10",
33
33
  "mediabunny": "^1.45.3",
34
- "@hyperframes/core": "0.6.79",
35
- "@hyperframes/player": "0.6.79"
34
+ "@hyperframes/core": "0.6.80",
35
+ "@hyperframes/player": "0.6.80"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/react": "19",
@@ -46,7 +46,7 @@
46
46
  "vite": "^6.4.2",
47
47
  "vitest": "^3.2.4",
48
48
  "zustand": "^5.0.0",
49
- "@hyperframes/producer": "0.6.79"
49
+ "@hyperframes/producer": "0.6.80"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "react": "19",
@@ -37,6 +37,7 @@ export const PROP_LABELS: Record<string, string> = {
37
37
  letterSpacing: "Tracking",
38
38
  skewX: "Skew X",
39
39
  skewY: "Skew Y",
40
+ innerText: "Counter Value",
40
41
  };
41
42
 
42
43
  export const PROP_UNITS: Record<string, string> = {
@@ -65,6 +66,7 @@ export const PROP_TOOLTIPS: Record<string, string> = {
65
66
  height: "Element height",
66
67
  autoAlpha: "Like opacity but hides element completely at 0",
67
68
  visibility: "Show or hide the element",
69
+ innerText: "End value for a number roll-up (the number it counts up/down to)",
68
70
  };
69
71
 
70
72
  export const EASE_LABELS: Record<string, string> = {
@@ -154,6 +156,7 @@ export const PROP_CONSTRAINTS: Record<string, { min?: number; max?: number; step
154
156
  y: { step: 1 },
155
157
  fontSize: { min: 1, step: 1 },
156
158
  letterSpacing: { step: 0.1 },
159
+ innerText: { step: 1 },
157
160
  };
158
161
 
159
162
  export function clampPropertyValue(prop: string, value: number): number {
@@ -209,6 +209,75 @@ describe("edit history", () => {
209
209
  expect(state.undo[0].files["index.html"].after).toBe("c");
210
210
  });
211
211
 
212
+ it("coalesces entries with the same coalesceKey within the window (prop: format)", () => {
213
+ const first = buildEditHistoryEntry({
214
+ projectId: "project-1",
215
+ label: "Edit title color",
216
+ kind: "source",
217
+ coalesceKey: "prop:title.color",
218
+ files: {
219
+ "index.html": { before: "a", after: "b" },
220
+ },
221
+ now: 100,
222
+ id: "entry-1",
223
+ });
224
+ const second = buildEditHistoryEntry({
225
+ projectId: "project-1",
226
+ label: "Edit title color",
227
+ kind: "source",
228
+ coalesceKey: "prop:title.color",
229
+ files: {
230
+ "index.html": { before: "b", after: "c" },
231
+ },
232
+ now: 200,
233
+ id: "entry-2",
234
+ });
235
+
236
+ const state = pushEditHistoryEntry(
237
+ pushEditHistoryEntry(createEmptyEditHistory(), first),
238
+ second,
239
+ { coalesceMs: 1000 },
240
+ );
241
+
242
+ expect(state.undo).toHaveLength(1);
243
+ expect(state.undo[0].id).toBe("entry-2");
244
+ expect(state.undo[0].files["index.html"].before).toBe("a");
245
+ expect(state.undo[0].files["index.html"].after).toBe("c");
246
+ });
247
+
248
+ it("does not coalesce entries with different coalesceKeys (cross-prop separation)", () => {
249
+ const titleEdit = buildEditHistoryEntry({
250
+ projectId: "project-1",
251
+ label: "Edit title color",
252
+ kind: "source",
253
+ coalesceKey: "prop:title.color",
254
+ files: {
255
+ "index.html": { before: "a", after: "b" },
256
+ },
257
+ now: 100,
258
+ id: "entry-title",
259
+ });
260
+ const bodyEdit = buildEditHistoryEntry({
261
+ projectId: "project-1",
262
+ label: "Edit body color",
263
+ kind: "source",
264
+ coalesceKey: "prop:body.color",
265
+ files: {
266
+ "index.html": { before: "b", after: "c" },
267
+ },
268
+ now: 200,
269
+ id: "entry-body",
270
+ });
271
+
272
+ const state = pushEditHistoryEntry(
273
+ pushEditHistoryEntry(createEmptyEditHistory(), titleEdit),
274
+ bodyEdit,
275
+ { coalesceMs: 1000 },
276
+ );
277
+
278
+ expect(state.undo.map((e) => e.id)).toEqual(["entry-title", "entry-body"]);
279
+ });
280
+
212
281
  it("does not coalesce source editor edits outside the coalesce window", () => {
213
282
  const first = buildEditHistoryEntry({
214
283
  projectId: "project-1",
@@ -241,4 +310,47 @@ describe("edit history", () => {
241
310
 
242
311
  expect(state.undo.map((entry) => entry.id)).toEqual(["entry-1", "entry-2"]);
243
312
  });
313
+
314
+ it("coalesces entries exactly at the coalesce boundary (delta === coalesceMs is inclusive)", () => {
315
+ const first = buildEditHistoryEntry({
316
+ projectId: "project-1",
317
+ label: "Edit source",
318
+ kind: "source",
319
+ coalesceKey: "source:index.html",
320
+ files: {
321
+ "index.html": { before: "a", after: "b" },
322
+ },
323
+ now: 100,
324
+ id: "entry-1",
325
+ });
326
+ const second = buildEditHistoryEntry({
327
+ projectId: "project-1",
328
+ label: "Edit source",
329
+ kind: "source",
330
+ coalesceKey: "source:index.html",
331
+ files: {
332
+ "index.html": { before: "b", after: "c" },
333
+ },
334
+ now: 1100, // exactly coalesceMs=1000ms after first
335
+ id: "entry-2",
336
+ });
337
+
338
+ const state = pushEditHistoryEntry(
339
+ pushEditHistoryEntry(createEmptyEditHistory(), first),
340
+ second,
341
+ { coalesceMs: 1000 },
342
+ );
343
+
344
+ // Boundary is <=: delta of exactly 1000ms coalesces into one entry.
345
+ expect(state.undo).toHaveLength(1);
346
+ expect(state.undo[0].id).toBe("entry-2");
347
+ expect(state.undo[0].files["index.html"].before).toBe("a");
348
+ expect(state.undo[0].files["index.html"].after).toBe("c");
349
+ });
350
+
351
+ it.todo("gesture-start/commit collapses intermediate drag steps into one undo entry");
352
+
353
+ it.todo(
354
+ "origin:applyPatches edits are excluded from undo stack to prevent undo loops (requires SDK session)",
355
+ );
244
356
  });