@hyperframes/studio 0.7.105 → 0.7.107

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 (71) hide show
  1. package/dist/assets/{hyperframes-player-Jx07OQgw.js → hyperframes-player-BLrfwq5o.js} +1 -1
  2. package/dist/assets/{index-CryZ0PGi.js → index-BA9yhzfR.js} +1 -1
  3. package/dist/assets/{index-Dqi2GmOp.js → index-BSBk5srs.js} +1 -1
  4. package/dist/assets/{index-DuxKGsHZ.js → index-dF-CmLZu.js} +218 -218
  5. package/dist/assets/index-zQ4JFwwB.css +1 -0
  6. package/dist/{chunk-OBAG3GWK.js → chunk-AZYHQC6V.js} +6 -7
  7. package/dist/chunk-AZYHQC6V.js.map +1 -0
  8. package/dist/{domEditingLayers-AT7G6F4L.js → domEditingLayers-7AMZ7GFI.js} +4 -2
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.html +2 -2
  11. package/dist/index.js +5335 -3954
  12. package/dist/index.js.map +1 -1
  13. package/package.json +8 -7
  14. package/src/components/EditorShell.tsx +0 -2
  15. package/src/components/StudioErrorBoundary.test.tsx +97 -0
  16. package/src/components/StudioErrorBoundary.tsx +7 -0
  17. package/src/components/StudioOverlays.tsx +15 -13
  18. package/src/components/editor/DomEditOverlay.tsx +36 -18
  19. package/src/components/editor/DomEditSelectionChrome.test.tsx +119 -31
  20. package/src/components/editor/DomEditSelectionChrome.tsx +29 -3
  21. package/src/components/editor/InlineTextToolbar.test.tsx +296 -0
  22. package/src/components/editor/InlineTextToolbar.tsx +281 -0
  23. package/src/components/editor/OffCanvasIndicators.tsx +38 -0
  24. package/src/components/editor/domEditInlineText.test.ts +86 -0
  25. package/src/components/editor/domEditInlineText.ts +99 -0
  26. package/src/components/editor/domEditInlineTextElement.test.ts +59 -0
  27. package/src/components/editor/domEditOverlayCrop.test.ts +136 -2
  28. package/src/components/editor/domEditOverlayCrop.ts +99 -15
  29. package/src/components/editor/domEditOverlayGeometry.test.ts +13 -0
  30. package/src/components/editor/domEditOverlayGeometry.ts +13 -10
  31. package/src/components/editor/domEditOverlayTransform.ts +88 -0
  32. package/src/components/editor/domEditing.ts +1 -0
  33. package/src/components/editor/domEditingLayers.ts +7 -6
  34. package/src/components/editor/inlineTextStyleRange.test.ts +743 -0
  35. package/src/components/editor/inlineTextStyleRange.ts +593 -0
  36. package/src/components/editor/inlineTextStyleRead.ts +47 -0
  37. package/src/components/editor/useDomEditNudge.ts +2 -2
  38. package/src/components/editor/useInlineTextEditing.tsx +119 -0
  39. package/src/components/feedback/CrashFeedbackPrompt.tsx +27 -0
  40. package/src/components/feedback/StudioFeedbackCard.tsx +373 -0
  41. package/src/components/feedback/feedbackTrigger.test.ts +168 -0
  42. package/src/components/feedback/feedbackTrigger.ts +269 -0
  43. package/src/components/feedback/projectProvenance.test.ts +120 -0
  44. package/src/components/feedback/projectProvenance.ts +83 -0
  45. package/src/components/renders/useRenderQueue.ts +62 -4
  46. package/src/components/storyboard/StoryboardFrameFocus.tsx +2 -2
  47. package/src/components/storyboard/StoryboardViewModeGuard.test.tsx +19 -2
  48. package/src/contexts/DomEditContext.tsx +4 -0
  49. package/src/hooks/domEditPersistFailure.ts +0 -7
  50. package/src/hooks/useAppHotkeys.ts +4 -4
  51. package/src/hooks/useDomEditCommits.test.tsx +103 -21
  52. package/src/hooks/useDomEditCommits.ts +2 -0
  53. package/src/hooks/useDomEditSession.ts +2 -0
  54. package/src/hooks/useDomEditTextCommits.ts +110 -11
  55. package/src/hooks/useFileTree.ts +8 -3
  56. package/src/hooks/useInlineTextEdit.test.tsx +555 -0
  57. package/src/hooks/useInlineTextEdit.ts +320 -0
  58. package/src/player/lib/playbackShortcuts.ts +5 -4
  59. package/src/telemetry/breadcrumbs.test.ts +68 -0
  60. package/src/telemetry/breadcrumbs.ts +70 -0
  61. package/src/telemetry/client.ts +5 -0
  62. package/src/telemetry/events.ts +84 -3
  63. package/src/utils/sourcePatcher.ts +5 -1
  64. package/src/utils/studioHelpers.ts +2 -5
  65. package/src/utils/timelineDiscovery.ts +3 -24
  66. package/src/utils/typingTarget.test.ts +54 -0
  67. package/src/utils/typingTarget.ts +43 -0
  68. package/dist/assets/index-tBPidglp.css +0 -1
  69. package/dist/chunk-OBAG3GWK.js.map +0 -1
  70. package/src/components/StudioFeedbackBar.tsx +0 -217
  71. /package/dist/{domEditingLayers-AT7G6F4L.js.map → domEditingLayers-7AMZ7GFI.js.map} +0 -0
