@agent-native/core 0.192.0 → 0.192.1-nightly-20260926144920

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 (82) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/calendar/actions/cancel-booking.ts +12 -4
  3. package/corpus/templates/calendar/actions/list-bookings.ts +23 -2
  4. package/corpus/templates/calendar/actions/list-events.ts +42 -29
  5. package/corpus/templates/calendar/actions/update-booking-link.ts +27 -1
  6. package/corpus/templates/calendar/app/hooks/use-bookings.ts +9 -2
  7. package/corpus/templates/calendar/app/i18n/en-US.ts +8 -0
  8. package/corpus/templates/calendar/app/lib/booking-sorting.ts +7 -0
  9. package/corpus/templates/calendar/app/lib/event-colors.ts +29 -4
  10. package/corpus/templates/calendar/app/pages/BookingsList.tsx +99 -19
  11. package/corpus/templates/calendar/app/pages/CalendarView.tsx +5 -11
  12. package/corpus/templates/calendar/app/pages/ManageBookingPage.tsx +5 -0
  13. package/corpus/templates/calendar/changelog/2026-09-25-ambiguous-zoom-bookings-need-review.md +6 -0
  14. package/corpus/templates/calendar/changelog/2026-09-25-bookings-list-shows-newest-bookings-first.md +6 -0
  15. package/corpus/templates/calendar/changelog/2026-09-25-canceling-a-booking-now-resolves-its-zoom-meeting-first.md +6 -0
  16. package/corpus/templates/calendar/changelog/2026-09-25-colleague-events-use-sidebar-colors.md +6 -0
  17. package/corpus/templates/calendar/changelog/2026-09-25-public-booking-pages-reject-malformed-meeting-settings-befor.md +6 -0
  18. package/corpus/templates/calendar/changelog/2026-09-25-shared-booking-cancellations-require-editor-access-and-unres.md +6 -0
  19. package/corpus/templates/calendar/server/db/schema.ts +5 -0
  20. package/corpus/templates/calendar/server/handlers/availability.ts +32 -29
  21. package/corpus/templates/calendar/server/handlers/bookings.ts +189 -62
  22. package/corpus/templates/calendar/server/lib/booking-link-utils.ts +46 -1
  23. package/corpus/templates/calendar/server/lib/zoom.ts +66 -1
  24. package/corpus/templates/calendar/server/plugins/db.ts +13 -0
  25. package/corpus/templates/calendar/shared/api.ts +4 -0
  26. package/corpus/templates/clips/app/components/capture-install-options.tsx +9 -6
  27. package/corpus/templates/clips/app/components/clips-command-menu.tsx +4 -0
  28. package/corpus/templates/clips/app/i18n/en-US.ts +5 -4
  29. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +4 -0
  30. package/corpus/templates/clips/app/routes/share.$shareId.tsx +7 -33
  31. package/corpus/templates/clips/changelog/2026-09-26-desktop-platform-icon.md +6 -0
  32. package/corpus/templates/clips/changelog/2026-09-26-the-empty-comments-state-now-explains-how-screen-recordings-.md +6 -0
  33. package/corpus/templates/clips/changelog/2026-09-26-the-empty-comments-view-has-a-clearer-headline-and-a-focused.md +6 -0
  34. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +26 -2
  35. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +74 -40
  36. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +64 -42
  37. package/corpus/templates/design/app/components/design/design-canvas/creation.ts +1 -0
  38. package/corpus/templates/design/app/components/design/multi-screen/types.ts +5 -2
  39. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-screen.ts +56 -25
  40. package/corpus/templates/design/app/routes/design-systems.tsx +2 -4
  41. package/corpus/templates/design/app/routes/design.$id.tsx +2 -1
  42. package/corpus/templates/design/app/routes/public-design-meta.ts +0 -39
  43. package/corpus/templates/design/app/routes/templates.tsx +2 -4
  44. package/corpus/templates/design/app/routes/visual-edit_.$id.tsx +2 -1
  45. package/corpus/templates/design/changelog/2026-09-25-escape-finishes-open-pen-paths.md +7 -0
  46. package/corpus/templates/design/changelog/2026-09-25-pen-paths-remain-available-after-save-failures-and-retries-k.md +6 -0
  47. package/corpus/templates/design/changelog/2026-09-25-pen-retries-keep-extending-the-selected-vector-after-a-save-.md +6 -0
  48. package/corpus/templates/design/changelog/2026-09-25-pressing-enter-finishes-a-new-pen-path-and-selects-its-vector-on-move.md +6 -0
  49. package/corpus/templates/design/changelog/2026-09-26-cmd-d-places-screen-duplicates-in-the-next-open-slot.md +6 -0
  50. package/corpus/templates/design/changelog/2026-09-26-dropping-a-layer-beside-a-sibling-keeps-that-insertion-point.md +6 -0
  51. package/corpus/templates/design/changelog/2026-09-26-layers-dragged-out-of-frames-now-land-at-the-board-level-and.md +6 -0
  52. package/corpus/templates/design/changelog/2026-09-26-opening-shared-designs-no-longer-fails-during-client-startup.md +6 -0
  53. package/corpus/templates/design/package.json +1 -1
  54. package/corpus/templates/design/scripts/check-client-server-boundary.ts +55 -0
  55. package/corpus/templates/design/server/lib/public-design-meta.server.ts +40 -0
  56. package/corpus/templates/mail/app/components/email/EmailListItem.tsx +11 -9
  57. package/corpus/templates/mail/app/components/email/EmailThread.tsx +9 -1
  58. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +2 -2
  59. package/corpus/templates/mail/app/components/settings/AiFilterSection.tsx +25 -2
  60. package/corpus/templates/mail/app/hooks/use-emails.ts +159 -27
  61. package/corpus/templates/mail/app/i18n/en-US.ts +2 -1
  62. package/corpus/templates/mail/app/lib/label-display.ts +6 -0
  63. package/corpus/templates/mail/app/pages/InboxPage.tsx +5 -0
  64. package/corpus/templates/mail/changelog/2026-09-26-fixed-importance-actions-label-display-and-triage-loading-fe.md +6 -0
  65. package/corpus/templates/mail/changelog/2026-09-26-mail-cancels-stale-thread-read-cooldown-retries-after-a-newe.md +6 -0
  66. package/corpus/templates/mail/server/lib/google-api.ts +12 -7
  67. package/corpus/templates/slides/app/i18n/en-US.ts +0 -1
  68. package/corpus/templates/slides/app/pages/Index.tsx +1 -29
  69. package/corpus/templates/slides/changelog/2026-09-26-create-decks-from-the-prompt-without-a-separate-new-deck-button.md +6 -0
  70. package/corpus/templates/tasks/.agents/skills/agent-watchdog/SKILL.md +7 -2
  71. package/dist/client/settings/UsageSection.js +18 -5
  72. package/dist/client/settings/recent-prompt-groups.d.ts +12 -0
  73. package/dist/client/settings/recent-prompt-groups.js +20 -0
  74. package/dist/client/use-action.js +7 -0
  75. package/dist/observability/routes.d.ts +5 -5
  76. package/dist/progress/routes.d.ts +1 -1
  77. package/dist/provider-api/actions/custom-provider-registration.d.ts +14 -14
  78. package/dist/provider-api/actions/provider-api.d.ts +13 -13
  79. package/dist/secrets/routes.d.ts +3 -3
  80. package/dist/server/action-routes.js +25 -2
  81. package/dist/server/agent-engine-ollama-models-route.d.ts +2 -2
  82. package/package.json +3 -3
