@agent-native/core 0.177.0-nightly-20260906034133 → 0.177.0

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 (44) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/CHANGELOG.md +13 -0
  3. package/corpus/templates/calendar/CHANGELOG.md +20 -0
  4. package/corpus/templates/clips/CHANGELOG.md +7 -0
  5. package/corpus/templates/clips/server/plugins/db.ts +10 -2
  6. package/corpus/templates/content/CHANGELOG.md +6 -0
  7. package/corpus/templates/design/CHANGELOG.md +16 -0
  8. package/corpus/templates/design/app/components/design/EditPanel.tsx +8 -1
  9. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +12 -20
  10. package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +23 -22
  11. package/corpus/templates/design/app/pages/DesignEditor.tsx +43 -6
  12. package/corpus/templates/design/app/pages/Index.tsx +10 -3
  13. package/corpus/templates/design/app/pages/Templates.tsx +1 -18
  14. package/corpus/templates/design/app/pages/design-editor/canvas-primitive-insert.ts +25 -11
  15. package/corpus/templates/design/app/pages/design-editor/canvas-primitives.ts +8 -18
  16. package/corpus/templates/design/app/pages/design-editor/commands/create-primitive.ts +4 -0
  17. package/corpus/templates/design/changelog/2026-09-04-board-text-is-readable-on-a-light-canvas.md +6 -0
  18. package/corpus/templates/design/changelog/2026-09-04-designs-grid-fills-its-last-row.md +6 -0
  19. package/corpus/templates/design/changelog/2026-09-04-no-design-system-now-sticks-when-you-choose-it.md +6 -0
  20. package/corpus/templates/design/changelog/2026-09-04-the-canvas-keeps-its-colour-when-you-draw-on-it.md +6 -0
  21. package/corpus/templates/design/changelog/2026-09-04-the-design-system-picker-stays-after-a-template-fil.md +6 -0
  22. package/corpus/templates/dispatch/CHANGELOG.md +6 -0
  23. package/corpus/templates/factory/CHANGELOG.md +18 -0
  24. package/corpus/templates/mail/CHANGELOG.md +11 -0
  25. package/dist/automation/index.d.ts +1 -1
  26. package/dist/chat-threads/schema.d.ts +4 -4
  27. package/dist/chat-threads/schema.js +5 -5
  28. package/dist/collab/routes.d.ts +1 -1
  29. package/dist/collab/struct-routes.d.ts +1 -1
  30. package/dist/email-catalog/log.js +2 -0
  31. package/dist/email-catalog/schema.d.ts +1 -1
  32. package/dist/email-catalog/schema.js +2 -2
  33. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  34. package/dist/observability/routes.d.ts +4 -4
  35. package/dist/org/migrations.js +36 -8
  36. package/dist/org/schema.d.ts +8 -8
  37. package/dist/org/schema.js +13 -9
  38. package/dist/progress/routes.d.ts +1 -1
  39. package/dist/secrets/routes.d.ts +6 -6
  40. package/dist/secrets/schema.d.ts +2 -2
  41. package/dist/secrets/schema.js +3 -3
  42. package/dist/server/identity-sso-migrations.js +24 -4
  43. package/package.json +3 -3
  44. package/corpus/templates/clips/data/clips-transactional-emails/config.json +0 -3
package/corpus/README.md CHANGED
@@ -31,4 +31,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
31
31
 
32
32
  ## Generated Counts
33
33
 
34
- - template files: 9019
34
+ - template files: 9023
@@ -3,6 +3,19 @@
3
3
  All notable user-facing changes to Agent-Native Analytics are documented here. Open it any
4
4
  time from the command menu (Cmd+K → "What's new") or from Settings.
5
5
 
6
+ ## 2026-09-04
7
+
8
+ ### Improved
9
+
10
+ - The agent now answers data questions with explicit confidence instead of a dead end, and uses existing dashboards first
11
+
12
+ ## 2026-09-03
13
+
14
+ ### Fixed
15
+
16
+ - Analytics autosaves now skip unchanged edits, keeping history focused on real changes.
17
+ - Saved Analytics panels recover when chart fields are nested in their config
18
+
6
19
  ## 2026-09-01
7
20
 
8
21
  ### Added
@@ -3,10 +3,30 @@
3
3
  All notable user-facing changes to Agent-Native Calendar are documented here. Open it any
4
4
  time from the command menu (Cmd+K → "What's new") or from Settings.
5
5
 
6
+ ## 2026-09-05
7
+
8
+ ### Improved
9
+
10
+ - Calendar booking pages show the docs hero wave background
11
+
12
+ ## 2026-09-04
13
+
14
+ ### Improved
15
+
16
+ - Generated booking-link OG images now use the shared branded background.
17
+
18
+ ### Fixed
19
+
20
+ - Calendar controls now add the right source, shared events use their calendar colors, and color changes appear immediately.
21
+ - Calendar sidebar remains toggleable while agent chat is open.
22
+ - Read-only calendar events keep their date and time controls disabled
23
+
6
24
  ## 2026-09-03
