@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
@@ -882,11 +882,6 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
882
882
  setPenPointer(null);
883
883
  setPenCloseHover(false);
884
884
  }, []);
885
- const cancelActivePenPath = useCallback(() => {
886
- clearActivePenPath();
887
- penContinuesVectorEditRef.current = false;
888
- penContinuationBaseCountRef.current = 0;
889
- }, [clearActivePenPath]);
890
885
  // Last raw client point the pen ghost/close-hover preview was computed
891
886
  // from (P18). A wheel pan/zoom gesture mutates pan/zoom every animation
892
887
  // frame via applyViewToDom without the mouse itself moving, so the
@@ -5636,19 +5631,16 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
5636
5631
  useEffect(() => {
5637
5632
  const handleKeyDown = (event: KeyboardEvent) => {
5638
5633
  if (event.key !== "Escape") return;
5639
- const hasActivePenPath =
5640
- Boolean(activePenPathRef.current) &&
5641
- !isEditableHotkeyTarget(event.target);
5642
5634
  const cancelled = cancelActiveDrag(
5643
5635
  eventEpochMilliseconds(event.timeStamp),
5644
5636
  );
5645
- if (hasActivePenPath) cancelActivePenPath();
5646
- if (cancelled || hasActivePenPath) {
5637
+ if (cancelled) {
5647
5638
  event.preventDefault();
5648
5639
  event.stopPropagation();
5649
5640
  event.stopImmediatePropagation();
5650
5641
  return;
5651
5642
  }
5643
+ if (activePenPathRef.current) return;
5652
5644
  // No in-flight drag to cancel: Escape while in vector edit mode exits
5653
5645
  // the mode entirely (matches Figma), rather than being a no-op.
5654
5646
  if (vectorEdit) {
@@ -5663,7 +5655,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
5663
5655
  return () => {
5664
5656
  window.removeEventListener("keydown", handleKeyDown, true);
5665
5657
  };
5666
- }, [cancelActiveDrag, cancelActivePenPath, vectorEdit]);
5658
+ }, [cancelActiveDrag, vectorEdit]);
5667
5659
 
5668
5660
  const beginPan = useCallback(
5669
5661
  (e: React.MouseEvent) => {
@@ -6359,7 +6351,9 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
6359
6351
  const lastPersisted = persistedEntries[persistedEntries.length - 1];
6360
6352
  // Do not call onPrimitiveCreated for board objects (sentinel frameId).
6361
6353
  if (lastPersisted && lastPersisted.frameId !== "__board__") {
6362
- onPrimitiveCreated?.(lastPersisted.frameId, lastPersisted.nodeId);
6354
+ onPrimitiveCreated?.(lastPersisted.frameId, lastPersisted.nodeId, {
6355
+ preserveActiveTool: true,
6356
+ });
6363
6357
  }
6364
6358
  }, [
6365
6359
  onCreatePrimitive,
@@ -6382,7 +6376,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
6382
6376
  nextTool?: "move" | "pen";
6383
6377
  },
6384
6378
  ) => {
6385
- const nextTool = options?.nextTool ?? "pen";
6379
+ let nextTool: "move" | "pen" = options?.nextTool ?? "pen";
6386
6380
  // Clear before committing: the commit flushes React synchronously, and
6387
6381
  // an effect it wakes can re-enter here and commit the same path twice.
6388
6382
  clearActivePenPath();
@@ -6392,31 +6386,47 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
6392
6386
  }
6393
6387
 
6394
6388
  if (penContinuesVectorEditRef.current) {
6395
- penContinuesVectorEditRef.current = false;
6396
6389
  const active = vectorEditRef.current;
6397
6390
  const baseCount = penContinuationBaseCountRef.current;
6398
- penContinuationBaseCountRef.current = 0;
6399
- if (
6400
- active &&
6401
- (path.nodes.length > baseCount || path.closed !== active.path.closed)
6402
- ) {
6403
- const toLocal = (point: Point): Point => ({
6404
- x: point.x - active.originCanvas.x,
6405
- y: point.y - active.originCanvas.y,
6406
- });
6407
- active.onChange(
6408
- {
6409
- closed: path.closed,
6410
- nodes: path.nodes.map((node) => ({
6411
- ...node,
6412
- point: toLocal(node.point),
6413
- handleIn: node.handleIn ? toLocal(node.handleIn) : undefined,
6414
- handleOut: node.handleOut ? toLocal(node.handleOut) : undefined,
6415
- })),
6416
- },
6417
- "commit",
6391
+ const changed =
6392
+ path.nodes.length > baseCount || path.closed !== active?.path.closed;
6393
+ const accepted =
6394
+ !changed ||
6395
+ Boolean(
6396
+ active &&
6397
+ active.onChange(
6398
+ {
6399
+ closed: path.closed,
6400
+ nodes: path.nodes.map((node) => ({
6401
+ ...node,
6402
+ point: {
6403
+ x: node.point.x - active.originCanvas.x,
6404
+ y: node.point.y - active.originCanvas.y,
6405
+ },
6406
+ handleIn: node.handleIn
6407
+ ? {
6408
+ x: node.handleIn.x - active.originCanvas.x,
6409
+ y: node.handleIn.y - active.originCanvas.y,
6410
+ }
6411
+ : undefined,
6412
+ handleOut: node.handleOut
6413
+ ? {
6414
+ x: node.handleOut.x - active.originCanvas.x,
6415
+ y: node.handleOut.y - active.originCanvas.y,
6416
+ }
6417
+ : undefined,
6418
+ })),
6419
+ },
6420
+ "commit",
6421
+ ),
6418
6422
  );
6423
+ if (!accepted) {
6424
+ activePenPathRef.current = path;
6425
+ setActivePenPath(path);
6426
+ return;
6419
6427
  }
6428
+ penContinuesVectorEditRef.current = false;
6429
+ penContinuationBaseCountRef.current = 0;
6420
6430
  if (nextTool === "move") active?.onExit();
6421
6431
  onActiveToolChange?.(nextTool);
6422
6432
  return;
@@ -6432,7 +6442,6 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
6432
6442
  updateNodeId: continuation.nodeId,
6433
6443
  });
6434
6444
  if (!persisted) {
6435
- continuationPenPathRef.current = null;
6436
6445
  activePenPathRef.current = path;
6437
6446
  setActivePenPath(path);
6438
6447
  return;
@@ -6445,6 +6454,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
6445
6454
  const persisted = commitDraftPrimitive(draft, undefined, {
6446
6455
  nextTool,
6447
6456
  });
6457
+ if (!persisted) nextTool = "pen";
6448
6458
  continuationPenPathRef.current =
6449
6459
  persisted && !path.closed && options?.continueAfterCommit
6450
6460
  ? {
@@ -6455,9 +6465,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
6455
6465
  : null;
6456
6466
  }
6457
6467
  // The parent selection callback receives nextTool, but board primitives
6458
- // intentionally bypass that generic callback and
6459
- // asynchronous selection reconciliation can otherwise paint Move for a
6460
- // frame. Drive the controlled tool explicitly at the commit boundary.
6468
+ // intentionally bypass it, so update the controlled tool here too.
6461
6469
  onActiveToolChange?.(nextTool);
6462
6470
  },
6463
6471
  [
@@ -10513,13 +10521,27 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
10513
10521
  event.preventDefault();
10514
10522
  event.stopPropagation();
10515
10523
  event.stopImmediatePropagation();
10516
- finishPenPath(path, {
10517
- continueAfterCommit: true,
10518
- nextTool: "move",
10524
+ if (dragState.current?.type === "pen-node") cancelActiveDrag();
10525
+ const pathToFinish = activePenPathRef.current;
10526
+ if (!pathToFinish) return;
10527
+ const continuesExistingPath =
10528
+ continuationPenPathRef.current !== null ||
10529
+ penContinuesVectorEditRef.current;
10530
+ finishPenPath(pathToFinish, {
10531
+ continueAfterCommit: continuationPenPathRef.current !== null,
10532
+ nextTool: continuesExistingPath ? "pen" : "move",
10519
10533
  });
10520
10534
  return;
10521
10535
  }
10522
10536
 
10537
+ if (event.key === "Escape") {
10538
+ event.preventDefault();
10539
+ event.stopPropagation();
10540
+ event.stopImmediatePropagation();
10541
+ finishPenPath(path);
10542
+ return;
10543
+ }
10544
+
10523
10545
  if (event.key === "Delete" || event.key === "Backspace") {
10524
10546
  event.preventDefault();
10525
10547
  event.stopPropagation();
@@ -10532,7 +10554,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
10532
10554
  return () => {
10533
10555
  window.removeEventListener("keydown", handleKeyDown, true);
10534
10556
  };
10535
- }, [finishPenPath, undoActivePenPathSegment]);
10557
+ }, [cancelActiveDrag, finishPenPath, undoActivePenPathSegment]);
10536
10558
 
10537
10559
  useEffect(() => {
10538
10560
  const tool = normalizeCanvasTool(activeTool ?? localActiveTool);
@@ -18,6 +18,7 @@ export interface CreatePrimitiveSpec {
18
18
  fromClick: boolean;
19
19
  /** Commit an in-progress pen path without overriding a newly chosen tool. */
20
20
  preserveActiveTool?: boolean;
21
+ nextTool?: "move" | "pen";
21
22
  }
22
23
 
23
24
  /**
@@ -293,7 +293,10 @@ export interface MultiScreenCanvasProps {
293
293
  onPrimitiveCreated?: (
294
294
  screenId: string,
295
295
  nodeId: string,
296
- options?: { nextTool?: "move" | "pen" },
296
+ options?: {
297
+ nextTool?: "move" | "pen";
298
+ preserveActiveTool?: boolean;
299
+ },
297
300
  ) => void;
298
301
  onUpdatePenPath?: (
299
302
  screenId: string,
@@ -855,7 +858,7 @@ export interface VectorEditOverlayState {
855
858
  originCanvas: Point;
856
859
  selectedAnchorIndex: number | null;
857
860
  onSelectedAnchorChange: (nodeIndex: number | null) => void;
858
- onChange: (nextPath: PenPath, phase: "preview" | "commit") => void;
861
+ onChange: (nextPath: PenPath, phase: "preview" | "commit") => boolean;
859
862
  onExit: () => void;
860
863
  }
861
864
 
@@ -58,50 +58,76 @@ export function getDuplicateScreenGeometry(
58
58
  sourceGeometry: FrameGeometry,
59
59
  occupiedGeometries: readonly FrameGeometry[],
60
60
  ): FrameGeometry {
61
- const rowBottom = sourceGeometry.y + sourceGeometry.height;
62
- const rowGeometries = occupiedGeometries.filter(
63
- (geometry) =>
64
- geometry.y < rowBottom && geometry.y + geometry.height > sourceGeometry.y,
65
- );
66
- const x = Math.max(
67
- sourceGeometry.x + sourceGeometry.width + DUPLICATE_SCREEN_GAP,
68
- ...rowGeometries.map(
69
- (geometry) => geometry.x + geometry.width + DUPLICATE_SCREEN_GAP,
61
+ return {
62
+ ...getFirstFreeDuplicateGeometry(
63
+ {
64
+ ...sourceGeometry,
65
+ x: sourceGeometry.x + sourceGeometry.width + DUPLICATE_SCREEN_GAP,
66
+ y: sourceGeometry.y,
67
+ },
68
+ occupiedGeometries,
70
69
  ),
71
- );
72
- const z = Math.max(
73
- sourceGeometry.z ?? 0,
74
- ...occupiedGeometries.map((geometry) => geometry.z ?? 0),
75
- );
76
- return { ...sourceGeometry, x, y: sourceGeometry.y, z: z + 1 };
70
+ z: (sourceGeometry.z ?? 0) + 1,
71
+ };
72
+ }
73
+
74
+ function getFirstFreeDuplicateGeometry(
75
+ candidate: FrameGeometry,
76
+ occupiedGeometries: readonly FrameGeometry[],
77
+ ): FrameGeometry {
78
+ let free = { ...candidate };
79
+ while (true) {
80
+ const overlap = occupiedGeometries
81
+ .filter((geometry) => duplicateGeometriesOverlap(free, geometry))
82
+ .sort((left, right) => left.x - right.x)[0];
83
+ if (!overlap) return free;
84
+ free = {
85
+ ...free,
86
+ x: overlap.x + overlap.width + DUPLICATE_SCREEN_GAP,
87
+ };
88
+ }
77
89
  }
78
90
 
79
91
  function duplicateGeometriesOverlap(
80
92
  left: FrameGeometry,
81
93
  right: FrameGeometry,
94
+ gap = DUPLICATE_SCREEN_GAP,
82
95
  ): boolean {
83
96
  const sameRow =
84
97
  left.y < right.y + right.height && right.y < left.y + left.height;
85
98
  return (
86
99
  sameRow &&
87
- left.x < right.x + right.width + DUPLICATE_SCREEN_GAP &&
88
- right.x < left.x + left.width + DUPLICATE_SCREEN_GAP
100
+ left.x < right.x + right.width + gap &&
101
+ right.x < left.x + left.width + gap
89
102
  );
90
103
  }
91
104
 
92
105
  function reserveDuplicateGeometry(
93
106
  filename: string,
94
107
  candidate: FrameGeometry,
108
+ occupiedGeometries: readonly FrameGeometry[],
95
109
  pendingGeometries: ReadonlyMap<string, FrameGeometry>,
110
+ preserveRequestedPosition: boolean,
96
111
  ): FrameGeometry {
97
- let reserved = { ...candidate };
98
- for (const [pendingFilename, pendingGeometry] of pendingGeometries) {
99
- if (
100
- pendingFilename !== filename &&
101
- duplicateGeometriesOverlap(reserved, pendingGeometry)
102
- ) {
103
- reserved.x =
104
- pendingGeometry.x + pendingGeometry.width + DUPLICATE_SCREEN_GAP;
112
+ const otherPending = [...pendingGeometries.entries()]
113
+ .filter(([pendingFilename]) => pendingFilename !== filename)
114
+ .map(([, geometry]) => geometry);
115
+ const reserved =
116
+ preserveRequestedPosition &&
117
+ !otherPending.some((geometry) =>
118
+ duplicateGeometriesOverlap(candidate, geometry),
119
+ )
120
+ ? { ...candidate }
121
+ : getFirstFreeDuplicateGeometry(candidate, [
122
+ ...occupiedGeometries,
123
+ ...otherPending,
124
+ ]);
125
+ if (reserved.x === candidate.x && reserved.y === candidate.y) {
126
+ const overlappingZ = occupiedGeometries
127
+ .filter((geometry) => duplicateGeometriesOverlap(candidate, geometry, 0))
128
+ .map((geometry) => geometry.z ?? 0);
129
+ if (overlappingZ.length > 0) {
130
+ reserved.z = Math.max(reserved.z ?? 0, ...overlappingZ) + 1;
105
131
  }
106
132
  }
107
133
  const pendingZ = [...pendingGeometries.values()].map(
@@ -185,6 +211,7 @@ export function runDuplicateScreen(
185
211
  }: DuplicateScreenArgs,
186
212
  screenId: string,
187
213
  request?: {
214
+ mode?: "alt-click" | "alt-drag";
188
215
  canvasPosition?: { x: number; y: number };
189
216
  preserveCamera?: boolean;
190
217
  historyBatchId?: string;
@@ -269,12 +296,16 @@ export function runDuplicateScreen(
269
296
  z: (adjacentGeometry.z ?? 0) + (request.duplicateStackIndex ?? 0),
270
297
  }
271
298
  : adjacentGeometry);
299
+ const preserveRequestedPosition =
300
+ request?.canvasPosition !== undefined && request.mode !== "alt-click";
272
301
  const createdGeometry = recoveryState?.geometry
273
302
  ? recoveryState.geometry
274
303
  : reserveDuplicateGeometry(
275
304
  filename,
276
305
  requestedGeometry,
306
+ occupiedGeometries,
277
307
  pendingDuplicateGeometriesRef.current,
308
+ preserveRequestedPosition,
278
309
  );
279
310
  pendingDuplicateGeometriesRef.current.set(filename, createdGeometry);
280
311
  // Carry screen dimensions/height mode for every duplicate so the new frame
@@ -5,10 +5,8 @@ import type { LoaderFunctionArgs, MetaFunction } from "react-router";
5
5
  import enUSMessages from "@/i18n/en-US";
6
6
 
7
7
  import { getDb, schema } from "../../server/db";
8
- import {
9
- designResourceMeta,
10
- publicDesignMetaLoaderData,
11
- } from "./public-design-meta";
8
+ import { publicDesignMetaLoaderData } from "../../server/lib/public-design-meta.server";
9
+ import { designResourceMeta } from "./public-design-meta";
12
10
 
13
11
  export { default } from "../pages/DesignSystems";
14
12
 
@@ -2,8 +2,9 @@ import type { LoaderFunctionArgs, MetaFunction } from "react-router";
2
2
 
3
3
  import enUSMessages from "@/i18n/en-US";
4
4
 
5
+ import { loadPublicDesignMeta } from "../../server/lib/public-design-meta.server";
5
6
  import DesignEditorRoute from "../pages/DesignEditor";
6
- import { designResourceMeta, loadPublicDesignMeta } from "./public-design-meta";
7
+ import { designResourceMeta } from "./public-design-meta";
7
8
 
8
9
  /**
9
10
  * Keep the route module itself as a React Fast Refresh boundary. A bare
@@ -1,14 +1,8 @@
1
- import { getConfiguredAppBasePath } from "@agent-native/core/server";
2
1
  import {
3
- SSR_QUERY_CACHE_KEY_HEADER,
4
2
  buildResourceSocialMeta,
5
3
  normalizeDocumentTitle,
6
4
  type SocialMetaDescriptor,
7
5
  } from "@agent-native/core/shared";
8
- import { and, eq } from "drizzle-orm";
9
- import { data } from "react-router";
10
-
11
- import { getDb, schema } from "../../server/db";
12
6
 
13
7
  export interface PublicDesignMetaData {
14
8
  resource: { title: string; description: string | null } | null;
@@ -16,39 +10,6 @@ export interface PublicDesignMetaData {
16
10
  basePath: string;
17
11
  }
18
12
 
19
- export function publicDesignMetaLoaderData(payload: PublicDesignMetaData) {
20
- return data(payload, {
21
- headers: { [SSR_QUERY_CACHE_KEY_HEADER]: "query" },
22
- });
23
- }
24
-
25
- export async function loadPublicDesignMeta(
26
- id: string | undefined,
27
- requestUrl: string,
28
- ): Promise<PublicDesignMetaData> {
29
- const [resource] = id
30
- ? await getDb()
31
- .select({
32
- title: schema.designs.title,
33
- description: schema.designs.description,
34
- })
35
- .from(schema.designs)
36
- .where(
37
- and(
38
- eq(schema.designs.id, id),
39
- eq(schema.designs.visibility, "public"),
40
- ),
41
- )
42
- .limit(1)
43
- : [];
44
-
45
- return {
46
- resource: resource ?? null,
47
- origin: new URL(requestUrl).origin,
48
- basePath: getConfiguredAppBasePath(),
49
- };
50
- }
51
-
52
13
  export function designResourceMeta(
53
14
  loaderData: PublicDesignMetaData | undefined,
54
15
  fallbackTitle: string,
@@ -5,10 +5,8 @@ import type { LoaderFunctionArgs, MetaFunction } from "react-router";
5
5
  import enUSMessages from "@/i18n/en-US";
6
6
 
7
7
  import { getDb, schema } from "../../server/db";
8
- import {
9
- designResourceMeta,
10
- publicDesignMetaLoaderData,
11
- } from "./public-design-meta";
8
+ import { publicDesignMetaLoaderData } from "../../server/lib/public-design-meta.server";
9
+ import { designResourceMeta } from "./public-design-meta";
12
10
 
13
11
  export { default } from "../pages/Templates";
14
12
 
@@ -2,8 +2,9 @@ import type { LoaderFunctionArgs, MetaFunction } from "react-router";
2
2
 
3
3
  import enUSMessages from "@/i18n/en-US";
4
4
 
5
+ import { loadPublicDesignMeta } from "../../server/lib/public-design-meta.server";
5
6
  import DesignEditorRoute from "../pages/DesignEditor";
6
- import { designResourceMeta, loadPublicDesignMeta } from "./public-design-meta";
7
+ import { designResourceMeta } from "./public-design-meta";
7
8
 
8
9
  /**
9
10
  * The skill's local editor has its own route so a capability can authorize this
@@ -0,0 +1,7 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-09-25
4
+ ---
5
+
6
+ Escape finishes a Pen path as an open vector; during a pointer gesture, it
7
+ cancels that gesture and keeps previously placed anchors.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-09-25
4
+ ---
5
+
6
+ Pen paths remain available after save failures, and retries keep the active tool
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-09-25
4
+ ---
5
+
6
+ Pen retries keep extending the selected vector after a save failure
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-09-25
4
+ ---
5
+
6
+ Pressing Enter finishes a new Pen path and selects its vector on Move.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-09-26
4
+ ---
5
+
6
+ Cmd+D places screen duplicates in the next open slot
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-09-26
4
+ ---
5
+
6
+ Dropping a layer beside a sibling keeps that insertion point.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-09-26
4
+ ---
5
+
6
+ Layers dragged out of frames now land at the board level, and copies keep their intended order and selection.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-09-26
4
+ ---
5
+
6
+ Opening shared designs no longer fails during client startup
@@ -5,7 +5,7 @@
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "agent-native dev --inspect",
8
- "build": "agent-native build",
8
+ "build": "agent-native build && tsx scripts/check-client-server-boundary.ts",
9
9
  "start": "agent-native start",
10
10
  "test": "vitest --run",
11
11
  "typecheck": "agent-native typecheck && pnpm exec tsc --noEmit -p app/components/design/bridge/tsconfig.json",
@@ -0,0 +1,55 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const clientRoot = path.resolve(
6
+ path.dirname(fileURLToPath(import.meta.url)),
7
+ "../build/client",
8
+ );
9
+ const textAsset = /\.(?:css|html|js|json|map|mjs)$/i;
10
+ const forbidden = [
11
+ ["PGlite database client", "__agentNativePgliteClients"],
12
+ ["server database module", /server[\\/]db(?:[\\/.'"?]|$)/i],
13
+ ] as const;
14
+
15
+ async function textAssets(directory: string): Promise<string[]> {
16
+ const entries = await readdir(directory, { withFileTypes: true });
17
+ const paths = await Promise.all(
18
+ entries.map((entry) => {
19
+ const filePath = path.join(directory, entry.name);
20
+ return entry.isDirectory()
21
+ ? textAssets(filePath)
22
+ : textAsset.test(entry.name)
23
+ ? [filePath]
24
+ : [];
25
+ }),
26
+ );
27
+ return paths.flat();
28
+ }
29
+
30
+ const assets = await textAssets(clientRoot);
31
+ if (assets.length === 0) {
32
+ throw new Error(`No text assets found in client build: ${clientRoot}`);
33
+ }
34
+
35
+ const leaks: string[] = [];
36
+ for (const asset of assets) {
37
+ const source = await readFile(asset, "utf8");
38
+ for (const [label, pattern] of forbidden) {
39
+ if (
40
+ typeof pattern === "string"
41
+ ? source.includes(pattern)
42
+ : pattern.test(source)
43
+ ) {
44
+ leaks.push(`${path.relative(clientRoot, asset)}: ${label}`);
45
+ }
46
+ }
47
+ }
48
+
49
+ if (leaks.length > 0) {
50
+ throw new Error(
51
+ `Server database code found in client assets:\n${leaks.join("\n")}`,
52
+ );
53
+ }
54
+
55
+ console.log(`Checked ${assets.length} client assets for server database code.`);
@@ -0,0 +1,40 @@
1
+ import { getConfiguredAppBasePath } from "@agent-native/core/server";
2
+ import { SSR_QUERY_CACHE_KEY_HEADER } from "@agent-native/core/shared";
3
+ import { and, eq } from "drizzle-orm";
4
+ import { data } from "react-router";
5
+
6
+ import type { PublicDesignMetaData } from "../../app/routes/public-design-meta";
7
+ import { getDb, schema } from "../db";
8
+
9
+ export function publicDesignMetaLoaderData(payload: PublicDesignMetaData) {
10
+ return data(payload, {
11
+ headers: { [SSR_QUERY_CACHE_KEY_HEADER]: "query" },
12
+ });
13
+ }
14
+
15
+ export async function loadPublicDesignMeta(
16
+ id: string | undefined,
17
+ requestUrl: string,
18
+ ): Promise<PublicDesignMetaData> {
19
+ const [resource] = id
20
+ ? await getDb()
21
+ .select({
22
+ title: schema.designs.title,
23
+ description: schema.designs.description,
24
+ })
25
+ .from(schema.designs)
26
+ .where(
27
+ and(
28
+ eq(schema.designs.id, id),
29
+ eq(schema.designs.visibility, "public"),
30
+ ),
31
+ )
32
+ .limit(1)
33
+ : [];
34
+
35
+ return {
36
+ resource: resource ?? null,
37
+ origin: new URL(requestUrl).origin,
38
+ basePath: getConfiguredAppBasePath(),
39
+ };
40
+ }