@agent-native/core 0.176.1 → 0.177.0-nightly-20260828155142

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/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/actions/set-workspace-feature-flag.ts +2 -2
  3. package/corpus/templates/analytics/app/components/agents/FeatureFlagsFleetPanel.tsx +16 -1
  4. package/corpus/templates/analytics/changelog/2026-08-27-show-analytics-s-own-registered-flags-in-fleet-feature-flag-.md +6 -0
  5. package/corpus/templates/analytics/server/lib/workspace-feature-flags.ts +204 -32
  6. package/corpus/templates/assets/changelog/2026-08-26-templates-across-brand-kits.md +1 -0
  7. package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +61 -68
  8. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +9 -3
  9. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +3 -3
  10. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +8 -3
  11. package/corpus/templates/calendar/app/lib/calendar-timezone.ts +12 -3
  12. package/corpus/templates/calendar/changelog/2026-08-28-all-day-events-stay-in-the-compact-top-bar-in-day-and-week-v.md +6 -0
  13. package/corpus/templates/calendar/changelog/2026-08-28-event-creators-can-choose-whether-a-meeting-shows-as-free-or.md +6 -0
  14. package/corpus/templates/clips/app/global.css +0 -27
  15. package/corpus/templates/clips/app/i18n/en-US.ts +2 -0
  16. package/corpus/templates/clips/app/routes/download.tsx +131 -16
  17. package/corpus/templates/clips/app/routes/share.$shareId.tsx +35 -20
  18. package/corpus/templates/clips/server/routes/api/clips-latest.json.get.ts +77 -22
  19. package/corpus/templates/forms/actions/response-insights.ts +4 -4
  20. package/corpus/templates/forms/actions/retry-response-deliveries.ts +26 -0
  21. package/corpus/templates/forms/server/db/schema.ts +64 -17
  22. package/corpus/templates/forms/server/handlers/submissions.ts +1022 -94
  23. package/corpus/templates/forms/server/lib/integrations.ts +100 -33
  24. package/corpus/templates/forms/server/middleware/00-public-cors.ts +1 -1
  25. package/corpus/templates/forms/server/plugins/db.ts +40 -0
  26. package/corpus/templates/plan/agent-native.json +1 -4
  27. package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +6 -1
  28. package/corpus/templates/slides/app/components/editor/EditorActionCluster.tsx +2 -2
  29. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +6 -6
  30. package/corpus/templates/slides/app/components/editor/ImageOverlay.tsx +78 -12
  31. package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +4 -4
  32. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +111 -27
  33. package/corpus/templates/slides/app/components/editor/SlidesLayersPanel.tsx +20 -34
  34. package/corpus/templates/slides/app/components/editor/slide-style.ts +2 -0
  35. package/corpus/templates/slides/app/global.css +35 -0
  36. package/corpus/templates/slides/app/lib/sanitize-slide-html.ts +133 -5
  37. package/corpus/templates/slides/app/lib/slide-image-replacement.ts +675 -0
  38. package/corpus/templates/slides/app/lib/validate-native-slide-html.ts +1 -0
  39. package/corpus/templates/slides/app/pages/DeckEditor.tsx +44 -44
  40. package/corpus/templates/slides/app/root.tsx +1 -22
  41. package/corpus/templates/slides/changelog/2026-08-27-image-fit-controls-apply-cover-crops-and-position.md +6 -0
  42. package/corpus/templates/slides/changelog/2026-08-28-shared-slides-with-a-style-block-render-again.md +6 -0
  43. package/dist/action.d.ts +3 -1
  44. package/dist/client/FeedbackButton.d.ts +12 -0
  45. package/dist/client/FeedbackButton.js +62 -28
  46. package/dist/client/app-providers.js +13 -1
  47. package/dist/client/feedback-context.d.ts +1 -0
  48. package/dist/client/feedback-context.js +3 -2
  49. package/dist/client/host-bridge.d.ts +1 -0
  50. package/dist/client/observability/ThumbsFeedback.js +73 -10
  51. package/dist/client/webmcp.d.ts +4 -0
  52. package/dist/client/webmcp.js +51 -4
  53. package/dist/collab/routes.d.ts +1 -1
  54. package/dist/collab/struct-routes.d.ts +1 -1
  55. package/dist/index.browser.d.ts +1 -1
  56. package/dist/index.browser.js +1 -1
  57. package/dist/observability/routes.d.ts +5 -5
  58. package/dist/observability/routes.js +8 -2
  59. package/dist/observability/store.d.ts +1 -1
  60. package/dist/observability/store.js +18 -3
  61. package/dist/observability/types.d.ts +1 -0
  62. package/dist/progress/routes.d.ts +1 -1
  63. package/dist/resources/handlers.d.ts +2 -2
  64. package/dist/secrets/routes.d.ts +9 -9
  65. package/dist/server/action-routes.d.ts +1 -6
  66. package/dist/server/action-routes.js +58 -17
  67. package/dist/server/agent-chat-plugin.js +8 -1
  68. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  69. package/dist/server/transcribe-voice.d.ts +1 -1
  70. package/docs/content/webmcp.mdx +31 -12
  71. package/package.json +1 -1