7
25
 
8
26
  ### Improved
9
27
 
28
+ - Calendar makes all-day event creation discoverable from the visible all-day row
29
+ - Shared Google calendars now appear automatically with instant visibility controls and local display colors.
10
30
  - Calendar now supports reliable batch event updates and booking cancellations.
11
31
  - The agent now asks for your approval before it deletes events in bulk, emails your guests about a cancellation or change, or moves an event to another calendar.
12
32
 
@@ -3,6 +3,13 @@
3
3
  All notable user-facing changes to Clips are documented here. Open it any time
4
4
  from the command menu (Cmd+K → "What's new") or from Settings.
5
5
 
6
+ ## 2026-09-04
7
+
8
+ ### Fixed
9
+
10
+ - Clips desktop shows its real version again in About and Get Info, and nightly builds resume.
11
+ - Meeting notes now stop within about 15 seconds of a Zoom, Teams, or Meet call ending, even while music or a video keeps playing afterward.
12
+
6
13
  ## 2026-09-01
7
14
 
8
15
  ### Improved
@@ -1090,7 +1090,7 @@ export const migrations = runMigrations(
1090
1090
  sql: `CREATE TABLE IF NOT EXISTS clips_backfill_leases (
1091
1091
  lease_key TEXT PRIMARY KEY,
1092
1092
  holder TEXT NOT NULL,
1093
- expires_at INTEGER NOT NULL
1093
+ expires_at BIGINT NOT NULL
1094
1094
  )`,
1095
1095
  },
1096
1096
  {
@@ -1135,7 +1135,7 @@ export const migrations = runMigrations(
1135
1135
  sql: `CREATE TABLE IF NOT EXISTS clips_backfill_leases (
1136
1136
  lease_key TEXT PRIMARY KEY,
1137
1137
  holder TEXT NOT NULL,
1138
- expires_at INTEGER NOT NULL
1138
+ expires_at BIGINT NOT NULL
1139
1139
  )`,
1140
1140
  },
1141
1141
  {
@@ -1210,6 +1210,14 @@ export const migrations = runMigrations(
1210
1210
  // pass a reason — absent stays distinguishable from every named cause.
1211
1211
  sql: `ALTER TABLE clips_meetings ADD COLUMN IF NOT EXISTS end_reason TEXT`,
1212
1212
  },
1213
+ {
1214
+ version: 70,
1215
+ name: "clips-backfill-leases-expires-at-bigint",
1216
+ sql: `
1217
+ -- guard:allow-destructive-ddl — widen legacy int4 timestamp storage to preserve Date.now() values
1218
+ ALTER TABLE clips_backfill_leases ALTER COLUMN expires_at TYPE BIGINT
1219
+ `,
1220
+ },
1213
1221
  ],
1214
1222
  { table: "clips_migrations" },
1215
1223
  );
@@ -3,6 +3,12 @@
3
3
  All notable user-facing changes to Agent-Native Content are documented here. Open it any
4
4
  time from the command menu (Cmd+K → "What's new").
5
5
 
6
+ ## 2026-09-03
7
+
8
+ ### Fixed
9
+
10
+ - Database records can be added even when older rows use legacy positions.
11
+
6
12
  ## 2026-08-28
7
13
 
8
14
  ### Fixed
@@ -3,10 +3,26 @@
3
3
  All notable user-facing changes to Design are documented here. Open it any time
4
4
  from the command menu (Cmd+K → "What's new") or from Settings.
5
5
 
6
+ ## 2026-09-04
7
+
8
+ ### Improved
9
+
10
+ - The designs list now fills every row instead of leaving a single card stranded on the last one, and the card actions menu is readable in light mode.
11
+ - The design system picker stays in the editor chat after a template fills the design, so later prompts can still target a specific system.
12
+
13
+ ### Fixed
14
+
15
+ - Text drawn straight onto the canvas now takes a colour you can read against it, instead of always being white.
16
+ - Design editor links now keep agent tools available.
17
+ - Choosing "No design system" now clears the design's system instead of snapping back to your default.
18
+ - Drawing the first shape on the board no longer flips the canvas to a different colour, and the Canvas swatch shows the colour it is painted with instead of "NONE".
19
+
6
20
  ## 2026-09-02
7
21
 
8
22
  ### Fixed
9
23
 
24
+ - Fill and Stroke in the inspector now paint a pen path, line, polygon, or star itself rather than tinting its selection bounding box.
25
+ - A closed pen path now takes the same fill and no stroke a drawn rectangle takes, instead of a black outline you had to hide.
10
26
  - Edit with AI now opens a focused editing prompt for selected elements in multi-screen view and reliably returns generated options without misclassifying an active request as superseded.
