@hyperframes/studio 0.8.24 → 0.8.25
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-CTQCs_KG.js → hyperframes-player-CTP_00ix.js} +1 -1
- package/dist/assets/{index-okdE01ky.js → index-Bjb4YqS_.js} +1 -1
- package/dist/assets/{index-Bq3M0sjr.js → index-DSk_ALFr.js} +218 -218
- package/dist/assets/{index-9WJFcXTP.js → index-Di-8Jxqc.js} +1 -1
- package/dist/assets/index-DnRfAiK2.css +1 -0
- package/dist/{chunk-6BT6DTB4.js → chunk-A7S5SGNA.js} +2 -2
- package/dist/chunk-A7S5SGNA.js.map +1 -0
- package/dist/{domEditingLayers-URA7BLWE.js → domEditingLayers-JMMVOGTO.js} +2 -2
- package/dist/index.d.ts +4 -0
- package/dist/index.html +2 -2
- package/dist/index.js +6206 -5011
- package/dist/index.js.map +1 -1
- package/package.json +8 -7
- package/src/components/editor/TopologyLens.test.tsx +294 -0
- package/src/components/editor/TopologyLens.tsx +206 -0
- package/src/components/editor/domEditOverlayGeometry.ts +14 -0
- package/src/components/editor/domEditOverlayGeometryBatch.test.ts +18 -0
- package/src/components/editor/domEditingLayers.test.ts +9 -0
- package/src/components/editor/domEditingLayers.ts +8 -8
- package/src/components/editor/topologyLensGeometry.test.ts +44 -0
- package/src/components/editor/topologyLensGeometry.ts +119 -0
- package/src/components/editor/topologyLensState.test.ts +116 -0
- package/src/components/editor/topologyLensState.ts +72 -0
- package/src/components/nle/PreviewOverlays.topologyLens.test.tsx +126 -0
- package/src/components/nle/PreviewOverlays.tsx +24 -18
- package/src/components/sidebar/CompositionsTab.drag.test.tsx +16 -0
- package/src/components/sidebar/CompositionsTab.tsx +10 -3
- package/src/components/ui/HyperframesLoader.tsx +3 -48
- package/src/components/ui/HyperframesMark.tsx +53 -0
- package/src/contexts/DomEditContext.tsx +12 -0
- package/src/hooks/domEditCommitRunner.test.ts +59 -0
- package/src/hooks/domEditCommitRunner.ts +55 -20
- package/src/hooks/domEditCommitTypes.ts +8 -1
- package/src/hooks/domEditTextCommitPlan.ts +47 -0
- package/src/hooks/useDomEditAttributeCommits.ts +4 -1
- package/src/hooks/useDomEditCommits.test.tsx +207 -8
- package/src/hooks/useDomEditCommits.ts +54 -14
- package/src/hooks/useDomEditPositionPatchCommit.test.tsx +3 -4
- package/src/hooks/useDomEditPositionPatchCommit.ts +27 -29
- package/src/hooks/useDomEditSession.ts +6 -2
- package/src/hooks/useDomEditTextCommits.test.tsx +86 -2
- package/src/hooks/useDomEditTextCommits.ts +64 -86
- package/src/hooks/useDomEditWiring.ts +1 -1
- package/src/hooks/useDomSelection.ts +2 -0
- package/src/hooks/useDomSelectionTypes.ts +2 -0
- package/src/hooks/useExternalFileChangeCoordinator.test.tsx +71 -4
- package/src/hooks/useExternalFileChangeCoordinator.ts +18 -9
- package/src/hooks/useGsapAnimationFetchFallback.test.ts +13 -1
- package/src/hooks/useGsapAnimationFetchFallback.ts +7 -3
- package/src/hooks/useGsapAnimationOps.test.tsx +101 -0
- package/src/hooks/useGsapAnimationOps.ts +4 -4
- package/src/hooks/useGsapAwareEditing.test.tsx +30 -2
- package/src/hooks/useGsapAwareEditing.ts +31 -10
- package/src/hooks/useGsapSelectionHandlers.test.tsx +3 -1
- package/src/hooks/useGsapSelectionHandlers.ts +21 -11
- package/src/hooks/useRenderClipContent.test.ts +27 -0
- package/src/hooks/useRenderClipContent.ts +5 -0
- package/src/hooks/useStudioExternalFileChanges.ts +5 -0
- package/src/player/components/CompositionThumbnail.test.ts +66 -1
- package/src/player/components/CompositionThumbnail.tsx +6 -0
- package/src/player/store/playerStore.test.ts +24 -0
- package/src/player/store/thumbnailSlice.ts +6 -0
- package/src/styles/studio.css +179 -0
- package/src/utils/domEditSaveQueue.test.ts +5 -3
- package/src/utils/domEditSaveQueue.ts +6 -1
- package/src/webmcp/StudioAgentTools.test.ts +70 -0
- package/src/webmcp/StudioAgentTools.tsx +89 -21
- package/src/webmcp/handles.test.ts +172 -15
- package/src/webmcp/handles.ts +152 -35
- package/src/webmcp/tools/animationTools.test.ts +241 -60
- package/src/webmcp/tools/animationTools.ts +191 -104
- package/src/webmcp/tools/contentTools.test.ts +103 -41
- package/src/webmcp/tools/contentTools.ts +149 -105
- package/src/webmcp/tools/inspectTools.test.ts +47 -5
- package/src/webmcp/tools/inspectTools.ts +59 -19
- package/src/webmcp/tools/lookTools.test.ts +185 -38
- package/src/webmcp/tools/lookTools.ts +134 -21
- package/src/webmcp/tools/selectionTools.test.ts +105 -9
- package/src/webmcp/tools/selectionTools.ts +34 -19
- package/src/webmcp/tools/targetedWriteTools.test.ts +125 -0
- package/src/webmcp/tools/transformTools.test.ts +140 -54
- package/src/webmcp/tools/transformTools.ts +215 -56
- package/src/webmcp/useStudioAgentTools.test.tsx +310 -10
- package/src/webmcp/useStudioAgentTools.ts +27 -15
- package/src/webmcp/webmcpTestUtils.ts +37 -0
- package/src/webmcp/writeCoordinator.test.ts +375 -0
- package/src/webmcp/writeCoordinator.ts +473 -0
- package/dist/assets/index-yGhfxxoL.css +0 -1
- package/dist/chunk-6BT6DTB4.js.map +0 -1
- /package/dist/{domEditingLayers-URA7BLWE.js.map → domEditingLayers-JMMVOGTO.js.map} +0 -0
|
@@ -1,66 +1,70 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `studio_animate`: author motion.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* `void ...catch()`, so it returns nothing and cannot be awaited.
|
|
10
|
-
* - `handleGsapAddKeyframeBatch` returns a promise but catches its own failure,
|
|
11
|
-
* so awaiting it proves the call finished, not that it landed.
|
|
12
|
-
* - `handleGsapDeleteAnimation` discards its promise entirely.
|
|
13
|
-
* - `handleGsapUpdateMeta` is the one honest signal: it returns a boolean.
|
|
14
|
-
* Its `false` is ambiguous though, meaning either no selection or a failed
|
|
15
|
-
* write, so the no-selection case is ruled out before dispatch.
|
|
16
|
-
*
|
|
17
|
-
* U8 solved the same problem by reading the result back. That does not work
|
|
18
|
-
* here: the animation list comes from React state that only refreshes on a
|
|
19
|
-
* render, and no render happens inside one tool call. So rather than fake a
|
|
20
|
-
* verification, these report what was dispatched and tell the agent to call
|
|
21
|
-
* `studio_inspect` to see the result. Saying "I asked for this" is honest;
|
|
22
|
-
* saying "this happened" would not be.
|
|
4
|
+
* Add, update, and delete await the shared GSAP commit pipeline. Its promise is
|
|
5
|
+
* the single settlement boundary for persistence and live-preview sync, so a
|
|
6
|
+
* successful tool response never races ahead of the pixels the user sees.
|
|
7
|
+
* Keyframe writes still report dispatch because their existing actor catches
|
|
8
|
+
* its own failure instead of returning a landed signal.
|
|
23
9
|
*/
|
|
24
10
|
|
|
25
11
|
import type { DomEditSelection } from "../../components/editor/domEditingTypes";
|
|
26
|
-
import { toolFailure,
|
|
12
|
+
import { toolFailure, type ToolFailure } from "../toolResult";
|
|
13
|
+
import {
|
|
14
|
+
dispatched,
|
|
15
|
+
runTargetedWrite,
|
|
16
|
+
type StudioWriteResult,
|
|
17
|
+
type TargetedWriteDeps,
|
|
18
|
+
WRITE_RECEIPT_DESCRIPTION,
|
|
19
|
+
} from "../writeCoordinator";
|
|
27
20
|
|
|
28
21
|
export type GsapMethod = "to" | "from" | "set" | "fromTo";
|
|
29
22
|
|
|
30
23
|
const METHODS: readonly GsapMethod[] = ["to", "from", "set", "fromTo"];
|
|
31
24
|
|
|
32
|
-
export interface AnimationToolDeps {
|
|
33
|
-
|
|
34
|
-
getWriteBlockedReason: () => string | null;
|
|
25
|
+
export interface AnimationToolDeps extends TargetedWriteDeps {
|
|
26
|
+
getAnimationsForSelection: (selection: DomEditSelection) => Promise<readonly { id: string }[]>;
|
|
35
27
|
readPlayhead: () => { currentTime: number; duration: number; isPlaying: boolean };
|
|
36
|
-
addAnimation: (method: GsapMethod) =>
|
|
28
|
+
addAnimation: (selection: DomEditSelection, method: GsapMethod) => Promise<boolean>;
|
|
37
29
|
updateAnimation: (
|
|
30
|
+
selection: DomEditSelection,
|
|
38
31
|
animationId: string,
|
|
39
32
|
updates: { duration?: number; ease?: string; position?: number },
|
|
40
33
|
) => Promise<boolean>;
|
|
41
34
|
addKeyframe: (
|
|
35
|
+
selection: DomEditSelection,
|
|
42
36
|
animationId: string,
|
|
43
37
|
percent: number,
|
|
44
38
|
properties: Record<string, number | string>,
|
|
45
39
|
) => Promise<void>;
|
|
46
|
-
deleteAnimation: (animationId: string) =>
|
|
40
|
+
deleteAnimation: (selection: DomEditSelection, animationId: string) => Promise<boolean>;
|
|
47
41
|
}
|
|
48
42
|
|
|
49
43
|
const INSPECT_HINT = "Call studio_inspect to see the result.";
|
|
50
44
|
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return
|
|
45
|
+
async function animationBelongsToTarget(
|
|
46
|
+
deps: AnimationToolDeps,
|
|
47
|
+
selection: DomEditSelection,
|
|
48
|
+
animationId: string,
|
|
49
|
+
): Promise<ToolFailure | null> {
|
|
50
|
+
const animations = await deps.getAnimationsForSelection(selection);
|
|
51
|
+
return animations.some((animation) => animation.id === animationId)
|
|
52
|
+
? null
|
|
53
|
+
: toolFailure(
|
|
54
|
+
"invalid",
|
|
55
|
+
`animation ${animationId} does not belong to the target handle`,
|
|
56
|
+
"Select the target, then call studio_inspect for its current animation ids.",
|
|
57
|
+
);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
function readAnimationId(value: unknown): string | null {
|
|
61
61
|
return typeof value === "string" && value.trim() ? value : null;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
function isRawGsapExpression(value: unknown): value is string {
|
|
65
|
+
return typeof value === "string" && value.startsWith("__raw:");
|
|
66
|
+
}
|
|
67
|
+
|
|
64
68
|
export interface StudioAddAnimationResult {
|
|
65
69
|
method: GsapMethod;
|
|
66
70
|
/** Where it was inserted, which is the playhead, not a value you supplied. */
|
|
@@ -70,26 +74,32 @@ export interface StudioAddAnimationResult {
|
|
|
70
74
|
|
|
71
75
|
export async function studioAddAnimation(
|
|
72
76
|
deps: AnimationToolDeps,
|
|
73
|
-
input: { method?: unknown },
|
|
74
|
-
|
|
77
|
+
input: { handle?: unknown; method?: unknown },
|
|
78
|
+
signal: AbortSignal = new AbortController().signal,
|
|
79
|
+
): Promise<StudioWriteResult<StudioAddAnimationResult>> {
|
|
75
80
|
const method = METHODS.find((candidate) => candidate === input.method);
|
|
76
81
|
if (!method) {
|
|
77
|
-
return
|
|
82
|
+
return preDispatchFailure(
|
|
83
|
+
"add-animation",
|
|
84
|
+
toolFailure("invalid", `method must be one of ${METHODS.join(", ")}`),
|
|
85
|
+
);
|
|
78
86
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
return runTargetedWrite(deps, {
|
|
88
|
+
handle: input.handle,
|
|
89
|
+
operation: "add-animation",
|
|
90
|
+
signal,
|
|
91
|
+
write: async (selection) => {
|
|
92
|
+
const { currentTime } = deps.readPlayhead();
|
|
93
|
+
const landed = await deps.addAnimation(selection, method);
|
|
94
|
+
if (!landed) {
|
|
95
|
+
return toolFailure(
|
|
96
|
+
"failed",
|
|
97
|
+
"the animation did not land",
|
|
98
|
+
"The target may be stale. Call studio_look and try again with its current handle.",
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return dispatched({ method, insertedAtSeconds: currentTime, dispatched: true }, false);
|
|
102
|
+
},
|
|
93
103
|
});
|
|
94
104
|
}
|
|
95
105
|
|
|
@@ -100,42 +110,65 @@ export interface StudioUpdateAnimationResult {
|
|
|
100
110
|
|
|
101
111
|
export async function studioUpdateAnimation(
|
|
102
112
|
deps: AnimationToolDeps,
|
|
103
|
-
input: {
|
|
104
|
-
|
|
113
|
+
input: {
|
|
114
|
+
handle?: unknown;
|
|
115
|
+
animationId?: unknown;
|
|
116
|
+
duration?: unknown;
|
|
117
|
+
ease?: unknown;
|
|
118
|
+
position?: unknown;
|
|
119
|
+
},
|
|
120
|
+
signal: AbortSignal = new AbortController().signal,
|
|
121
|
+
): Promise<StudioWriteResult<StudioUpdateAnimationResult>> {
|
|
105
122
|
const animationId = readAnimationId(input.animationId);
|
|
106
123
|
if (!animationId) {
|
|
107
|
-
return
|
|
124
|
+
return preDispatchFailure(
|
|
125
|
+
"update-animation",
|
|
126
|
+
toolFailure("invalid", "animationId must be a non-empty string", INSPECT_HINT),
|
|
127
|
+
);
|
|
108
128
|
}
|
|
109
129
|
|
|
110
130
|
const updates: { duration?: number; ease?: string; position?: number } = {};
|
|
111
131
|
if (typeof input.duration === "number" && Number.isFinite(input.duration)) {
|
|
112
|
-
if (input.duration < 0)
|
|
132
|
+
if (input.duration < 0)
|
|
133
|
+
return preDispatchFailure(
|
|
134
|
+
"update-animation",
|
|
135
|
+
toolFailure("invalid", "duration must not be negative"),
|
|
136
|
+
);
|
|
113
137
|
updates.duration = input.duration;
|
|
114
138
|
}
|
|
139
|
+
if (isRawGsapExpression(input.ease)) {
|
|
140
|
+
return preDispatchFailure(
|
|
141
|
+
"update-animation",
|
|
142
|
+
toolFailure("invalid", "raw JavaScript expressions are not accepted"),
|
|
143
|
+
);
|
|
144
|
+
}
|
|
115
145
|
if (typeof input.ease === "string" && input.ease.trim()) updates.ease = input.ease;
|
|
116
146
|
if (typeof input.position === "number" && Number.isFinite(input.position)) {
|
|
117
147
|
updates.position = input.position;
|
|
118
148
|
}
|
|
119
149
|
if (Object.keys(updates).length === 0) {
|
|
120
|
-
return
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
// Ruled out BEFORE dispatch on purpose: the handler answers `false` for both
|
|
124
|
-
// "nothing selected" and "the write failed", so a false afterwards would be
|
|
125
|
-
// ambiguous. Eliminating one of the two makes the other one legible.
|
|
126
|
-
const blocked = guard(deps);
|
|
127
|
-
if (blocked) return blocked;
|
|
128
|
-
|
|
129
|
-
const landed = await deps.updateAnimation(animationId, updates);
|
|
130
|
-
if (!landed) {
|
|
131
|
-
return toolFailure(
|
|
132
|
-
"failed",
|
|
133
|
-
`the update to ${animationId} did not land`,
|
|
134
|
-
"The animation id may be stale. studio_inspect lists the current ones.",
|
|
150
|
+
return preDispatchFailure(
|
|
151
|
+
"update-animation",
|
|
152
|
+
toolFailure("invalid", "give at least one of duration, ease, position"),
|
|
135
153
|
);
|
|
136
154
|
}
|
|
137
|
-
|
|
138
|
-
|
|
155
|
+
return runTargetedWrite(deps, {
|
|
156
|
+
handle: input.handle,
|
|
157
|
+
operation: "update-animation",
|
|
158
|
+
signal,
|
|
159
|
+
preflight: (selection) => animationBelongsToTarget(deps, selection, animationId),
|
|
160
|
+
write: async (selection) => {
|
|
161
|
+
const landed = await deps.updateAnimation(selection, animationId, updates);
|
|
162
|
+
if (!landed) {
|
|
163
|
+
return toolFailure(
|
|
164
|
+
"failed",
|
|
165
|
+
`the update to ${animationId} did not land`,
|
|
166
|
+
"The animation id may be stale. studio_inspect lists the current ones.",
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
return dispatched({ animationId, updated: updates }, false);
|
|
170
|
+
},
|
|
171
|
+
});
|
|
139
172
|
}
|
|
140
173
|
|
|
141
174
|
export interface StudioAddKeyframeResult {
|
|
@@ -147,36 +180,58 @@ export interface StudioAddKeyframeResult {
|
|
|
147
180
|
|
|
148
181
|
export async function studioAddKeyframe(
|
|
149
182
|
deps: AnimationToolDeps,
|
|
150
|
-
input: { animationId?: unknown; percent?: unknown; properties?: unknown },
|
|
151
|
-
|
|
183
|
+
input: { handle?: unknown; animationId?: unknown; percent?: unknown; properties?: unknown },
|
|
184
|
+
signal: AbortSignal = new AbortController().signal,
|
|
185
|
+
): Promise<StudioWriteResult<StudioAddKeyframeResult>> {
|
|
152
186
|
const animationId = readAnimationId(input.animationId);
|
|
153
187
|
if (!animationId) {
|
|
154
|
-
return
|
|
188
|
+
return preDispatchFailure(
|
|
189
|
+
"add-keyframe",
|
|
190
|
+
toolFailure("invalid", "animationId must be a non-empty string", INSPECT_HINT),
|
|
191
|
+
);
|
|
155
192
|
}
|
|
156
193
|
const percent = input.percent;
|
|
157
194
|
if (typeof percent !== "number" || !Number.isFinite(percent) || percent < 0 || percent > 100) {
|
|
158
195
|
// Validated here because nothing in the platform checks input against the
|
|
159
196
|
// schema; the tool receives whatever the agent sent.
|
|
160
|
-
return
|
|
197
|
+
return preDispatchFailure(
|
|
198
|
+
"add-keyframe",
|
|
199
|
+
toolFailure("invalid", "percent must be a number between 0 and 100"),
|
|
200
|
+
);
|
|
161
201
|
}
|
|
162
202
|
const raw = input.properties;
|
|
163
203
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
164
|
-
return
|
|
204
|
+
return preDispatchFailure(
|
|
205
|
+
"add-keyframe",
|
|
206
|
+
toolFailure("invalid", "properties must be an object of GSAP property to value"),
|
|
207
|
+
);
|
|
165
208
|
}
|
|
166
209
|
const properties: Record<string, number | string> = {};
|
|
167
210
|
for (const [key, value] of Object.entries(raw)) {
|
|
211
|
+
if (isRawGsapExpression(value)) {
|
|
212
|
+
return preDispatchFailure(
|
|
213
|
+
"add-keyframe",
|
|
214
|
+
toolFailure("invalid", "raw JavaScript expressions are not accepted"),
|
|
215
|
+
);
|
|
216
|
+
}
|
|
168
217
|
if (typeof value === "number" || typeof value === "string") properties[key] = value;
|
|
169
218
|
}
|
|
170
219
|
if (Object.keys(properties).length === 0) {
|
|
171
|
-
return
|
|
220
|
+
return preDispatchFailure(
|
|
221
|
+
"add-keyframe",
|
|
222
|
+
toolFailure("invalid", "properties must contain at least one number or string value"),
|
|
223
|
+
);
|
|
172
224
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
225
|
+
return runTargetedWrite(deps, {
|
|
226
|
+
handle: input.handle,
|
|
227
|
+
operation: "add-keyframe",
|
|
228
|
+
signal,
|
|
229
|
+
preflight: (selection) => animationBelongsToTarget(deps, selection, animationId),
|
|
230
|
+
write: async (selection) => {
|
|
231
|
+
await deps.addKeyframe(selection, animationId, percent, properties);
|
|
232
|
+
return dispatched({ animationId, percent, properties, dispatched: true }, false);
|
|
233
|
+
},
|
|
234
|
+
});
|
|
180
235
|
}
|
|
181
236
|
|
|
182
237
|
export interface StudioDeleteAnimationResult {
|
|
@@ -186,59 +241,81 @@ export interface StudioDeleteAnimationResult {
|
|
|
186
241
|
|
|
187
242
|
export async function studioDeleteAnimation(
|
|
188
243
|
deps: AnimationToolDeps,
|
|
189
|
-
input: { animationId?: unknown },
|
|
190
|
-
|
|
244
|
+
input: { handle?: unknown; animationId?: unknown },
|
|
245
|
+
signal: AbortSignal = new AbortController().signal,
|
|
246
|
+
): Promise<StudioWriteResult<StudioDeleteAnimationResult>> {
|
|
191
247
|
const animationId = readAnimationId(input.animationId);
|
|
192
248
|
if (!animationId) {
|
|
193
|
-
return
|
|
249
|
+
return preDispatchFailure(
|
|
250
|
+
"delete-animation",
|
|
251
|
+
toolFailure("invalid", "animationId must be a non-empty string", INSPECT_HINT),
|
|
252
|
+
);
|
|
194
253
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
254
|
+
return runTargetedWrite(deps, {
|
|
255
|
+
handle: input.handle,
|
|
256
|
+
operation: "delete-animation",
|
|
257
|
+
signal,
|
|
258
|
+
preflight: (selection) => animationBelongsToTarget(deps, selection, animationId),
|
|
259
|
+
write: async (selection) => {
|
|
260
|
+
const landed = await deps.deleteAnimation(selection, animationId);
|
|
261
|
+
if (!landed) {
|
|
262
|
+
return toolFailure(
|
|
263
|
+
"failed",
|
|
264
|
+
`the delete of ${animationId} did not land`,
|
|
265
|
+
"The animation id may be stale. studio_inspect lists the current ones.",
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
return dispatched({ animationId, dispatched: true }, false);
|
|
269
|
+
},
|
|
270
|
+
});
|
|
201
271
|
}
|
|
202
272
|
|
|
203
|
-
const
|
|
273
|
+
const SETTLEMENT_NOTE =
|
|
274
|
+
"Success means persistence and live-preview synchronization have finished. Inspect afterward when exact authored values matter.";
|
|
275
|
+
const KEYFRAME_DISPATCH_CAVEAT = `Reports what was dispatched, not what landed: the keyframe actor does not report back. ${INSPECT_HINT}`;
|
|
204
276
|
|
|
205
277
|
export const STUDIO_ADD_ANIMATION_INPUT_SCHEMA = {
|
|
206
278
|
type: "object",
|
|
207
279
|
properties: {
|
|
280
|
+
handle: { type: "string", description: "A source-safe element handle from studio_look." },
|
|
208
281
|
method: { type: "string", enum: METHODS, description: "The GSAP method to add." },
|
|
209
282
|
},
|
|
210
|
-
required: ["method"],
|
|
283
|
+
required: ["handle", "method"],
|
|
211
284
|
additionalProperties: false,
|
|
212
285
|
} as const;
|
|
213
286
|
|
|
214
287
|
export const STUDIO_ADD_ANIMATION_DESCRIPTION = [
|
|
215
|
-
"Add a GSAP animation to
|
|
288
|
+
"Add a GSAP animation to one element using its source-safe handle from studio_look.",
|
|
216
289
|
"It is inserted AT THE PLAYHEAD, which this tool does not control: call studio_seek first",
|
|
217
290
|
"to choose when it starts. The result reports where the playhead actually was.",
|
|
218
|
-
|
|
291
|
+
SETTLEMENT_NOTE,
|
|
292
|
+
WRITE_RECEIPT_DESCRIPTION,
|
|
219
293
|
].join(" ");
|
|
220
294
|
|
|
221
295
|
export const STUDIO_UPDATE_ANIMATION_INPUT_SCHEMA = {
|
|
222
296
|
type: "object",
|
|
223
297
|
properties: {
|
|
298
|
+
handle: { type: "string", description: "A source-safe element handle from studio_look." },
|
|
224
299
|
animationId: { type: "string", description: "An animation id from studio_inspect." },
|
|
225
300
|
duration: { type: "number", minimum: 0, description: "Duration in seconds." },
|
|
226
301
|
ease: { type: "string", description: "A GSAP ease, for example power2.out." },
|
|
227
302
|
position: { type: "number", description: "Start position in seconds." },
|
|
228
303
|
},
|
|
229
|
-
required: ["animationId"],
|
|
304
|
+
required: ["handle", "animationId"],
|
|
230
305
|
additionalProperties: false,
|
|
231
306
|
} as const;
|
|
232
307
|
|
|
233
308
|
export const STUDIO_UPDATE_ANIMATION_DESCRIPTION = [
|
|
234
309
|
"Change an existing animation's duration, ease or position.",
|
|
235
|
-
"
|
|
236
|
-
"
|
|
310
|
+
"It waits for persistence and live-preview synchronization before reporting success.",
|
|
311
|
+
"Get current ids from studio_inspect.",
|
|
312
|
+
WRITE_RECEIPT_DESCRIPTION,
|
|
237
313
|
].join(" ");
|
|
238
314
|
|
|
239
315
|
export const STUDIO_ADD_KEYFRAME_INPUT_SCHEMA = {
|
|
240
316
|
type: "object",
|
|
241
317
|
properties: {
|
|
318
|
+
handle: { type: "string", description: "A source-safe element handle from studio_look." },
|
|
242
319
|
animationId: { type: "string", description: "An animation id from studio_inspect." },
|
|
243
320
|
percent: {
|
|
244
321
|
type: "number",
|
|
@@ -251,26 +328,36 @@ export const STUDIO_ADD_KEYFRAME_INPUT_SCHEMA = {
|
|
|
251
328
|
description: 'GSAP property to value, for example {"y": -50, "opacity": 0}.',
|
|
252
329
|
},
|
|
253
330
|
},
|
|
254
|
-
required: ["animationId", "percent", "properties"],
|
|
331
|
+
required: ["handle", "animationId", "percent", "properties"],
|
|
255
332
|
additionalProperties: false,
|
|
256
333
|
} as const;
|
|
257
334
|
|
|
258
335
|
export const STUDIO_ADD_KEYFRAME_DESCRIPTION = [
|
|
259
336
|
"Add a keyframe to an existing animation at a percentage through it.",
|
|
260
337
|
"All the properties land in one commit, so they are one undo entry.",
|
|
261
|
-
|
|
338
|
+
KEYFRAME_DISPATCH_CAVEAT,
|
|
339
|
+
WRITE_RECEIPT_DESCRIPTION,
|
|
262
340
|
].join(" ");
|
|
263
341
|
|
|
264
342
|
export const STUDIO_DELETE_ANIMATION_INPUT_SCHEMA = {
|
|
265
343
|
type: "object",
|
|
266
344
|
properties: {
|
|
345
|
+
handle: { type: "string", description: "A source-safe element handle from studio_look." },
|
|
267
346
|
animationId: { type: "string", description: "An animation id from studio_inspect." },
|
|
268
347
|
},
|
|
269
|
-
required: ["animationId"],
|
|
348
|
+
required: ["handle", "animationId"],
|
|
270
349
|
additionalProperties: false,
|
|
271
350
|
} as const;
|
|
272
351
|
|
|
273
352
|
export const STUDIO_DELETE_ANIMATION_DESCRIPTION = [
|
|
274
|
-
"Remove an animation from the
|
|
275
|
-
|
|
353
|
+
"Remove an animation from the element named by handle. Undo reverses it.",
|
|
354
|
+
SETTLEMENT_NOTE,
|
|
355
|
+
WRITE_RECEIPT_DESCRIPTION,
|
|
276
356
|
].join(" ");
|
|
357
|
+
|
|
358
|
+
function preDispatchFailure<T extends object>(
|
|
359
|
+
operation: "add-animation" | "update-animation" | "add-keyframe" | "delete-animation",
|
|
360
|
+
failure: ToolFailure,
|
|
361
|
+
): StudioWriteResult<T> {
|
|
362
|
+
return { ...failure, stage: "refused", operation };
|
|
363
|
+
}
|