@@ -55,6 +55,14 @@ interface SubmissionPayload {
55
55
  clientSurface?: string | null;
56
56
  }
57
57
 
58
+ export interface IntegrationDeliverySnapshot {
59
+ id: string;
60
+ type: IntegrationType;
61
+ name: string;
62
+ url: string;
63
+ payload: unknown;
64
+ }
65
+
58
66
  /** Human-readable label for a client-surface token. */
59
67
  function clientSurfaceLabel(surface: string): string {
60
68
  switch (surface) {
@@ -142,7 +150,7 @@ function formatDebugContext(submission: SubmissionPayload): string[] {
142
150
  );
143
151
  }
144
152
  if (submission.activeRunId) {
145
- lines.push(`Run: \`${submission.activeRunId}\``);
153
+ lines.push(`Request ID: \`${submission.activeRunId}\``);
146
154
  }
147
155
  if (submission.pageUrl) {
148
156
  const appLabel = appLabelFromUrl(submission.pageUrl);
@@ -308,20 +316,89 @@ const payloadBuilders: Record<
308
316
  webhook: buildWebhookPayload,
309
317
  };
310
318
 
319
+ export function buildIntegrationDeliverySnapshot(
320
+ integration: FormIntegration,
321
+ submission: SubmissionPayload,
322
+ ): IntegrationDeliverySnapshot {
323
+ const buildPayload = payloadBuilders[integration.type] ?? buildWebhookPayload;
324
+ return {
325
+ id: integration.id,
326
+ type: integration.type,
327
+ name: integration.name,
328
+ url: integration.url,
329
+ payload: buildPayload(submission),
330
+ };
331
+ }
332
+
333
+ export function buildIntegrationDeliverySnapshots(
334
+ integrations: FormIntegration[],
335
+ submission: SubmissionPayload,
336
+ ): IntegrationDeliverySnapshot[] {
337
+ return integrations
338
+ .filter((integration) => integration.enabled && integration.url)
339
+ .map((integration) =>
340
+ buildIntegrationDeliverySnapshot(integration, submission),
341
+ );
342
+ }
343
+
344
+ export async function deliverIntegrationDelivery(
345
+ snapshot: IntegrationDeliverySnapshot,
346
+ idempotencyKey?: string,
347
+ ): Promise<void> {
348
+ if (!isWebhookUrlAllowed(snapshot.url)) {
349
+ throw new Error("blocked URL");
350
+ }
351
+ const result = await deliverJsonWebhook({
352
+ url: snapshot.url,
353
+ payload: snapshot.payload,
354
+ ...(idempotencyKey
355
+ ? { headers: { "Idempotency-Key": idempotencyKey } }
356
+ : {}),
357
+ });
358
+ if (result.ok) return;
359
+ if (result.blocked) throw new Error("blocked URL");
360
+ throw new Error(
361
+ result.status
362
+ ? `destination returned ${result.status}`
363
+ : "destination request failed",
364
+ );
365
+ }
366
+
311
367
  // ---------------------------------------------------------------------------
312
368
  // Fire integrations
313
369
  // ---------------------------------------------------------------------------
314
370
 
315
- /** Fire all enabled integrations for a submission. Never throws. */
371
+ export type DeliveryStatus = "pending" | "succeeded" | "failed";
372
+ export type DeliveryStatuses = Record<string, DeliveryStatus>;
373
+
374
+ export function integrationDeliveryKey(integrationId: string): string {
375
+ return `integration:${integrationId}`;
376
+ }
377
+
378
+ interface FireIntegrationsOptions {
379
+ deliveryStatus?: Readonly<DeliveryStatuses>;
380
+ onStatusChange?: (
381
+ destination: string,
382
+ status: DeliveryStatus,
383
+ ) => Promise<void> | void;
384
+ }
385
+
386
+ /** Fire enabled integrations, skipping destinations already delivered. */
316
387
  export async function fireIntegrations(
317
388
  integrations: FormIntegration[],
318
389
  submission: SubmissionPayload,
319
- ): Promise<void> {
390
+ options: FireIntegrationsOptions = {},
391
+ ): Promise<DeliveryStatuses> {
320
392
  const enabled = integrations.filter((i) => i.enabled && i.url);
321
- if (enabled.length === 0) return;
393
+ const statuses: DeliveryStatuses = { ...(options.deliveryStatus ?? {}) };
394
+ if (enabled.length === 0) return statuses;
322
395
 
323
- await Promise.allSettled(
396
+ const results = await Promise.allSettled(
324
397
  enabled.map(async (integration) => {
398
+ const destination = integrationDeliveryKey(integration.id);
399
+ if (options.deliveryStatus?.[destination] === "succeeded") return;
400
+
401
+ let status: DeliveryStatus = "failed";
325
402
  // SSRF guard — a form-author can persist any URL in their integration
326
403
  // config. Anonymous submissions then trigger a server-side POST. Block
327
404
  // private IPs, cloud-metadata endpoints, and non-http(s) schemes
@@ -330,38 +407,28 @@ export async function fireIntegrations(
330
407
  console.warn(
331
408
  `[integrations] ${integration.type} "${integration.name}" rejected: blocked URL`,
332
409
  );
333
- return;
334
- }
335
-
336
- const buildPayload =
337
- payloadBuilders[integration.type] ?? buildWebhookPayload;
338
- const payload = buildPayload(submission);
339
-
340
- try {
341
- const result = await deliverJsonWebhook({
342
- url: integration.url,
343
- payload,
344
- });
345
- if (!result.ok) {
346
- if (result.blocked) {
347
- console.warn(
348
- `[integrations] ${integration.type} "${integration.name}" rejected: blocked URL`,
349
- );
350
- return;
351
- }
410
+ } else {
411
+ try {
412
+ await deliverIntegrationDelivery(
413
+ buildIntegrationDeliverySnapshot(integration, submission),
414
+ );
415
+ status = "succeeded";
416
+ } catch (err) {
352
417
  console.warn(
353
- result.status
354
- ? `[integrations] ${integration.type} "${integration.name}" returned ${result.status}`
355
- : `[integrations] ${integration.type} "${integration.name}" failed:`,
356
- result.error,
418
+ `[integrations] ${integration.type} "${integration.name}" failed:`,
419
+ err,
357
420
  );
358
421
  }
359
- } catch (err) {
360
- console.warn(
361
- `[integrations] ${integration.type} "${integration.name}" failed:`,
362
- err,
363
- );
364
422
  }
423
+
424
+ statuses[destination] = status;
425
+ await options.onStatusChange?.(destination, status);
365
426
  }),
366
427
  );
428
+
429
+ const rejected = results.find(
430
+ (result): result is PromiseRejectedResult => result.status === "rejected",
431
+ );
432
+ if (rejected) throw rejected.reason;
433
+ return statuses;
367
434
  }
@@ -28,7 +28,7 @@ export default defineEventHandler((event) => {
28
28
  setResponseHeader(
29
29
  event,
30
30
  "Access-Control-Allow-Headers",
31
- "Content-Type,Accept",
31
+ "Content-Type,Accept,Idempotency-Key",
32
32
  );
33
33
 
34
34
  if (getMethod(event) === "OPTIONS") {
@@ -150,6 +150,46 @@ CREATE INDEX IF NOT EXISTS form_shares_resource_idx ON form_shares (resource_id,
150
150
  sqlite: `ALTER TABLE responses ADD COLUMN IF NOT EXISTS client_surface TEXT`,
151
151
  },
152
152
  },
153
+ {
154
+ version: 13,
155
+ name: "responses-idempotency-key",
156
+ sql: {
157
+ postgres: `ALTER TABLE responses ADD COLUMN IF NOT EXISTS idempotency_key TEXT;
158
+ CREATE UNIQUE INDEX IF NOT EXISTS responses_form_idempotency_key_idx ON responses (form_id, idempotency_key)`,
159
+ sqlite: `ALTER TABLE responses ADD COLUMN IF NOT EXISTS idempotency_key TEXT;
160
+ CREATE UNIQUE INDEX IF NOT EXISTS responses_form_idempotency_key_idx ON responses (form_id, idempotency_key)`,
161
+ },
162
+ },
163
+ {
164
+ version: 14,
165
+ name: "responses-delivery-status",
166
+ sql: {
167
+ postgres: `ALTER TABLE responses ADD COLUMN IF NOT EXISTS delivery_status TEXT`,
168
+ sqlite: `ALTER TABLE responses ADD COLUMN IF NOT EXISTS delivery_status TEXT`,
169
+ },
170
+ },
171
+ {
172
+ version: 15,
173
+ name: "response-delivery-snapshots",
174
+ sql: `ALTER TABLE responses ADD COLUMN IF NOT EXISTS delivery_snapshot TEXT;
175
+ CREATE TABLE IF NOT EXISTS response_deliveries (
176
+ id TEXT PRIMARY KEY,
177
+ response_id TEXT NOT NULL REFERENCES responses(id),
178
+ destination TEXT NOT NULL,
179
+ kind TEXT NOT NULL,
180
+ payload TEXT NOT NULL,
181
+ status TEXT NOT NULL DEFAULT 'pending',
182
+ claim_token TEXT,
183
+ claimed_at TEXT,
184
+ error_message TEXT,
185
+ created_at TEXT NOT NULL,
186
+ updated_at TEXT NOT NULL
187
+ );
188
+ CREATE UNIQUE INDEX IF NOT EXISTS response_deliveries_response_destination_idx
189
+ ON response_deliveries (response_id, destination);
190
+ CREATE INDEX IF NOT EXISTS response_deliveries_status_idx
191
+ ON response_deliveries (status, claimed_at)`,
192
+ },
153
193
  ],
154
194
  { table: "forms_migrations" },
155
195
  );
@@ -1,10 +1,7 @@
1
1
  {
2
2
  "version": 1,
3
3
  "onboarding": {
4
- "firstRun": {
5
- "development": "off",
6
- "production": "connect-and-integrations"
7
- }
4
+ "firstRun": "off"
8
5
  },
9
6
  "doctor": {
10
7
  "failOnBuild": false
@@ -1063,9 +1063,14 @@ export function SlideInner({
1063
1063
  // Slides with fmd-slide markup carry their layout in the raw HTML contract;
1064
1064
  // render them as-is so supported semantic classes and inline styles survive.
1065
1065
  const content = typeof slide.content === "string" ? slide.content : "";
1066
+ const trimmedContent = content.trimStart();
1067
+ const isConvertedMarkdownImage =
1068
+ /^<img\b\s+data-markdown-image(?:\s*=\s*(?:"true"|'true'|true))?(?:\s|>)/i.test(
1069
+ trimmedContent,
1070
+ );
1066
1071
  const isRawHtml =
1067
1072
  content.includes('class="fmd-slide"') ||
1068
- content.trimStart().startsWith("<") ||
1073
+ (trimmedContent.startsWith("<") && !isConvertedMarkdownImage) ||
1069
1074
  ["blank", "section", "statement", "full-image"].includes(slide.layout);
1070
1075
 
1071
1076
  if (!isRawHtml && slide.layout === "two-column") {
@@ -4,7 +4,7 @@ import {
4
4
  IconCircle,
5
5
  IconLine,
6
6
  IconPlus,
7
- IconShape2,
7
+ IconShape,
8
8
  IconSquare,
9
9
  IconTextSize,
10
10
  IconTriangle,
@@ -158,7 +158,7 @@ export function EditorActionCluster({
158
158
  shapeMenuOpen || shapeType ? ACTIVE_CLASS : IDLE_CLASS,
159
159
  )}
160
160
  >
161
- <IconShape2 className="size-4" />
161
+ <IconShape className="size-4" />
162
162
  </button>
163
163
  </PopoverTrigger>
164
164
  </TooltipTrigger>
@@ -34,7 +34,7 @@ import {
34
34
  IconPlus,
35
35
  IconSquare,
36
36
  IconTextSize,
37
- IconTransitionRight,
37
+ IconBolt,
38
38
  IconLayersSubtract,
39
39
  } from "@tabler/icons-react";
40
40
  import { useTheme } from "next-themes";
@@ -445,9 +445,9 @@ export default function EditorToolbar({
445
445
  commands.push({
446
446
  id: "element-animations",
447
447
  group: "slideTools",
448
- label: t("editorToolbar.elementAnimations"),
448
+ label: t("animations.title"),
449
449
  keywords: ["animation", "motion", "transition"],
450
- icon: IconTransitionRight,
450
+ icon: IconBolt,
451
451
  active: animationsOpen,
452
452
  run: onToggleAnimations,
453
453
  });
@@ -505,7 +505,7 @@ export default function EditorToolbar({
505
505
  group: "slideTools" as const,
506
506
  label: t(transition.labelKey),
507
507
  keywords: ["slide", "transition", transition.value],
508
- icon: IconTransitionRight,
508
+ icon: IconBolt,
509
509
  active: activeSlideTransition === transition.value,
510
510
  run: () => onChangeSlideTransition(transition.value),
511
511
  })),
@@ -803,8 +803,8 @@ export default function EditorToolbar({
803
803
  : undefined
804
804
  }
805
805
  >
806
- <IconTransitionRight className="size-4" />
807
- {t("editorToolbar.elementAnimations")}
806
+ <IconBolt className="size-4" />
807
+ {t("animations.title")}
808
808
  </DropdownMenuItem>
809
809
  )}
810
810
  {canEdit && currentSlide && onToggleLayers && (
@@ -7,18 +7,25 @@ import {
7
7
  IconMaximize,
8
8
  IconMinimize,
9
9
  } from "@tabler/icons-react";
10
- import { useEffect, useRef } from "react";
10
+ import { useEffect, useLayoutEffect, useRef, useState } from "react";
11
11
  import { createPortal } from "react-dom";
12
12
 
13
+ import {
14
+ IMAGE_OBJECT_POSITION_VALUES,
15
+ type ImageObjectPosition,
16
+ } from "@/lib/slide-image-replacement";
17
+
13
18
  interface ImageOverlayProps {
14
19
  anchorRect: DOMRect;
15
20
  src: string;
16
21
  objectFit: "cover" | "contain";
22
+ objectPosition: ImageObjectPosition;
17
23
  onGenerate: () => void;
18
24
  onLibrary: () => void;
19
25
  onUpload: () => void;
20
26
  onDownload: () => void;
21
27
  onToggleObjectFit: () => void;
28
+ onChangeObjectPosition: (objectPosition: ImageObjectPosition) => void;
22
29
  onClose: () => void;
23
30
  }
24
31
 
@@ -26,15 +33,19 @@ export default function ImageOverlay({
26
33
  anchorRect,
27
34
  src,
28
35
  objectFit,
36
+ objectPosition,
29
37
  onGenerate,
30
38
  onLibrary,
31
39
  onUpload,
32
40
  onDownload,
33
41
  onToggleObjectFit,
42
+ onChangeObjectPosition,
34
43
  onClose,
35
44
  }: ImageOverlayProps) {
36
45
  const t = useT();
37
46
  const menuRef = useRef<HTMLDivElement>(null);
47
+ const [menuHeight, setMenuHeight] = useState(0);
48
+ const isPlaceholder = src.startsWith("placeholder:");
38
49
 
39
50
  useEffect(() => {
40
51
  const handleClick = (e: MouseEvent) => {
@@ -53,6 +64,11 @@ export default function ImageOverlay({
53
64
  };
54
65
  }, [onClose]);
55
66
 
67
+ useLayoutEffect(() => {
68
+ const height = menuRef.current?.getBoundingClientRect().height;
69
+ if (height && height !== menuHeight) setMenuHeight(height);
70
+ }, [anchorRect, isPlaceholder, menuHeight, objectFit]);
71
+
56
72
  const menuWidth = 180;
57
73
  const vw = window.innerWidth;
58
74
  const vh = window.innerHeight;
@@ -61,13 +77,14 @@ export default function ImageOverlay({
61
77
 
62
78
  if (vw < 640) {
63
79
  left = Math.max(8, (vw - menuWidth) / 2);
64
- top = Math.min(anchorRect.bottom + 8, vh - 260);
80
+ top = anchorRect.bottom + 8;
65
81
  } else {
66
82
  left = anchorRect.left - menuWidth - 8;
67
83
  top = anchorRect.top + anchorRect.height / 2 - 100;
68
84
  if (left < 8) left = 8;
69
85
  }
70
- top = Math.max(8, Math.min(top, vh - 220));
86
+ const maxTop = Math.max(8, vh - (menuHeight || 220) - 8);
87
+ top = Math.max(8, Math.min(top, maxTop));
71
88
 
72
89
  return createPortal(
73
90
  <div
@@ -116,15 +133,64 @@ export default function ImageOverlay({
116
133
  <IconDownload className="w-3.5 h-3.5 text-muted-foreground" />
117
134
  Download
118
135
  </button>
119
- <div className="mx-1.5 border-t border-border" />
120
- <button onClick={onToggleObjectFit} className="image-overlay-btn">
121
- {objectFit === "cover" ? (
122
- <IconMinimize className="w-3.5 h-3.5 text-muted-foreground" />
123
- ) : (
124
- <IconMaximize className="w-3.5 h-3.5 text-muted-foreground" />
125
- )}
126
- Fit: {objectFit === "cover" ? "Cover" : "Contain"}
127
- </button>
136
+ {!isPlaceholder && (
137
+ <>
138
+ <div className="mx-1.5 border-t border-border" />
139
+ <button onClick={onToggleObjectFit} className="image-overlay-btn">
140
+ {objectFit === "cover" ? (
141
+ <IconMinimize className="w-3.5 h-3.5 text-muted-foreground" />
142
+ ) : (
143
+ <IconMaximize className="w-3.5 h-3.5 text-muted-foreground" />
144
+ )}
145
+ Fit: {objectFit === "cover" ? "Cover" : "Contain"}
146
+ </button>
147
+ {objectFit === "cover" && (
148
+ <label className="image-overlay-position">
149
+ <span>{t("styleInspector.position")}</span>
150
+ <select
151
+ aria-label={t("styleInspector.position")}
152
+ value={objectPosition}
153
+ onChange={(event) => {
154
+ const value = event.currentTarget.value;
155
+ if (
156
+ IMAGE_OBJECT_POSITION_VALUES.includes(
157
+ value as ImageObjectPosition,
158
+ )
159
+ ) {
160
+ onChangeObjectPosition(value as ImageObjectPosition);
161
+ }
162
+ }}
163
+ >
164
+ <option value="left top">
165
+ {t("styleInspector.top")} {t("styleInspector.left")}
166
+ </option>
167
+ <option value="center top">
168
+ {t("styleInspector.top")} {t("styleInspector.center")}
169
+ </option>
170
+ <option value="right top">
171
+ {t("styleInspector.top")} {t("styleInspector.right")}
172
+ </option>
173
+ <option value="left center">{t("styleInspector.left")}</option>
174
+ <option value="center center">
175
+ {t("styleInspector.center")}
176
+ </option>
177
+ <option value="right center">
178
+ {t("styleInspector.right")}
179
+ </option>
180
+ <option value="left bottom">
181
+ {t("styleInspector.bottom")} {t("styleInspector.left")}
182
+ </option>
183
+ <option value="center bottom">
184
+ {t("styleInspector.bottom")} {t("styleInspector.center")}
185
+ </option>
186
+ <option value="right bottom">
187
+ {t("styleInspector.bottom")} {t("styleInspector.right")}
188
+ </option>
189
+ </select>
190
+ </label>
191
+ )}
192
+ </>
193
+ )}
128
194
  </div>,
129
195
  document.body,
130
196
  );
@@ -34,7 +34,7 @@ import {
34
34
  IconSpacingVertical,
35
35
  IconStackBack,
36
36
  IconStackFront,
37
- IconTransitionRight,
37
+ IconBolt,
38
38
  IconUnderline,
39
39
  IconZoomIn,
40
40
  IconZoomOut,
@@ -256,14 +256,14 @@ export function SlideContextToolbar({
256
256
  MENU_BUTTON_CLASS,
257
257
  animationsOpen && TOGGLE_ACTIVE_CLASS,
258
258
  )}
259
- aria-label={t("editorToolbar.transition")}
259
+ aria-label={t("animations.title")}
260
260
  aria-pressed={animationsOpen}
261
261
  onClick={onOpenAnimations}
262
262
  >
263
- <IconTransitionRight className="size-3.5" />
263
+ <IconBolt className="size-3.5" />
264
264
  </Button>
265
265
  </TooltipTrigger>
266
- <TooltipContent>{t("editorToolbar.transition")}</TooltipContent>
266
+ <TooltipContent>{t("animations.title")}</TooltipContent>
267
267
  </Tooltip>
268
268
  <div className={TOOLBAR_DIVIDER} />
269
269
  </>