@hyperframes/studio 0.7.106 → 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.
- package/dist/assets/{hyperframes-player-CvVcx_CV.js → hyperframes-player-BLrfwq5o.js} +1 -1
- package/dist/assets/{index-CRzCCuPH.js → index-BA9yhzfR.js} +1 -1
- package/dist/assets/{index-BbYg_isc.js → index-BSBk5srs.js} +1 -1
- package/dist/assets/{index-DerI0ikN.js → index-dF-CmLZu.js} +218 -218
- package/dist/assets/index-zQ4JFwwB.css +1 -0
- package/dist/{chunk-OBAG3GWK.js → chunk-AZYHQC6V.js} +6 -7
- package/dist/chunk-AZYHQC6V.js.map +1 -0
- package/dist/{domEditingLayers-AT7G6F4L.js → domEditingLayers-7AMZ7GFI.js} +4 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.html +2 -2
- package/dist/index.js +5243 -3933
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/components/EditorShell.tsx +0 -2
- package/src/components/StudioErrorBoundary.test.tsx +97 -0
- package/src/components/StudioErrorBoundary.tsx +7 -0
- package/src/components/StudioOverlays.tsx +15 -13
- package/src/components/editor/DomEditOverlay.tsx +36 -18
- package/src/components/editor/DomEditSelectionChrome.test.tsx +85 -0
- package/src/components/editor/DomEditSelectionChrome.tsx +29 -3
- package/src/components/editor/InlineTextToolbar.test.tsx +296 -0
- package/src/components/editor/InlineTextToolbar.tsx +281 -0
- package/src/components/editor/OffCanvasIndicators.tsx +38 -0
- package/src/components/editor/domEditInlineText.test.ts +86 -0
- package/src/components/editor/domEditInlineText.ts +99 -0
- package/src/components/editor/domEditInlineTextElement.test.ts +59 -0
- package/src/components/editor/domEditing.ts +1 -0
- package/src/components/editor/domEditingLayers.ts +7 -6
- package/src/components/editor/inlineTextStyleRange.test.ts +743 -0
- package/src/components/editor/inlineTextStyleRange.ts +593 -0
- package/src/components/editor/inlineTextStyleRead.ts +47 -0
- package/src/components/editor/useDomEditNudge.ts +2 -2
- package/src/components/editor/useInlineTextEditing.tsx +119 -0
- package/src/components/feedback/CrashFeedbackPrompt.tsx +27 -0
- package/src/components/feedback/StudioFeedbackCard.tsx +373 -0
- package/src/components/feedback/feedbackTrigger.test.ts +168 -0
- package/src/components/feedback/feedbackTrigger.ts +269 -0
- package/src/components/feedback/projectProvenance.test.ts +120 -0
- package/src/components/feedback/projectProvenance.ts +83 -0
- package/src/components/renders/useRenderQueue.ts +62 -4
- package/src/components/storyboard/StoryboardFrameFocus.tsx +2 -2
- package/src/components/storyboard/StoryboardViewModeGuard.test.tsx +19 -2
- package/src/contexts/DomEditContext.tsx +4 -0
- package/src/hooks/domEditPersistFailure.ts +0 -7
- package/src/hooks/useAppHotkeys.ts +4 -4
- package/src/hooks/useDomEditCommits.test.tsx +103 -21
- package/src/hooks/useDomEditCommits.ts +2 -0
- package/src/hooks/useDomEditSession.ts +2 -0
- package/src/hooks/useDomEditTextCommits.ts +110 -11
- package/src/hooks/useFileTree.ts +8 -3
- package/src/hooks/useInlineTextEdit.test.tsx +555 -0
- package/src/hooks/useInlineTextEdit.ts +320 -0
- package/src/player/lib/playbackShortcuts.ts +5 -4
- package/src/telemetry/breadcrumbs.test.ts +68 -0
- package/src/telemetry/breadcrumbs.ts +70 -0
- package/src/telemetry/client.ts +5 -0
- package/src/telemetry/events.ts +84 -3
- package/src/utils/sourcePatcher.ts +5 -1
- package/src/utils/studioHelpers.ts +2 -5
- package/src/utils/timelineDiscovery.ts +3 -24
- package/src/utils/typingTarget.test.ts +54 -0
- package/src/utils/typingTarget.ts +43 -0
- package/dist/assets/index-tBPidglp.css +0 -1
- package/dist/chunk-OBAG3GWK.js.map +0 -1
- package/src/components/StudioFeedbackBar.tsx +0 -217
- /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
|
-
});
|
|
File without changes
|