11
27
  - A greeting or question as the first message in a new design no longer ends with a false "couldn't confirm that a Design artifact was saved" reply.
12
28
 
@@ -263,6 +263,9 @@ interface EditPanelProps {
263
263
  */
264
264
  /** Design-level canvas background — the surround, not a screen's document. */
265
265
  canvasBackground?: string | null;
266
+ /** What the canvas is actually painted with when nothing is stored, so the
267
+ * swatch reads as a colour rather than as an absent value. */
268
+ canvasBackgroundFallback?: string | null;
266
269
  onCanvasBackgroundChange?: (value: string, meta?: StyleChangeMeta) => void;
267
270
  onScreenGeometryChange?: (
268
271
  screenId: string,
@@ -1429,6 +1432,7 @@ function PageProperties({
1429
1432
  onStyleChange,
1430
1433
  onStylesChange,
1431
1434
  canvasBackground,
1435
+ canvasBackgroundFallback,
1432
1436
  onCanvasBackgroundChange,
1433
1437
  }: {
1434
1438
  scope: "canvas" | "document";
@@ -1436,6 +1440,7 @@ function PageProperties({
1436
1440
  onStyleChange: StyleChangeHandler;
1437
1441
  onStylesChange?: StylesChangeHandler;
1438
1442
  canvasBackground?: string | null;
1443
+ canvasBackgroundFallback?: string | null;
1439
1444
  onCanvasBackgroundChange?: (value: string, meta?: StyleChangeMeta) => void;
1440
1445
  }) {
1441
1446
  const t = useT();
@@ -1462,7 +1467,7 @@ function PageProperties({
1462
1467
  <PanelSection title={t("editPanel.sections.canvas")}>
1463
1468
  <ColorInput
1464
1469
  label={t("editPanel.labels.background")}
1465
- value={canvasBackground ?? ""}
1470
+ value={canvasBackground ?? canvasBackgroundFallback ?? ""}
1466
1471
  // meta carries phase: "preview" while dragging vs "commit" on
1467
1472
  // release. Dropping it persists every tick and the picker jumps.
1468
1473
  onChange={(value, meta) => onCanvasBackgroundChange(value, meta)}
@@ -1775,6 +1780,7 @@ export const EditPanel = memo(function EditPanel({
1775
1780
  selectedScreenLayoutGrid,
1776
1781
  onLayoutGridChange,
1777
1782
  canvasBackground,
1783
+ canvasBackgroundFallback,
1778
1784
  onCanvasBackgroundChange,
1779
1785
  onScreenGeometryChange,
1780
1786
  pageStyles = {},
@@ -2356,6 +2362,7 @@ export const EditPanel = memo(function EditPanel({
2356
2362
  onStyleChange={onStyleChange}
2357
2363
  onStylesChange={onStylesChange}
2358
2364
  canvasBackground={canvasBackground}
2365
+ canvasBackgroundFallback={canvasBackgroundFallback}
2359
2366
  onCanvasBackgroundChange={onCanvasBackgroundChange}
2360
2367
  />
2361
2368
  ) : null}
@@ -147,6 +147,10 @@ import { type ElementInfo, type PortableStyleSnapshot } from "./types";
147
147
  * resizable frame inside an infinite, pannable surface.
148
148
  */
149
149
  const SCREEN_WIDTH = OVERVIEW_FRAME_WIDTH;
150
+ /** The board layers sit inside the canvas container, which redefines this var
151
+ * when the design stores a colour — so reading it live is what keeps every
152
+ * board layer the same colour as the canvas around it. */
153
+ const CANVAS_BACKGROUND_VAR = "var(--design-editor-canvas-bg)";
150
154
  const SCREEN_HEIGHT = 640;
151
155
  const SCREEN_CARD_HEIGHT = SCREEN_HEIGHT + 26;
152
156
  const SCREEN_GAP = 56;
@@ -226,7 +230,6 @@ function hasScreenChildLayers(content: string): boolean {
226
230
 
227
231
  import {
228
232
  getBoardContentKey,
229
- resolveBoardSurfaceBackground,
230
233
  getBoardContentLayerSignature,
231
234
  getBoardSurfaceContentBounds,
232
235
  getBoardSurfaceRenderContent,
@@ -685,19 +688,6 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
685
688
  height: surfaceSize.height / scale,
686
689
  };
687
690
  }, [canvasZoom, pan.x, pan.y, surfaceSize.height, surfaceSize.width]);
688
- // The board iframe cannot read the host's CSS vars. Prefer the live
689
- // design canvas colour; the theme var is only the unset fallback.
690
- const boardSurfaceBackground = useMemo(() => {
691
- const themed =
692
- typeof window === "undefined"
693
- ? ""
694
- : window
695
- .getComputedStyle(document.documentElement)
696
- .getPropertyValue("--design-editor-canvas-bg")
697
- .trim();
698
- return resolveBoardSurfaceBackground(canvasBackground, themed);
699
- }, [canvasBackground, resolvedTheme]);
700
-
701
691
  const boardSurfaceRenderGeometry = useMemo(() => {
702
692
  if (!boardFrameGeometry) return undefined;
703
693
  const focusGeometry = boardSurfaceFocusPoint
@@ -746,16 +736,16 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
746
736
  return null;
747
737
  }
748
738
  return getBoardSurfaceStaticPreviewContent({
739
+ darkScheme: resolvedTheme === "dark",
749
740
  html: boardSurfaceHtml,
750
741
  logicalGeometry: boardFrameGeometry,
751
742
  viewport: boardStaticPreviewViewport,
752
- background: boardSurfaceBackground,
753
743
  });
754
744
  }, [
755
745
  boardSurfaceHtml,
756
746
  boardFrameGeometry,
757
747
  boardStaticPreviewViewport,
758
- boardSurfaceBackground,
748
+ resolvedTheme,
759
749
  ]);
760
750
  const showBoardStaticPreview = Boolean(
761
751
  boardFrameGeometry &&
@@ -8547,7 +8537,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
8547
8537
  pointerEvents: "none",
8548
8538
  transform: `scale(${boardFrameGeometry.width / boardStaticPreviewViewport.width}, ${boardFrameGeometry.height / boardStaticPreviewViewport.height})`,
8549
8539
  transformOrigin: "top left",
8550
- background: boardSurfaceBackground,
8540
+ background: CANVAS_BACKGROUND_VAR,
8551
8541
  }}
8552
8542
  />
8553
8543
  </div>
@@ -8572,8 +8562,10 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
8572
8562
  });
8573
8563
  const boardLayerSignature =
8574
8564
  getBoardContentLayerSignature(boardFileContent);
8575
- const boardRenderContent =
8576
- getBoardSurfaceRenderContent(boardFileContent);
8565
+ const boardRenderContent = getBoardSurfaceRenderContent(
8566
+ boardFileContent,
8567
+ resolvedTheme === "dark",
8568
+ );
8577
8569
  return (
8578
8570
  // Overflow-hidden wrapper so the board iframe never bleeds outside
8579
8571
  // its declared logical surface. z-index 0 keeps it below screen
@@ -8593,7 +8585,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
8593
8585
  // board document. Inside the document it would be part of
8594
8586
  // the srcdoc, so every colour-picker tick would rebuild the
8595
8587
  // iframe; out here it is a CSS change and updates instantly.
8596
- background: boardSurfaceBackground,
8588
+ background: CANVAS_BACKGROUND_VAR,
8597
8589
  }}
8598
8590
  >
8599
8591
  <DesignCanvas
@@ -9,19 +9,15 @@ export function hasBoardSurfaceContent(html: string | undefined) {
9
9
  return content.replace(/<!--[\s\S]*?-->/g, "").trim().length > 0;
10
10
  }
11
11
 
12
- const BOARD_SURFACE_RENDER_STYLE = `<style data-agent-native-board-surface-render>html,body{background:transparent!important;background-color:transparent!important;background-image:none!important;}body{margin:0!important;position:relative;overflow:visible;}body>:not([data-agent-native-node-id]):not(style):not(script),body>[data-agent-native-node-id]:not([data-an-primitive]):not([data-agent-native-preserve-styles="true"]):has([data-agent-native-node-id]),body>[data-agent-native-node-id="body"],body>[data-agent-native-node-id="Body"],body>[data-agent-native-layer-name="body"],body>[data-agent-native-layer-name="Body"],body>[data-agent-native-layer-name="<body>"]{background:transparent!important;background-color:transparent!important;background-image:none!important;box-shadow:none!important;}[data-agent-native-board-backdrop-candidate="true"]{display:none!important;pointer-events:none!important;}</style>`;
13
- // The comma syntax also works in the lightweight DOM used by canvas tests.
14
- export const BOARD_SURFACE_BACKGROUND = "hsl(0, 0%, 10%)";
15
-
16
- export function resolveBoardSurfaceBackground(
17
- canvasBackground?: string | null,
18
- themedFallback?: string | null,
19
- ): string {
20
- return (
21
- canvasBackground?.trim() ||
22
- themedFallback?.trim() ||
23
- BOARD_SURFACE_BACKGROUND
24
- );
12
+ /**
13
+ * `color-scheme` is load-bearing, not cosmetic. Chrome paints an opaque base
14
+ * behind a frame whose scheme disagrees with its embedder, and that base sits
15
+ * between this transparent document and the canvas colour on the host layer —
16
+ * white in a dark editor. Matching the editor is what keeps the frame see-through.
17
+ */
18
+ function boardSurfaceRenderStyle(darkScheme: boolean) {
19
+ const scheme = darkScheme ? "html{color-scheme:dark!important;}" : "";
20
+ return `<style data-agent-native-board-surface-render>${scheme}html,body{background:transparent!important;background-color:transparent!important;background-image:none!important;}body{margin:0!important;position:relative;overflow:visible;}body>:not([data-agent-native-node-id]):not(style):not(script),body>[data-agent-native-node-id]:not([data-an-primitive]):not([data-agent-native-preserve-styles="true"]):has([data-agent-native-node-id]),body>[data-agent-native-node-id="body"],body>[data-agent-native-node-id="Body"],body>[data-agent-native-layer-name="body"],body>[data-agent-native-layer-name="Body"],body>[data-agent-native-layer-name="<body>"]{background:transparent!important;background-color:transparent!important;background-image:none!important;box-shadow:none!important;}[data-agent-native-board-backdrop-candidate="true"]{display:none!important;pointer-events:none!important;}</style>`;
25
21
  }
26
22
 
27
23
  const BOARD_SURFACE_BACKDROP_MIN_EDGE_PX = 2400;
@@ -267,12 +263,19 @@ function getCurrentLayerParentNodeId(
267
263
  return "body";
268
264
  }
269
265
 
270
- export function getBoardSurfaceRenderContent(html: string) {
266
+ export function getBoardSurfaceRenderContent(html: string, darkScheme = false) {
271
267
  if (!html) return html;
272
- const renderHtml = markAccidentalBoardBackdropCandidates(html);
273
- if (renderHtml.includes("data-agent-native-board-surface-render")) {
274
- return renderHtml;
268
+ const markedHtml = markAccidentalBoardBackdropCandidates(html);
269
+ const BOARD_SURFACE_RENDER_STYLE = boardSurfaceRenderStyle(darkScheme);
270
+ if (markedHtml.includes(BOARD_SURFACE_RENDER_STYLE)) {
271
+ return markedHtml;
275
272
  }
273
+ // A block from the other scheme is stale, not already-rendered: leaving it
274
+ // would pin the frame to the theme it was first rendered under.
275
+ const renderHtml = markedHtml.replace(
276
+ /<style data-agent-native-board-surface-render>[\s\S]*?<\/style>/i, // i18n-ignore render-style matcher, not visible UI copy
277
+ "",
278
+ );
276
279
  if (/<\/head>/i.test(html)) {
277
280
  return renderHtml.replace(
278
281
  /<\/head>/i,
@@ -321,24 +324,22 @@ function stripExecutableStaticPreviewContent(html: string) {
321
324
  * logical board paintable without allocating or scrolling a 131k iframe.
322
325
  */
323
326
  export function getBoardSurfaceStaticPreviewContent(args: {
324
- /** Themed canvas colour; falls back to the dark default when unresolved. */
325
- background?: string;
327
+ darkScheme?: boolean;
326
328
  html: string;
327
329
  logicalGeometry: FrameGeometry;
328
330
  viewport: { width: number; height: number };
329
331
  }) {
330
332
  const renderHtml = stripExecutableStaticPreviewContent(
331
- getBoardSurfaceRenderContent(args.html),
333
+ getBoardSurfaceRenderContent(args.html, args.darkScheme),
332
334
  );
333
335
  const width = Math.max(1, args.logicalGeometry.width);
334
336
  const height = Math.max(1, args.logicalGeometry.height);
335
337
  const viewportWidth = Math.max(1, args.viewport.width);
336
338
  const viewportHeight = Math.max(1, args.viewport.height);
337
- const background = args.background?.trim() || BOARD_SURFACE_BACKGROUND;
338
339
  const scale = Math.min(viewportWidth / width, viewportHeight / height);
339
340
  const offsetX = -args.logicalGeometry.x;
340
341
  const offsetY = -args.logicalGeometry.y;
341
- const style = `<style data-agent-native-board-static-preview>*,*::before,*::after{animation:none!important;animation-delay:0s!important;transition:none!important;caret-color:transparent!important;}html{width:${viewportWidth}px!important;height:${viewportHeight}px!important;overflow:hidden!important;}html,body{background:${background}!important;background-color:${background}!important;background-image:none!important;}body{margin:0!important;width:${width}px!important;height:${height}px!important;overflow:visible!important;transform:scale(${scale})!important;transform-origin:0 0!important;}body>[data-agent-native-node-id]{translate:${offsetX}px ${offsetY}px!important;}</style>`;
342
+ const style = `<style data-agent-native-board-static-preview>*,*::before,*::after{animation:none!important;animation-delay:0s!important;transition:none!important;caret-color:transparent!important;}html{width:${viewportWidth}px!important;height:${viewportHeight}px!important;overflow:hidden!important;}html,body{background:transparent!important;background-color:transparent!important;background-image:none!important;}body{margin:0!important;width:${width}px!important;height:${height}px!important;overflow:visible!important;transform:scale(${scale})!important;transform-origin:0 0!important;}body>[data-agent-native-node-id]{translate:${offsetX}px ${offsetY}px!important;}</style>`;
342
343
  if (/<\/head\s*>/i.test(renderHtml)) {
343
344
  return injectDocumentMarkup(renderHtml, style, { target: "head" });
344
345
  }
@@ -165,6 +165,7 @@ import {
165
165
  IconMessageCircle,
166
166
  } from "@tabler/icons-react";
167
167
  import { useQueryClient } from "@tanstack/react-query";
168
+ import { useTheme } from "next-themes";
168
169
  import {
169
170
  useState,
170
171
  useEffect,
@@ -5629,6 +5630,10 @@ function DesignEditor() {
5629
5630
  // layer (see beginRename in LayersPanel.tsx).
5630
5631
  const layersPanelRef = useRef<LayersPanelHandle | null>(null);
5631
5632
  const canvasContainerRef = useRef<HTMLDivElement>(null);
5633
+ /** Read by primitive creation, which runs far above where the colour is
5634
+ * derived. */
5635
+ const canvasBackgroundRef = useRef<string | null>(null);
5636
+ const { resolvedTheme } = useTheme();
5632
5637
  const activeEditorDragRef = useRef(false);
5633
5638
 
5634
5639
  // Live handle to the active DesignCanvas preview iframe. DesignCanvas owns the
@@ -8080,6 +8085,7 @@ function DesignEditor() {
8080
8085
  activeFile,
8081
8086
  applyLocalContentUpdate,
8082
8087
  boardFileId,
8088
+ canvasBackground: canvasBackgroundRef.current,
8083
8089
  canEditDesign,
8084
8090
  collabContentFileIdRef,
8085
8091
  collabContentRef,
@@ -15778,7 +15784,7 @@ function DesignEditor() {
15778
15784
  })),
15779
15785
  [firstRunTemplatesQuery.data?.templates],
15780
15786
  );
15781
- const firstRunDesignSystems = useMemo(
15787
+ const designSystemOptions = useMemo(
15782
15788
  () => designSystemPickerOptions(designSystems),
15783
15789
  [designSystems],
15784
15790
  );
@@ -15790,6 +15796,10 @@ function DesignEditor() {
15790
15796
  // first screen row exists.
15791
15797
  const [chatMessageCount, setChatMessageCount] = useState(0);
15792
15798
  const showFirstRunStart = designIsEmpty && chatMessageCount === 0;
15799
+ // The picked system is stored on the design and read by every later
15800
+ // generation, so it outlives the starting-point row a template retires.
15801
+ const showComposerDesignSystem =
15802
+ designSystemsLoading || designSystemOptions.length > 0;
15793
15803
  const applyTemplate = useActionMutation("create-design-from-template");
15794
15804
  const handleFirstRunTemplate = useCallback(
15795
15805
  async (templateId: string) => {
@@ -15820,9 +15830,11 @@ function DesignEditor() {
15820
15830
  selectedPromptDesignSystemId,
15821
15831
  ],
15822
15832
  );
15823
- const handleFirstRunDesignSystem = useCallback(
15833
+ const handleComposerDesignSystem = useCallback(
15824
15834
  (designSystemId: string | null) => {
15825
- setPromptDesignSystemId(designSystemId ?? undefined);
15835
+ // null is the user picking "No design system"; only undefined means
15836
+ // nothing has been chosen yet, which re-resolves the default.
15837
+ setPromptDesignSystemId(designSystemId);
15826
15838
  persistPromptDesignSystem(designSystemId);
15827
15839
  },
15828
15840
  [persistPromptDesignSystem],
@@ -16334,6 +16346,29 @@ function DesignEditor() {
16334
16346
  string | null
16335
16347
  >(null);
16336
16348
  const canvasBackground = canvasBackgroundDraft ?? persistedCanvasBackground;
16349
+ /** The inspector must show a colour, not "none", for a canvas the user has
16350
+ * not set — and the only honest source for that is what the container is
16351
+ * currently painted with, which follows the editor theme. */
16352
+ const [themedCanvasBackground, setThemedCanvasBackground] = useState<
16353
+ string | null
16354
+ >(null);
16355
+ canvasBackgroundRef.current = canvasBackground ?? themedCanvasBackground;
16356
+ useEffect(() => {
16357
+ if (canvasBackground) return;
16358
+ // A throwaway element, not the raw token: the token is space-separated
16359
+ // HSL, which the colour parser rejects. After a frame, because next-themes
16360
+ // sets the `dark` class in an ancestor effect React runs after this one.
16361
+ const frame = requestAnimationFrame(() => {
16362
+ const probe = document.createElement("span");
16363
+ probe.style.cssText =
16364
+ "position:absolute;visibility:hidden;background-color:var(--design-editor-canvas-bg)";
16365
+ document.body.append(probe);
16366
+ const painted = window.getComputedStyle(probe).backgroundColor;
16367
+ probe.remove();
16368
+ setThemedCanvasBackground(painted || null);
16369
+ });
16370
+ return () => cancelAnimationFrame(frame);
16371
+ }, [canvasBackground, resolvedTheme]);
16337
16372
  const handleCanvasBackgroundChange = useCallback(
16338
16373
  (value: string, meta?: { phase?: "preview" | "commit" }) => {
16339
16374
  if (!id || !canEditDesignRef.current) return;
@@ -19760,6 +19795,7 @@ function DesignEditor() {
19760
19795
  selectedScreenLayoutGrid,
19761
19796
  onLayoutGridChange: canEditDesign ? handleLayoutGridChange : undefined,
19762
19797
  canvasBackground,
19798
+ canvasBackgroundFallback: themedCanvasBackground,
19763
19799
  onCanvasBackgroundChange: canEditDesign
19764
19800
  ? handleCanvasBackgroundChange
19765
19801
  : undefined,
@@ -20016,13 +20052,13 @@ function DesignEditor() {
20016
20052
  }
20017
20053
  composerSlot={
20018
20054
  <>
20019
- {showFirstRunStart ? (
20055
+ {showComposerDesignSystem ? (
20020
20056
  <div data-design-system-picker className="px-3 pb-2">
20021
20057
  <DesignSystemPickerControl
20022
- designSystems={firstRunDesignSystems}
20058
+ designSystems={designSystemOptions}
20023
20059
  loading={designSystemsLoading}
20024
20060
  selectedId={selectedPromptDesignSystemId ?? null}
20025
- onChange={handleFirstRunDesignSystem}
20061
+ onChange={handleComposerDesignSystem}
20026
20062
  />
20027
20063
  </div>
20028
20064
  ) : null}
@@ -20523,6 +20559,7 @@ function DesignEditor() {
20523
20559
  BreakpointDeviceControl — see rightSidebarActions. */}
20524
20560
  <div
20525
20561
  ref={canvasContainerRef}
20562
+ data-design-canvas-container
20526
20563
  className="relative min-w-0 flex-1 overflow-hidden bg-[var(--design-editor-canvas-bg)]"
20527
20564
  // Overrides the themed canvas colour rather than a background
20528
20565
  // shorthand, so every descendant reading the var follows.
@@ -112,7 +112,9 @@ interface DesignListResult {
112
112
  designs: Design[];
113
113
  }
114
114
 
115
- const DESIGN_PAGE_SIZE = 12;
115
+ // The New Design card shares the grid, so a full page is pageSize + 1 tiles;
116
+ // 12 is what divides evenly into every breakpoint's column count.
117
+ const DESIGN_PAGE_SIZE = 11;
116
118
 
117
119
  export default function Index() {
118
120
  const t = useT();
@@ -1201,9 +1203,14 @@ export default function Index() {
1201
1203
  aria-label={t("home.actionsForDesign", {
1202
1204
  title: design.title,
1203
1205
  })}
1204
- className="h-7 w-7 bg-black/60 hover:bg-black/80 cursor-pointer"
1206
+ // A scrim over the user's thumbnail, which is
1207
+ // their content: a theme-following chip vanishes
1208
+ // on a thumbnail that happens to match it.
1209
+ // guard:allow-raw-color — scrim over user content
1210
+ className="h-7 w-7 bg-black/60 hover:bg-black/75 cursor-pointer"
1205
1211
  >
1206
- <IconDots className="w-3.5 h-3.5 text-foreground/70" />
1212
+ {/* guard:allow-raw-color scrim over user content */}
1213
+ <IconDots className="w-3.5 h-3.5 text-white" />
1207
1214
  </Button>
1208
1215
  </DropdownMenuTrigger>
1209
1216
  <DropdownMenuContent align="end">
@@ -319,15 +319,6 @@ export default function Templates() {
319
319
  </div>
320
320
  ) : null}
321
321
  <main className="mx-auto w-full max-w-[1480px] px-4 py-6 sm:px-6 sm:py-10">
322
- <div className="mb-8 max-w-2xl">
323
- <h1 className="text-lg font-semibold text-foreground">
324
- {t("templatesPage.title")}
325
- </h1>
326
- <p className="mt-1 text-sm leading-relaxed text-muted-foreground">
327
- {t("templatesPage.description")}
328
- </p>
329
- </div>
330
-
331
322
  {isLoading ? (
332
323
  <TemplateGridSkeleton />
333
324
  ) : isError ? (
@@ -339,7 +330,6 @@ export default function Templates() {
339
330
  <div className="flex flex-col gap-10">
340
331
  <TemplateSection
341
332
  title={t("templatesPage.yourTemplates")}
342
- description={t("templatesPage.yourTemplatesDescription")}
343
333
  templates={userTemplates}
344
334
  linkedTemplateId={linkedTemplateId}
345
335
  empty={t("templatesPage.yourTemplatesEmpty")}
@@ -428,7 +418,6 @@ export default function Templates() {
428
418
 
429
419
  function TemplateSection({
430
420
  title,
431
- description,
432
421
  templates,
433
422
  linkedTemplateId,
434
423
  empty,
@@ -436,7 +425,6 @@ function TemplateSection({
436
425
  onDelete,
437
426
  }: {
438
427
  title: string;
439
- description?: string;
440
428
  templates: DesignTemplateSummary[];
441
429
  linkedTemplateId?: string | null;
442
430
  empty?: string;
@@ -446,12 +434,7 @@ function TemplateSection({
446
434
  if (templates.length === 0 && !empty) return null;
447
435
  return (
448
436
  <section>
449
- <div className="mb-3">
450
- <h2 className="text-sm font-semibold text-foreground">{title}</h2>
451
- {description ? (
452
- <p className="mt-1 text-xs text-muted-foreground">{description}</p>
453
- ) : null}
454
- </div>
437
+ <h2 className="mb-3 text-sm font-semibold text-foreground">{title}</h2>
455
438
  {templates.length === 0 ? (
456
439
  <div className="flex min-h-32 items-center justify-center rounded-xl border border-dashed bg-muted/20 px-6 text-center text-sm text-muted-foreground">
457
440
  {empty}
@@ -15,6 +15,7 @@ import {
15
15
  import {
16
16
  BOARD_TEXT_AUTO_COLOR_MARKER,
17
17
  destinationBackgroundLightness,
18
+ resolveDestinationBackgroundLightnessOrNull,
18
19
  } from "./cross-screen-text-color";
19
20
  import { escapeHtmlAttributeValue, escapeHtmlText } from "./dom-utils";
20
21
  import { isStandaloneHttpUrl } from "./editor-state";
@@ -281,7 +282,13 @@ function deepestFrameContaining(
281
282
  export function appendCanvasPrimitiveToHtml(
282
283
  content: string,
283
284
  primitive: CanvasPrimitiveInsert,
284
- options?: { preserveNegativePosition?: boolean; isBoardTarget?: boolean },
285
+ options?: {
286
+ preserveNegativePosition?: boolean;
287
+ isBoardTarget?: boolean;
288
+ /** The canvas colour behind the board. The board document is transparent
289
+ * by design, so its surface can only be measured from the host. */
290
+ boardBackground?: string | null;
291
+ },
285
292
  ): string | null {
286
293
  if (typeof window === "undefined") return null;
287
294
  // A live/localhost screen stores its route URL here, not a document.
@@ -520,18 +527,25 @@ export function appendCanvasPrimitiveToHtml(
520
527
  element.style.alignItems = "flex-start";
521
528
  }
522
529
  // "currentColor" inherits the unstyled document's black body text, so
523
- // it is invisible on any dark surface the always-dark board, and
524
- // equally a screen whose own background is dark. Light screens keep
525
- // "currentColor" so text still inherits their theme.
526
- // Measure the frame the text actually lands in: a dark frame on a light
527
- // page would otherwise keep currentColor and render invisible.
528
- // isBoardTarget only says which surface is behind the text, and the
529
- // board is not always dark — a measured background always wins.
530
- const measuredLightness = destinationBackgroundLightness(hostOrBody);
530
+ // it is invisible on any dark surface. The board renderer forces its own
531
+ // document transparent, so a colour on that body is never painted and
532
+ // measuring it would judge a surface nobody sees — a frame inside the
533
+ // board still can be. The canvas behind it arrives from the host.
534
+ const boardSurfaceIsLight =
535
+ options?.isBoardTarget === true
536
+ ? resolveDestinationBackgroundLightnessOrNull([
537
+ { color: options.boardBackground ?? null },
538
+ ])
539
+ : null;
540
+ const measuredIsLight =
541
+ options?.isBoardTarget === true && hostOrBody === doc.body
542
+ ? null
543
+ : destinationBackgroundLightness(hostOrBody);
544
+ const surfaceIsLight = measuredIsLight ?? boardSurfaceIsLight;
531
545
  const autoTextNeedsLightFill =
532
- measuredLightness === null
546
+ surfaceIsLight === null
533
547
  ? options?.isBoardTarget === true
534
- : !measuredLightness;
548
+ : !surfaceIsLight;
535
549
  const resolvedTextColor =
536
550
  primitive.fill ?? defaultCanvasTextColor(autoTextNeedsLightFill);
537
551
  element.style.color = resolvedTextColor;