@@ -222,6 +222,19 @@ CREATE INDEX IF NOT EXISTS idx_bookings_slug_start ON bookings (slug, "start");`
222
222
  name: "bookings-meeting-link-pending",
223
223
  sql: `ALTER TABLE bookings ADD COLUMN IF NOT EXISTS meeting_link_pending BOOLEAN NOT NULL DEFAULT false`,
224
224
  },
225
+ {
226
+ version: 25,
227
+ name: "bookings-zoom-needs-review",
228
+ sql: `ALTER TABLE bookings ADD COLUMN IF NOT EXISTS zoom_needs_review BOOLEAN NOT NULL DEFAULT false`,
229
+ },
230
+ {
231
+ version: 26,
232
+ name: "bookings-zoom-provider-identifiers",
233
+ sql: `
234
+ ALTER TABLE bookings ADD COLUMN IF NOT EXISTS zoom_meeting_id TEXT;
235
+ ALTER TABLE bookings ADD COLUMN IF NOT EXISTS zoom_account_id TEXT;
236
+ `,
237
+ },
225
238
  ],
226
239
  { table: "calendar_migrations" },
227
240
  );
@@ -407,6 +407,10 @@ export interface Booking {
407
407
  googleEventId?: string;
408
408
  /** Token for cancel/reschedule link (only returned to the booker) */
409
409
  cancelToken?: string;
410
+ /** Zoom may have created the meeting even though its booking request failed. */
411
+ zoomNeedsReview?: boolean;
412
+ /** Cancellation requires a manual Zoom check because no provider ID was saved. */
413
+ zoomCancellationNeedsReview?: boolean;
410
414
  status: "confirmed" | "cancelled";
411
415
  createdAt: string;
412
416
  }
@@ -80,10 +80,16 @@ function desktopOsIcon(): typeof IconDeviceDesktop {
80
80
  return IconDeviceDesktop;
81
81
  }
82
82
 
83
+ const subscribeToDesktopPlatform = () => () => undefined;
84
+
83
85
  export function DesktopPlatformIcon(
84
86
  props: ComponentProps<typeof IconDeviceDesktop>,
85
87
  ) {
86
- const DesktopIcon = desktopOsIcon();
88
+ const DesktopIcon = useSyncExternalStore(
89
+ subscribeToDesktopPlatform,
90
+ desktopOsIcon,
91
+ () => IconDeviceDesktop,
92
+ );
87
93
  return <DesktopIcon {...props} />;
88
94
  }
89
95
 
@@ -198,11 +204,6 @@ export function CaptureInstallButton({
198
204
  );
199
205
  }
200
206
 
201
- /**
202
- * Compact recorder CTA for the web capture surface. Keep the trigger as plain
203
- * text so the action reads like a source choice; install destinations belong
204
- * in the menu rather than in a platform-specific icon treatment.
205
- */
206
207
  export function CaptureInstallMenu({
207
208
  children,
208
209
  className,
@@ -229,6 +230,7 @@ export function CaptureInstallMenu({
229
230
  attemptOpenDesktopApp(desktopHref);
230
231
  }}
231
232
  >
233
+ <DesktopPlatformIcon aria-hidden="true" className="size-4" />
232
234
  {children}
233
235
  </Button>
234
236
  );
@@ -243,6 +245,7 @@ export function CaptureInstallMenu({
243
245
  variant={variant}
244
246
  {...buttonProps}
245
247
  >
248
+ <DesktopPlatformIcon aria-hidden="true" className="size-4" />
246
249
  {children}
247
250
  <IconChevronDown className="size-3.5" />
248
251
  </Button>
@@ -451,6 +451,10 @@ export function ClipsCommandMenu({
451
451
  "The no-comments sidebar gives viewers a concise reason to try Clips and a clear path to sign up.",
452
452
  )
453
453
  .join(t("settings.changelogCommentSignup"))
454
+ .split(
455
+ "The empty comments state now explains how screen recordings help AI agents.",
456
+ )
457
+ .join(t("settings.changelogCommentsEmptyState"))
454
458
  .split(
455
459
  'Signed-in viewers who hit an unavailable, expired, or private share link now land in their library instead of the public marketing page when they choose "Go home."',
456
460
  )
@@ -359,10 +359,9 @@ const messages = {
359
359
  downloadForWindows: "Download for Windows",
360
360
  downloadForLinux: "Download for Linux",
361
361
  downloadDesktopApp: "Download desktop app",
362
- commentSignupTitle: "Give your AI agent the full picture",
363
- commentSignupContext: "Share transcript and timestamped frames in one link",
364
- commentSignupFeedback: "Turn recorded feedback into clear next steps",
365
- commentSignupDebug: "Capture console errors and failed requests",
362
+ commentSignupTitle: "Screen recordings your AI agent can see and hear",
363
+ commentSignupDescription:
364
+ "Clips is a free and open-source screen recorder for sharing bugs, feedback, and walkthroughs with AI agents.",
366
365
  agentEmptyTitle: "Join the conversation",
367
366
  agentEmptyDescription:
368
367
  "Create a free Clips account to comment, react, and ask about this clip.",
@@ -755,6 +754,8 @@ const messages = {
755
754
  changelogEmpty: "No updates yet.",
756
755
  changelogCommentSignup:
757
756
  "The no-comments sidebar gives viewers a concise reason to try Clips and a clear path to sign up.",
757
+ changelogCommentsEmptyState:
758
+ "The empty comments state now explains how screen recordings help AI agents.",
758
759
  changelogShareLink:
759
760
  'Signed-in viewers who hit an unavailable, expired, or private share link now land in their library instead of the public marketing page when they choose "Go home."',
760
761
  viewAllUpdates: "View all updates",
@@ -459,6 +459,10 @@ export default function SettingsIndexRoute() {
459
459
  "The no-comments sidebar gives viewers a concise reason to try Clips and a clear path to sign up.",
460
460
  )
461
461
  .join(t("settings.changelogCommentSignup"))
462
+ .split(
463
+ "The empty comments state now explains how screen recordings help AI agents.",
464
+ )
465
+ .join(t("settings.changelogCommentsEmptyState"))
462
466
  .split(
463
467
  'Signed-in viewers who hit an unavailable, expired, or private share link now land in their library instead of the public marketing page when they choose "Go home."',
464
468
  )
@@ -1869,12 +1869,10 @@ export default function ShareRoute() {
1869
1869
  sessionStatus !== "signing-out" &&
1870
1870
  comments.length === 0 ? (
1871
1871
  <PublicCommentsEmptyState
1872
- signInHref={signInHref}
1873
1872
  onSignUp={() => {
1874
1873
  fireShareCtaClick("signup");
1875
1874
  openCreateAccount("comment");
1876
1875
  }}
1877
- onSignIn={() => fireShareCtaClick("signin")}
1878
1876
  />
1879
1877
  ) : (
1880
1878
  <CommentsPanel
@@ -2054,45 +2052,21 @@ function formatRecordedOn(
2054
2052
  }).format(date);
2055
2053
  }
2056
2054
 
2057
- function PublicCommentsEmptyState({
2058
- signInHref,
2059
- onSignUp,
2060
- onSignIn,
2061
- }: {
2062
- signInHref: string;
2063
- onSignUp: () => void;
2064
- onSignIn: () => void;
2065
- }) {
2055
+ function PublicCommentsEmptyState({ onSignUp }: { onSignUp: () => void }) {
2066
2056
  const t = useT();
2067
2057
 
2068
2058
  return (
2069
2059
  <div className="mx-auto flex min-h-0 w-full max-w-sm flex-1 flex-col items-center justify-center gap-4 overflow-y-auto px-5 py-5 text-center">
2070
- <div className="flex size-10 items-center justify-center rounded-lg bg-primary/10 text-primary">
2071
- <IconDeviceDesktop aria-hidden="true" className="size-5" />
2072
- </div>
2073
- <h2 className="text-base font-semibold tracking-tight">
2060
+ <AgentNativeIcon aria-hidden="true" className="h-5 w-8 text-primary" />
2061
+ <h2 className="text-xl leading-6 font-semibold tracking-tight">
2074
2062
  {t("sharePage.commentSignupTitle")}
2075
2063
  </h2>
2076
2064
  <p className="max-w-xs text-sm leading-5 text-muted-foreground">
2077
- {t("sharePage.commentSignupContext")} ·{" "}
2078
- {t("sharePage.commentSignupFeedback")} ·{" "}
2079
- {t("sharePage.commentSignupDebug")}
2065
+ {t("sharePage.commentSignupDescription")}
2080
2066
  </p>
2081
- <div className="w-full space-y-2">
2082
- <Button type="button" className="w-full" onClick={onSignUp}>
2083
- {t("signInPrompt.createAccount")}
2084
- </Button>
2085
- <p className="text-center text-xs text-muted-foreground">
2086
- {t("sharePage.agentEmptySignInPrompt")}{" "}
2087
- <a
2088
- href={signInHref}
2089
- onClick={onSignIn}
2090
- className="font-medium text-foreground underline underline-offset-4 hover:no-underline"
2091
- >
2092
- {t("signInPrompt.signIn")}
2093
- </a>
2094
- </p>
2095
- </div>
2067
+ <Button type="button" onClick={onSignUp}>
2068
+ {t("signInPrompt.createAccount")}
2069
+ </Button>
2096
2070
  </div>
2097
2071
  );
2098
2072
  }
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-09-26
4
+ ---
5
+
6
+ See the desktop app for your platform at a glance.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-09-26
4
+ ---
5
+
6
+ The empty comments state now explains how screen recordings help AI agents.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-09-26
4
+ ---
5
+
6
+ The empty comments view has a clearer headline and a focused signup action.
@@ -12641,9 +12641,12 @@ export const editorChromeBridgeScript: string = `"use strict";
12641
12641
  };