@@ -1,217 +0,0 @@
1
- import { memo, useState, useCallback, useRef, useEffect } from "react";
2
- import { trackStudioFeedback } from "../telemetry/events";
3
-
4
- const DEFAULT_FEEDBACK_INTERVAL = 10;
5
- const AUTO_DISMISS_MS = 20_000;
6
-
7
- function isFeedbackDisabled(): boolean {
8
- try {
9
- return import.meta.env.VITE_HYPERFRAMES_NO_FEEDBACK === "1";
10
- } catch {
11
- return false;
12
- }
13
- }
14
-
15
- // fallow-ignore-next-line complexity
16
- function getFeedbackInterval(): number {
17
- try {
18
- const v = import.meta.env.VITE_HYPERFRAMES_FEEDBACK_INTERVAL as string | undefined;
19
- if (v) {
20
- const n = parseInt(v, 10);
21
- if (Number.isFinite(n) && n > 0) return n;
22
- }
23
- } catch {
24
- // import.meta.env unavailable
25
- }
26
- return DEFAULT_FEEDBACK_INTERVAL;
27
- }
28
-
29
- const STORAGE_KEYS = {
30
- sessionCount: "hyperframes-studio:feedbackSessionCount",
31
- lastPromptedAt: "hyperframes-studio:feedbackLastPromptedAt",
32
- } as const;
33
-
34
- // fallow-ignore-next-line complexity
35
- function shouldShowFeedback(): boolean {
36
- if (isFeedbackDisabled()) return false;
37
- try {
38
- const count = parseInt(localStorage.getItem(STORAGE_KEYS.sessionCount) || "0", 10) || 0;
39
- const lastAt = parseInt(localStorage.getItem(STORAGE_KEYS.lastPromptedAt) || "0", 10) || 0;
40
- return count - lastAt >= getFeedbackInterval();
41
- } catch {
42
- return false;
43
- }
44
- }
45
-
46
- const SESSION_COUNTED_KEY = "hyperframes-studio:feedbackSessionCounted";
47
-
48
- // fallow-ignore-next-line complexity
49
- function incrementSessionCount(): void {
50
- try {
51
- if (sessionStorage.getItem(SESSION_COUNTED_KEY)) return;
52
- sessionStorage.setItem(SESSION_COUNTED_KEY, "1");
53
- const count = parseInt(localStorage.getItem(STORAGE_KEYS.sessionCount) || "0", 10) || 0;
54
- localStorage.setItem(STORAGE_KEYS.sessionCount, String(count + 1));
55
- } catch {
56
- // storage unavailable
57
- }
58
- }
59
-
60
- function markPrompted(): void {
61
- try {
62
- const count = localStorage.getItem(STORAGE_KEYS.sessionCount) || "0";
63
- localStorage.setItem(STORAGE_KEYS.lastPromptedAt, count);
64
- } catch {
65
- // localStorage unavailable
66
- }
67
- }
68
-
69
- // fallow-ignore-next-line complexity
70
- export const StudioFeedbackBar = memo(function StudioFeedbackBar() {
71
- const [visible, setVisible] = useState(false);
72
- const [entered, setEntered] = useState(false);
73
- const [rating, setRating] = useState<number | null>(null);
74
- const [comment, setComment] = useState("");
75
- const [submitted, setSubmitted] = useState(false);
76
- const [exiting, setExiting] = useState(false);
77
- const inputRef = useRef<HTMLInputElement>(null);
78
- const dismissTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
79
-
80
- // On mount: increment session count, check if we should show
81
- useEffect(() => {
82
- incrementSessionCount();
83
- // Small delay so the bar doesn't flash on page load
84
- const showTimer = setTimeout(() => {
85
- if (shouldShowFeedback()) {
86
- setVisible(true);
87
- }
88
- }, 3000);
89
- return () => clearTimeout(showTimer);
90
- }, []);
91
-
92
- // Animate height in on entrance — appearing 3s after load, an instant 32px
93
- // bar shoves the whole preview stack up mid-task.
94
- useEffect(() => {
95
- if (!visible) return;
96
- const raf = requestAnimationFrame(() => setEntered(true));
97
- return () => cancelAnimationFrame(raf);
98
- }, [visible]);
99
-
100
- // Auto-dismiss timer — reset when user interacts (sets rating)
101
- useEffect(() => {
102
- if (!visible || rating !== null || submitted) return;
103
- dismissTimerRef.current = setTimeout(() => {
104
- handleDismiss();
105
- }, AUTO_DISMISS_MS);
106
- return () => {
107
- if (dismissTimerRef.current) clearTimeout(dismissTimerRef.current);
108
- };
109
- // eslint-disable-next-line react-hooks/exhaustive-deps
110
- }, [visible, rating, submitted]);
111
-
112
- // Focus text input when rating is selected
113
- useEffect(() => {
114
- if (rating !== null && inputRef.current) {
115
- inputRef.current.focus();
116
- }
117
- }, [rating]);
118
-
119
- const handleDismiss = useCallback(() => {
120
- setExiting(true);
121
- markPrompted();
122
- setTimeout(() => setVisible(false), 300);
123
- }, []);
124
-
125
- const handleSubmit = useCallback(() => {
126
- if (rating === null) return;
127
- trackStudioFeedback({
128
- rating,
129
- comment: comment.trim() || undefined,
130
- });
131
- setSubmitted(true);
132
- markPrompted();
133
- setTimeout(() => {
134
- setExiting(true);
135
- setTimeout(() => setVisible(false), 300);
136
- }, 1500);
137
- }, [rating, comment]);
138
-
139
- const handleRating = useCallback((n: number) => {
140
- setRating(n);
141
- // Cancel auto-dismiss — user is engaged
142
- if (dismissTimerRef.current) {
143
- clearTimeout(dismissTimerRef.current);
144
- dismissTimerRef.current = null;
145
- }
146
- }, []);
147
-
148
- if (!visible) return null;
149
-
150
- return (
151
- <div
152
- className={[
153
- "flex items-center gap-3 px-4 overflow-hidden border-t border-neutral-800/50 bg-neutral-900/80 text-[11px] transition-all duration-300 motion-reduce:transition-none",
154
- entered && !exiting ? "h-8 opacity-100" : "h-0 opacity-0 border-t-transparent",
155
- ].join(" ")}
156
- >
157
- {submitted ? (
158
- <span className="text-neutral-500">Thanks for the feedback!</span>
159
- ) : rating !== null ? (
160
- <>
161
- <input
162
- ref={inputRef}
163
- type="text"
164
- value={comment}
165
- onChange={(e) => setComment(e.target.value)}
166
- onKeyDown={(e) => {
167
- if (e.key === "Enter") handleSubmit();
168
- if (e.key === "Escape") handleDismiss();
169
- }}
170
- placeholder="Any details? (enter to send, esc to close)"
171
- className="flex-1 bg-transparent border-none text-[11px] text-neutral-300 placeholder-neutral-600 outline-none"
172
- maxLength={500}
173
- />
174
- <button
175
- onClick={handleSubmit}
176
- className="text-[11px] text-neutral-500 hover:text-neutral-300 transition-colors flex-shrink-0"
177
- >
178
- send
179
- </button>
180
- </>
181
- ) : (
182
- <>
183
- <span className="text-neutral-500 flex-shrink-0">Recommend HyperFrames?</span>
184
- <div className="flex items-center gap-0.5">
185
- {Array.from({ length: 11 }, (_, n) => n).map((n) => (
186
- <button
187
- key={n}
188
- onClick={() => handleRating(n)}
189
- className="w-6 h-6 rounded text-[11px] text-neutral-600 hover:text-neutral-200 hover:bg-neutral-700/50 transition-colors"
190
- >
191
- {n}
192
- </button>
193
- ))}
194
- </div>
195
- <div className="flex-1" />
196
- <button
197
- onClick={handleDismiss}
198
- className="text-neutral-700 hover:text-neutral-400 transition-colors flex-shrink-0"
199
- aria-label="Dismiss"
200
- >
201
- <svg
202
- width="12"
203
- height="12"
204
- viewBox="0 0 24 24"
205
- fill="none"
206
- stroke="currentColor"
207
- strokeWidth="2"
208
- strokeLinecap="round"
209
- >
210
- <path d="M18 6L6 18M6 6l12 12" />
211
- </svg>
212
- </button>
213
- </>
214
- )}
215
- </div>
216
- );
217
- });