12642
12642
  }
12643
12643
  }
12644
+ if (pointerOutsideCurrentParent && (!pointHit || pointHit === document.body || pointHit === document.documentElement) && dropContainerForTarget(target) === currentParent) {
12645
+ target = unnestAbsoluteToScreenRoot(el, clientX, clientY) || target;
12646
+ }
12644
12647
  var container = dropContainerForTarget(target);
12645
12648
  if (ignoreTargetAutoLayout && container && container !== document.body && isAutoLayoutElement(container)) {
12646
- return {
12649
+ target = {
12647
12650
  anchor: container,
12648
12651
  placement: "inside",
12649
12652
  axis: parentFlowAxis(container),
@@ -12651,13 +12654,34 @@ export const editorChromeBridgeScript: string = `"use strict";
12651
12654
  };
12652
12655
  }
12653
12656
  if (currentParent !== document.body && (container === document.body || container === document.documentElement || target?.anchor === document.body)) {
12654
- return {
12657
+ target = {
12655
12658
  anchor: currentParent,
12656
12659
  placement: "after",
12657
12660
  axis: "y",
12658
12661
  dropMode: "absolute-container"
12659
12662
  };
12660
12663
  }
12664
+ var exitedContainer = el.parentElement;
12665
+ var receivingContainer = exitedContainer && exitedContainer.parentElement;
12666
+ var targetContainer = dropContainerForTarget(target);
12667
+ if (!ignoreTargetAutoLayout && target && exitedContainer && receivingContainer && isContainerDropTarget(exitedContainer) && targetContainer === receivingContainer && (pointHit === receivingContainer || !pointHit || pointHit === document.body || pointHit === document.documentElement)) {
12668
+ target = {
12669
+ ...target,
12670
+ anchor: exitedContainer,
12671
+ placement: "after",
12672
+ axis: parentFlowAxis(receivingContainer),
12673
+ persistenceAnchor: exitedContainer,
12674
+ persistencePlacement: "after",
12675
+ gridCell: void 0,
12676
+ gridPlacement: void 0,
12677
+ gridDisplacement: void 0,
12678
+ gridDisplacementPlacements: void 0,
12679
+ gridDisplacementPrevStyles: void 0,
12680
+ guideRect: void 0,
12681
+ guideMode: void 0,
12682
+ guidePlacement: void 0
12683
+ };
12684
+ }
12661
12685
  if (target && target.dropMode === "flow-insert" && container && container !== document.body && isContainerDropTarget(container) && !isAutoLayoutElement(container) && isEmptyDropContainer(container, dragged)) {
12662
12686
  target.needsAutoLayoutConversion = true;
12663
12687
  target.conversionTarget = container;
@@ -1129,8 +1129,8 @@ interface DesignCanvasProps {
1129
1129
  * screen. When set to a non-null tool, DesignCanvas mounts a transparent
1130
1130
  * capture overlay above the iframe so pointer gestures draw a new
1131
1131
  * primitive instead of reaching the iframe's own content/editor-chrome
1132
- * bridge. `null`/`undefined` fully restores prior (pre-creation-tool)
1133
- * behavior — the overlay never mounts.
1132
+ * bridge. `null`/`undefined` disables pointer capture while keeping a
1133
+ * rejected Pen draft mounted for retry.
1134
1134
  */
1135
1135
  activeCreationTool?: CreationTool | null;
1136
1136
  selectedPenPathNodeId?: string | null;
@@ -1143,7 +1143,7 @@ interface DesignCanvasProps {
1143
1143
  * persisted primitive (see `appendCanvasPrimitiveToHtml` /
1144
1144
  * `draftPrimitiveToInsert` on the overview side).
1145
1145
  */
1146
- onCreatePrimitive?: (spec: CreatePrimitiveSpec) => string | void;
1146
+ onCreatePrimitive?: (spec: CreatePrimitiveSpec) => string | false | void;
1147
1147
  onUpdatePenPath?: (
1148
1148
  nodeId: string,
1149
1149
  path: PenPath,
@@ -8120,23 +8120,19 @@ export function DesignCanvas({
8120
8120
  title=""
8121
8121
  />
8122
8122
  ) : null}
8123
- {/* Single-screen click-to-place creation overlay — sits over the
8124
- iframe, NOT inside it, mirroring the SharedDrawOverlay pattern
8125
- below. Only mounts while a creation tool is active so it never
8126
- changes existing behavior otherwise (T14: single-screen mode
8127
- previously had no creation capability at all). */}
8128
- {activeCreationTool && !interactMode ? (
8129
- <SingleScreenCreationOverlay
8130
- tool={activeCreationTool}
8131
- iframeRef={iframeRef}
8132
- selectedPenPathNodeId={selectedPenPathNodeId}
8133
- onCreatePrimitive={onCreatePrimitive}
8134
- onUpdatePenPath={onUpdatePenPath}
8135
- />
8136
- ) : null}
8123
+ {/* Keep the overlay mounted while Move is active so a rejected Pen
8124
+ commit remains available to retry; without a creation tool it is
8125
+ transparent to pointer events. */}
8126
+ <SingleScreenCreationOverlay
8127
+ key={screenId}
8128
+ tool={interactMode ? null : (activeCreationTool ?? null)}
8129
+ iframeRef={iframeRef}
8130
+ selectedPenPathNodeId={selectedPenPathNodeId}
8131
+ onCreatePrimitive={onCreatePrimitive}
8132
+ onUpdatePenPath={onUpdatePenPath}
8133
+ />
8137
8134
  {/* OS file drag-over capture overlay — sits over the iframe, NOT
8138
- inside it, mirroring the SingleScreenCreationOverlay mount pattern
8139
- just above. Only mounts while a native OS file drag is actually in
8135
+ inside it. Only mounts while a native OS file drag is actually in
8140
8136
  progress over this wrapper (see handleWrapperDragEnter/Leave), so it
8141
8137
  never changes existing pointer/click behavior otherwise. Skipped
8142
8138
  while a creation tool is active — the two capture surfaces would
@@ -8692,10 +8688,10 @@ interface SingleScreenPenGestureState {
8692
8688
  const SINGLE_SCREEN_PEN_HIT_RADIUS_PX = 10;
8693
8689
 
8694
8690
  interface SingleScreenCreationOverlayProps {
8695
- tool: CreationTool;
8691
+ tool: CreationTool | null;
8696
8692
  iframeRef: React.RefObject<HTMLIFrameElement | null>;
8697
8693
  selectedPenPathNodeId?: string | null;
8698
- onCreatePrimitive?: (spec: CreatePrimitiveSpec) => string | void;
8694
+ onCreatePrimitive?: (spec: CreatePrimitiveSpec) => string | false | void;
8699
8695
  onUpdatePenPath?: (
8700
8696
  nodeId: string,
8701
8697
  path: PenPath,
@@ -8843,6 +8839,7 @@ function SingleScreenCreationOverlay({
8843
8839
  options?: {
8844
8840
  preserveActiveTool?: boolean;
8845
8841
  continueAfterCommit?: boolean;
8842
+ nextTool?: "move" | "pen";
8846
8843
  },
8847
8844
  ) => {
8848
8845
  const committed = path ? clonePenPath(path) : null;
@@ -8859,7 +8856,6 @@ function SingleScreenCreationOverlay({
8859
8856
  options?.preserveActiveTool === false ? "move" : undefined,
8860
8857
  );
8861
8858
  if (!updated) {
8862
- continuationPenPathRef.current = null;
8863
8859
  updatePenPath(committed);
8864
8860
  setPenGesturePreview(null);
8865
8861
  return;
@@ -8874,13 +8870,30 @@ function SingleScreenCreationOverlay({
8874
8870
  return;
8875
8871
  }
8876
8872
  clearPenPath();
8877
- const nodeId = onCreatePrimitive({
8878
- tool: "pen",
8879
- points: committed.nodes.map((node) => node.point),
8880
- penPath: committed,
8881
- fromClick: false,
8882
- preserveActiveTool: options?.preserveActiveTool,
8883
- });
8873
+ const restoreDraft = () => {
8874
+ updatePenPath(committed);
8875
+ setPenGesturePreview(null);
8876
+ setPenPointer(null);
8877
+ setPenCloseHover(false);
8878
+ };
8879
+ let nodeId: string | false | void;
8880
+ try {
8881
+ nodeId = onCreatePrimitive({
8882
+ tool: "pen",
8883
+ points: committed.nodes.map((node) => node.point),
8884
+ penPath: committed,
8885
+ fromClick: false,
8886
+ preserveActiveTool: options?.preserveActiveTool,
8887
+ nextTool: options?.nextTool,
8888
+ });
8889
+ } catch (error) {
8890
+ restoreDraft();
8891
+ throw error;
8892
+ }
8893
+ if (nodeId === false) {
8894
+ restoreDraft();
8895
+ return;
8896
+ }
8884
8897
  continuationPenPathRef.current =
8885
8898
  typeof nodeId === "string" &&
8886
8899
  !committed.closed &&
@@ -8934,6 +8947,12 @@ function SingleScreenCreationOverlay({
8934
8947
  finishPenPath(penPathRef.current, { preserveActiveTool: true });
8935
8948
  if (!penPathRef.current) continuationPenPathRef.current = null;
8936
8949
  }
8950
+ if (!tool) {
8951
+ dragRef.current = null;
8952
+ setDrag(null);
8953
+ setPenPointer(null);
8954
+ setPenCloseHover(false);
8955
+ }
8937
8956
  }, [finishPenPath, tool]);
8938
8957
 
8939
8958
  const emit = useCallback(
@@ -8995,14 +9014,13 @@ function SingleScreenCreationOverlay({
8995
9014
 
8996
9015
  const handlePointerDown = useCallback(
8997
9016
  (e: React.PointerEvent<HTMLDivElement>) => {
8998
- if (e.button !== 0) return;
9017
+ if (e.button !== 0 || !tool) return;
8999
9018
  if (tool === "pen") {
9000
9019
  e.preventDefault();
9001
9020
  e.stopPropagation();
9002
9021
  seedSelectedPenContinuation();
9003
- const currentPath = penPathRef.current?.closed
9004
- ? null
9005
- : penPathRef.current;
9022
+ const currentPath = penPathRef.current;
9023
+ if (currentPath?.closed) return;
9006
9024
  const pathBefore = currentPath ? clonePenPath(currentPath) : null;
9007
9025
  const rawPoint = toContentPoint(e.clientX, e.clientY);
9008
9026
  if (!pathBefore && continuationPenPathRef.current) {
@@ -9244,13 +9262,23 @@ function SingleScreenCreationOverlay({
9244
9262
  event.preventDefault();
9245
9263
  event.stopPropagation();
9246
9264
  event.stopImmediatePropagation();
9247
- if (event.key === "Escape") {
9265
+ if (event.key === "Escape" && penGestureRef.current) {
9266
+ const pathBefore = penGestureRef.current.pathBefore;
9248
9267
  clearPenPath();
9268
+ updatePenPath(pathBefore);
9249
9269
  return;
9250
9270
  }
9271
+ const continuesExistingPath =
9272
+ event.key === "Enter" && continuationPenPathRef.current !== null;
9251
9273
  finishPenPath(path, {
9252
- preserveActiveTool: false,
9253
- continueAfterCommit: true,
9274
+ preserveActiveTool: event.key === "Escape" || continuesExistingPath,
9275
+ continueAfterCommit: continuesExistingPath,
9276
+ nextTool:
9277
+ event.key === "Enter"
9278
+ ? continuesExistingPath
9279
+ ? "pen"
9280
+ : "move"
9281
+ : undefined,
9254
9282
  });
9255
9283
  };
9256
9284
 
@@ -9258,7 +9286,13 @@ function SingleScreenCreationOverlay({
9258
9286
  return () => window.removeEventListener("keydown", handleKeyDown, true);
9259
9287
  }, [clearPenPath, finishPenPath, tool, updatePenPath]);
9260
9288
 
9261
- const cursorClass = tool === "text" ? "cursor-text" : "cursor-crosshair";
9289
+ const cursorClass =
9290
+ tool === null
9291
+ ? "pointer-events-none"
9292
+ : cn(
9293
+ "pointer-events-auto",
9294
+ tool === "text" ? "cursor-text" : "cursor-crosshair",
9295
+ );
9262
9296
  const isLineTool = tool === "line" || tool === "arrow";
9263
9297
  const displayedPenPath =
9264
9298
  penGesturePreview ??
@@ -9287,7 +9321,7 @@ function SingleScreenCreationOverlay({
9287
9321
  y: point.y - previewScrollOffset.top,
9288
9322
  });
9289
9323
  const previewRect =
9290
- drag && drag.moved && !isLineTool && tool !== "pen"
9324
+ tool && drag && drag.moved && !isLineTool && tool !== "pen"
9291
9325
  ? getDraftGeometryFromPoints(
9292
9326
  toOverlayLocal(drag.startContent),
9293
9327
  toOverlayLocal(drag.currentContent),
@@ -9321,7 +9355,7 @@ function SingleScreenCreationOverlay({
9321
9355
  ref={penOverlayRef}
9322
9356
  data-design-canvas-creation-overlay
9323
9357
  data-creation-tool={tool}
9324
- className={cn("absolute inset-0 z-20 pointer-events-auto", cursorClass)}
9358
+ className={cn("absolute inset-0 z-20", cursorClass)}
9325
9359
  onPointerDown={handlePointerDown}
9326
9360
  onPointerMove={handlePointerMove}
9327
9361
  onPointerUp={handlePointerUp}
@@ -9339,7 +9373,7 @@ function SingleScreenCreationOverlay({
9339
9373
  }
9340
9374
  }}
9341
9375
  >
9342
- {tool === "pen" && displayedPenPathOverlay ? (
9376
+ {displayedPenPathOverlay ? (
9343
9377
  <SingleScreenPenPathOverlay
9344
9378
  path={displayedPenPathOverlay}
9345
9379
  closeHover={penCloseHover}