@agent-native/core 0.197.0-nightly-20260929180759 → 0.197.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 (55) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/CHANGELOG.md +10 -0
  3. package/corpus/templates/assets/CHANGELOG.md +8 -0
  4. package/corpus/templates/calendar/CHANGELOG.md +18 -0
  5. package/corpus/templates/clips/CHANGELOG.md +19 -0
  6. package/corpus/templates/content/CHANGELOG.md +16 -0
  7. package/corpus/templates/content/actions/_database-property-setup.ts +33 -2
  8. package/corpus/templates/content/actions/_database-setup-discovery.ts +4 -4
  9. package/corpus/templates/content/actions/_property-utils.ts +9 -1
  10. package/corpus/templates/content/actions/_relation-values.ts +291 -0
  11. package/corpus/templates/content/actions/configure-document-property.ts +53 -1
  12. package/corpus/templates/content/actions/search-content-database-rows.ts +124 -0
  13. package/corpus/templates/content/actions/set-document-property.ts +21 -1
  14. package/corpus/templates/content/agent-native.config.ts +5 -0
  15. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +892 -189
  16. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +7 -2
  17. package/corpus/templates/content/app/components/editor/database/navigation-state.ts +4 -1
  18. package/corpus/templates/content/app/hooks/use-content-database.ts +16 -2
  19. package/corpus/templates/content/app/hooks/use-document-properties.ts +65 -5
  20. package/corpus/templates/content/app/i18n-data.ts +28 -0
  21. package/corpus/templates/content/changelog/2026-09-28-link-rows-across-collections-with-relation-properties.md +6 -0
  22. package/corpus/templates/content/changelog/2026-09-29-related-pages-can-be-loaded-past-the-first-25-results.md +6 -0
  23. package/corpus/templates/content/parity/matrix.md +1 -1
  24. package/corpus/templates/content/parity/matrix.ts +1 -0
  25. package/corpus/templates/content/shared/api.ts +21 -0
  26. package/corpus/templates/content/shared/properties.ts +19 -0
  27. package/corpus/templates/crm/CHANGELOG.md +8 -0
  28. package/corpus/templates/design/CHANGELOG.md +33 -0
  29. package/corpus/templates/design/actions/clone-creative-context-design-native.ts +12 -3
  30. package/corpus/templates/design/actions/generate-design.ts +2 -29
  31. package/corpus/templates/design/actions/update-design.ts +33 -8
  32. package/corpus/templates/design/app/components/design/design-canvas/LocalNetworkAccessPrompt.tsx +2 -2
  33. package/corpus/templates/design/changelog/2026-09-29-design-tweak-controls-stay-intact-when-an-edit-contains-inva.md +6 -0
  34. package/corpus/templates/design/changelog/2026-09-29-the-chrome-fallback-guide-shows-the-local-network-allow-opti.md +6 -0
  35. package/corpus/templates/design/shared/tweak-definition-schema.ts +30 -0
  36. package/corpus/templates/dispatch/CHANGELOG.md +4 -0
  37. package/corpus/templates/mail/CHANGELOG.md +34 -0
  38. package/corpus/templates/mail/app/components/email/EmailList.tsx +29 -11
  39. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +4 -4
  40. package/corpus/templates/mail/app/pages/InboxPage.tsx +34 -10
  41. package/corpus/templates/mail/changelog/2026-09-29-mail-loading-layout.md +6 -0
  42. package/corpus/templates/slides/CHANGELOG.md +19 -0
  43. package/dist/automation/index.d.ts +1 -1
  44. package/dist/client/AppShellSkeleton.js +97 -54
  45. package/dist/collab/awareness.d.ts +2 -2
  46. package/dist/collab/struct-routes.d.ts +1 -1
  47. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  48. package/dist/notifications/routes.d.ts +2 -2
  49. package/dist/observability/routes.d.ts +7 -7
  50. package/dist/secrets/routes.d.ts +6 -6
  51. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  52. package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
  53. package/dist/triggers/actions/manage-automation.d.ts +2 -2
  54. package/docs/content/versioning-and-stability.mdx +3 -2
  55. package/package.json +4 -4
@@ -4152,7 +4152,10 @@ export function databaseSelectionCapabilities(args: {
4152
4152
  export function databaseBulkEditableProperties(properties: DocumentProperty[]) {
4153
4153
  return properties.filter(
4154
4154
  (property) =>
4155
- property.editable && !isComputedPropertyType(property.definition.type),
4155
+ property.editable &&
4156
+ !isComputedPropertyType(property.definition.type) &&
4157
+ // Row mutations do not accept relation values yet.
4158
+ property.definition.type !== "relation",
4156
4159
  );
4157
4160
  }
4158
4161
 
@@ -14190,7 +14193,9 @@ function databaseTableCellDisplayValue(
14190
14193
  );
14191
14194
  }
14192
14195
 
14193
- return displayValue(property, undefined, wrapCells ? "wrapped" : "compact");
14196
+ return displayValue(property, undefined, wrapCells ? "wrapped" : "compact", {
14197
+ interactiveRelations: true,
14198
+ });
14194
14199
  }
14195
14200
 
14196
14201
  export function isDatabasePropertyVisibleInView(
@@ -263,7 +263,10 @@ export function databaseSelectedItems(
263
263
  export function databaseBulkEditableProperties(properties: DocumentProperty[]) {
264
264
  return properties.filter(
265
265
  (property) =>
266
- property.editable && !isComputedPropertyType(property.definition.type),
266
+ property.editable &&
267
+ !isComputedPropertyType(property.definition.type) &&
268
+ // Row mutations do not accept relation values yet.
269
+ property.definition.type !== "relation",
267
270
  );
268
271
  }
269
272
 
@@ -55,6 +55,7 @@ import type {
55
55
  SuggestSourceJoinKeyResponse,
56
56
  UpdateContentDatabasePersonalViewRequest,
57
57
  ValidateBuilderSourceExecutionRequest,
58
+ DocumentPropertyRelationTarget,
58
59
  } from "@shared/api";
59
60
  import type { Query, QueryClient } from "@tanstack/react-query";
60
61
  import { useQuery, useQueryClient } from "@tanstack/react-query";
@@ -384,6 +385,7 @@ export function applyDocumentPropertyValueToDatabaseResponse<
384
385
  documentId: string;
385
386
  propertyId: string;
386
387
  value: ContentDatabaseResponse["properties"][number]["value"];
388
+ relationTargets?: DocumentPropertyRelationTarget[];
387
389
  },
388
390
  ): T | undefined {
389
391
  if (!current) return current;
@@ -404,7 +406,13 @@ export function applyDocumentPropertyValueToDatabaseResponse<
404
406
  if (existingIndex >= 0) {
405
407
  const properties = item.properties.map((property, index) =>
406
408
  index === existingIndex
407
- ? { ...property, value: patch.value }
409
+ ? {
410
+ ...property,
411
+ value: patch.value,
412
+ ...(patch.relationTargets
413
+ ? { relationTargets: patch.relationTargets }
414
+ : {}),
415
+ }
408
416
  : property,
409
417
  );
410
418
  changed = true;
@@ -418,7 +426,13 @@ export function applyDocumentPropertyValueToDatabaseResponse<
418
426
  ...item,
419
427
  properties: [
420
428
  ...item.properties,
421
- { ...databaseProperty, value: patch.value },
429
+ {
430
+ ...databaseProperty,
431
+ value: patch.value,
432
+ ...(patch.relationTargets
433
+ ? { relationTargets: patch.relationTargets }
434
+ : {}),
435
+ },
422
436
  ]
423
437
  .slice()
424
438
  .sort((a, b) => a.definition.position - b.definition.position),
@@ -1,11 +1,14 @@
1
1
  import {
2
+ callActionWithRetry,
2
3
  useActionMutation,
3
4
  useActionQuery,
4
5
  } from "@agent-native/core/client/hooks";
5
6
  import type {
6
7
  ConfigureDocumentPropertyRequest,
8
+ ContentDatabaseMutationTarget,
7
9
  ContentDatabaseItemsPageResponse,
8
10
  ContentDatabaseResponse,
11
+ DocumentDiscoveryPagination,
9
12
  DeleteDocumentPropertyRequest,
10
13
  DocumentPropertyDefinition,
11
14
  DocumentPropertyOption,
@@ -17,7 +20,11 @@ import type {
17
20
  UpdateDatabaseItemsRequest,
18
21
  UpdateDatabaseItemsResponse,
19
22
  } from "@shared/api";
20
- import { useQueryClient, type UseMutationResult } from "@tanstack/react-query";
23
+ import {
24
+ useInfiniteQuery,
25
+ useQueryClient,
26
+ type UseMutationResult,
27
+ } from "@tanstack/react-query";
21
28
  import { toast } from "sonner";
22
29
 
23
30
  import { dbText } from "../components/editor/database/text";
@@ -285,6 +292,54 @@ function withDatabaseScope<
285
292
  } as UseMutationResult<TData, Error, ScopedVariables, TContext>;
286
293
  }
287
294
 
295
+ export interface ContentDatabaseRowSearchResponse {
296
+ databaseId: string;
297
+ databaseDocumentId: string;
298
+ rows: Array<{ documentId: string; title: string; icon: string | null }>;
299
+ pagination: DocumentDiscoveryPagination;
300
+ /** Present when the viewer may add rows to the related database. */
301
+ rowCreation: {
302
+ target: ContentDatabaseMutationTarget;
303
+ schemaRevision: string;
304
+ } | null;
305
+ }
306
+
307
+ /** Search a collection's rows by title, for picking relation values. */
308
+ export function useContentDatabaseRowSearch(
309
+ databaseId: string | null | undefined,
310
+ query: string,
311
+ enabled: boolean,
312
+ ) {
313
+ return useInfiniteQuery({
314
+ queryKey: [
315
+ "action",
316
+ "search-content-database-rows",
317
+ { databaseId, query: query.trim() ? query : undefined },
318
+ ],
319
+ initialPageParam: 0,
320
+ queryFn: ({ pageParam, signal }) => {
321
+ if (!databaseId) throw new Error("databaseId is required");
322
+ return callActionWithRetry<ContentDatabaseRowSearchResponse>(
323
+ "search-content-database-rows",
324
+ {
325
+ databaseId,
326
+ limit: 25,
327
+ offset: pageParam,
328
+ ...(query.trim() ? { query } : {}),
329
+ },
330
+ { signal },
331
+ );
332
+ },
333
+ getNextPageParam: (page) =>
334
+ page.pagination.hasMore
335
+ ? (page.pagination.nextOffset ?? undefined)
336
+ : undefined,
337
+ enabled: enabled && !!databaseId,
338
+ placeholderData: (prev) => prev,
339
+ staleTime: 10_000,
340
+ });
341
+ }
342
+
288
343
  export function useDocumentProperties(
289
344
  documentId: string | null,
290
345
  databaseId: string | null,
@@ -450,6 +505,7 @@ export function useSetDocumentProperty(
450
505
  documentId: variables.documentId,
451
506
  propertyId: variables.propertyId,
452
507
  value: variables.value,
508
+ relationTargets: variables.relationTargets,
453
509
  }),
454
510
  );
455
511
  queryClient.setQueriesData<ContentDatabaseItemsPageResponse>(
@@ -459,6 +515,7 @@ export function useSetDocumentProperty(
459
515
  documentId: variables.documentId,
460
516
  propertyId: variables.propertyId,
461
517
  value: variables.value,
518
+ relationTargets: variables.relationTargets,
462
519
  }),
463
520
  );
464
521
  return { previous, ...sequence };
@@ -491,10 +548,11 @@ export function useSetDocumentProperty(
491
548
  if (!isLatestDocumentPropertyMutation(queryClient, mutationContext)) {
492
549
  return;
493
550
  }
494
- const savedValue =
495
- data.properties.find(
496
- (property) => property.definition.id === variables.propertyId,
497
- )?.value ?? variables.value;
551
+ const savedProperty = data.properties.find(
552
+ (property) => property.definition.id === variables.propertyId,
553
+ );
554
+ const savedValue = savedProperty?.value ?? variables.value;
555
+ const savedRelationTargets = savedProperty?.relationTargets;
498
556
  queryClient.setQueriesData<ContentDatabaseResponse>(
499
557
  contentDatabaseQueryFilter(databaseDocumentId),
500
558
  (current) =>
@@ -502,6 +560,7 @@ export function useSetDocumentProperty(
502
560
  documentId: variables.documentId,
503
561
  propertyId: variables.propertyId,
504
562
  value: savedValue as DocumentPropertyValue,
563
+ relationTargets: savedRelationTargets,
505
564
  }),
506
565
  );
507
566
  queryClient.setQueriesData<ContentDatabaseItemsPageResponse>(
@@ -511,6 +570,7 @@ export function useSetDocumentProperty(
511
570
  documentId: variables.documentId,
512
571
  propertyId: variables.propertyId,
513
572
  value: savedValue as DocumentPropertyValue,
573
+ relationTargets: savedRelationTargets,
514
574
  }),
515
575
  );
516
576
  void queryClient.invalidateQueries({
@@ -572,6 +572,34 @@ const editorPropertiesMessages = {
572
572
  pageCount_many: "{{count}} pages",
573
573
  pageCount_other: "{{count}} pages",
574
574
  pasteFileOrMediaLink: "Paste file or media link",
575
+ back: "Back",
576
+ relatedDatabase: "Related database",
577
+ searchDatabases: "Search databases",
578
+ thisDatabase: "This database",
579
+ noDatabases: "No databases found",
580
+ searchPages: "Search pages",
581
+ noMatchingPages: "No matching pages",
582
+ removeRelation: "Remove {{name}}",
583
+ openPage: "Open {{name}}",
584
+ unavailablePage: "Unavailable page",
585
+ unavailablePageCount_zero: "{{count}} unavailable",
586
+ unavailablePageCount_one: "{{count}} unavailable",
587
+ unavailablePageCount_two: "{{count}} unavailable",
588
+ unavailablePageCount_few: "{{count}} unavailable",
589
+ unavailablePageCount_many: "{{count}} unavailable",
590
+ unavailablePageCount_other: "{{count}} unavailable",
591
+ noRelatedDatabase: "No related database configured",
592
+ linkAPage: "Link or create a page…",
593
+ createPage: "Create “{{name}}”",
594
+ reorderRelation: "Reorder {{name}}",
595
+ selectMore: "Select more",
596
+ selectAPage: "Select a page",
597
+ selectedCount_zero: "{{count}} selected",
598
+ selectedCount_one: "{{count}} selected",
599
+ selectedCount_two: "{{count}} selected",
600
+ selectedCount_few: "{{count}} selected",
601
+ selectedCount_many: "{{count}} selected",
602
+ selectedCount_other: "{{count}} selected",
575
603
  personOrEmail: "Person or email",
576
604
  propertyMenuFor: "Property menu for {{name}}",
577
605
  description: "Description",
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: added
3
+ date: 2026-09-28
4
+ ---
5
+
6
+ Link rows across collections with relation properties
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-09-29
4
+ ---
5
+
6
+ Related pages can be loaded past the first 25 results
@@ -11,7 +11,7 @@ This generated matrix tracks whether high-value Content UI operations use the sa
11
11
  | database.form-submissions | database | Submit public database forms as new rows | action-backed | `submit-content-database-form` | `app/components/editor/database/FormView.tsx` | A validated form submission atomically creates a database row document and its editable property values. | - | - | P0 | covered | `actions/submit-content-database-form.db.test.ts` | - | - |
12
12
  | database.lifecycle-and-trash | database | Create, soft-delete, restore, list, and inspect content databases | action-backed | `create-content-database`, `create-inline-content-database`, `delete-content-database`, `describe-content-database`, `get-content-database`, `list-content-databases`, `list-trashed-content-databases`, `restore-content-database` | `app/components/editor/SlashCommandMenu.tsx`, `app/hooks/use-content-database.ts`, `app/hooks/use-documents.ts` | Database pages and database records are created, read, soft-deleted, restored, and listed. | - | - | P0 | covered | `actions/content-database-lifecycle.db.test.ts`, `actions/list-content-databases.db.test.ts`, `server/plugins/agent-chat.spec.ts`, `../../packages/core/src/server/agent-chat/content-a2a-capabilities.spec.ts` | `database-source-scope` | - |
13
13
  | database.private-preview-drafts | database | Persist and reconcile a user's private database-page preview draft | action-backed | `get-preview-document-draft`, `resolve-preview-document-draft`, `update-preview-document-draft` | `app/components/editor/database/DatabaseView.tsx`, `app/hooks/use-documents.ts` | A user's private preview draft is read, saved, conflict-checked, or deleted without changing the shared database page until the normal save flow applies it. | These per-user editor-state actions are intentionally hidden from agent tools because preview drafts are a private UI recovery mechanism. | - | P1 | covered | `actions/preview-document-draft.db.test.ts` | - | - |
14
- | database.properties-and-view-config | database | Configure properties, values, ordering, and saved views | action-backed | `configure-document-property`, `delete-document-property`, `duplicate-document-property`, `get-content-database-personal-view`, `list-document-properties`, `reorder-document-property`, `set-document-property`, `update-content-database-personal-view`, `update-content-database-view` | `app/components/editor/DocumentProperties.tsx`, `app/components/editor/DocumentDatabase.tsx`, `app/components/editor/database/DatabaseView.tsx`, `app/hooks/use-document-properties.ts` | Property schemas, property values, property order, filters, sorts, grouping, hidden columns, view type, and view settings are stored. | - | - | P0 | covered | `actions/database-setup-mcp.db.test.ts`, `actions/bind-content-database-source-field.db.test.ts`, `actions/content-database-source-actions.test.ts`, `actions/resync-content-database-source.db.test.ts` | `database-source-scope` | - |
14
+ | database.properties-and-view-config | database | Configure properties, values, ordering, and saved views | action-backed | `configure-document-property`, `delete-document-property`, `duplicate-document-property`, `get-content-database-personal-view`, `list-document-properties`, `reorder-document-property`, `search-content-database-rows`, `set-document-property`, `update-content-database-personal-view`, `update-content-database-view` | `app/components/editor/DocumentProperties.tsx`, `app/components/editor/DocumentDatabase.tsx`, `app/components/editor/database/DatabaseView.tsx`, `app/hooks/use-document-properties.ts` | Property schemas, property values, property order, filters, sorts, grouping, hidden columns, view type, and view settings are stored. | - | - | P0 | covered | `actions/database-setup-mcp.db.test.ts`, `actions/bind-content-database-source-field.db.test.ts`, `actions/content-database-source-actions.test.ts`, `actions/resync-content-database-source.db.test.ts` | `database-source-scope` | - |
15
15
  | database.rows | database | Add, duplicate, move, open, edit, and remove database rows | action-backed | `add-database-item`, `update-database-item`, `upsert-database-item-by-key`, `list-content-database-blocks`, `mutate-content-database-block`, `remove-database-items`, `duplicate-database-items`, `duplicate-database-item`, `update-database-items`, `migrate-content-database-rows`, `manage-content-database-migration`, `move-database-item`, `set-document-property` | `app/components/editor/DocumentDatabase.tsx`, `app/components/editor/database/DatabaseView.tsx` | Database row memberships and ordering are created, duplicated, moved, edited, and removed without deleting the backing page; bounded migrations atomically update row bodies and properties through the same canonical data model. | - | - | P0 | covered | `actions/database-row-batch-actions.db.test.ts`, `actions/upsert-database-item-by-key.db.test.ts`, `actions/migrate-content-database-rows.db.test.ts`, `actions/content-database-block-actions.db.test.ts`, `parity/__tests__/database-row-batch-reliability.test.ts` | `database-bulk-row-reliability` | - |
16
16
  | database.table-query-page | database | Query one constrained table page while retaining database metadata | action-backed | `query-content-database-items` | `app/components/editor/database/DatabaseView.tsx`, `app/hooks/use-content-database.ts` | - | This UI-only bounded projection is intentionally hidden with agentTool: false; agents use get-content-database for database reads and list-documents, get-document, search-documents, and navigate for workspace navigation. | - | P0 | covered | `actions/content-database-lifecycle.db.test.ts`, `app/hooks/use-content-database.test.ts` | - | - |
17
17
  | editor.agent-assist-prompts | editor | Ask AI from slash generation or comment context | client-assist | - | `app/components/editor/SlashCommandMenu.tsx`, `app/components/editor/CommentsSidebar.tsx` | No direct durable mutation; the prompt asks the agent to use document actions when it decides to write. | - | - | P1 | none | - | - | - |
@@ -610,6 +610,7 @@ export const parityMatrix: ParityRow[] = [
610
610
  "get-content-database-personal-view",
611
611
  "list-document-properties",
612
612
  "reorder-document-property",
613
+ "search-content-database-rows",
613
614
  "set-document-property",
614
615
  "update-content-database-personal-view",
615
616
  "update-content-database-view",
@@ -269,11 +269,27 @@ export type DocumentPropertySystemRole =
269
269
  | "files_parent"
270
270
  | "files_source";
271
271
 
272
+ /** A linked row of a relation property, resolved for the current viewer. */
273
+ export interface DocumentPropertyRelationTarget {
274
+ documentId: string;
275
+ title: string;
276
+ icon: string | null;
277
+ /** Set only when the viewer can read the target database. */
278
+ databaseId: string | null;
279
+ databaseDocumentId: string | null;
280
+ }
281
+
272
282
  export interface DocumentProperty {
273
283
  definition: DocumentPropertyDefinition;
274
284
  value: DocumentPropertyValue;
275
285
  editable: boolean;
276
286
  blocksField?: BlocksFieldIdentity;
287
+ /**
288
+ * Relation properties only: the linked rows this viewer may see, in value
289
+ * order. IDs in `value` without a target here are unavailable (restricted or
290
+ * trashed — deliberately indistinguishable).
291
+ */
292
+ relationTargets?: DocumentPropertyRelationTarget[];
277
293
  }
278
294
 
279
295
  export interface DocumentPropertiesResponse {
@@ -303,6 +319,11 @@ export interface SetDocumentPropertyRequest {
303
319
  propertyId: string;
304
320
  value: DocumentPropertyValue;
305
321
  expectedBlocksFieldRevision?: number;
322
+ /**
323
+ * Client-only hint: titles for relation IDs, shown until the server's
324
+ * resolved targets arrive. The action ignores it.
325
+ */
326
+ relationTargets?: DocumentPropertyRelationTarget[];
306
327
  }
307
328
 
308
329
  export interface DuplicateDocumentPropertyRequest {
@@ -31,6 +31,24 @@ export const DOCUMENT_PROPERTY_TYPES = [
31
31
  ...COMPUTED_DOCUMENT_PROPERTY_TYPES,
32
32
  ] as const;
33
33
 
34
+ /** Most rows one relation value may link to. */
35
+ export const MAX_RELATION_TARGETS = 100;
36
+
37
+ /** Trim, drop empties and non-strings, and dedupe relation IDs, keeping order. */
38
+ export function normalizeRelationIds(value: unknown): string[] {
39
+ const items = Array.isArray(value) ? value : value == null ? [] : [value];
40
+ const seen = new Set<string>();
41
+ const ids: string[] = [];
42
+ for (const item of items) {
43
+ if (typeof item !== "string") continue;
44
+ const id = item.trim();
45
+ if (!id || seen.has(id)) continue;
46
+ seen.add(id);
47
+ ids.push(id);
48
+ }
49
+ return ids;
50
+ }
51
+
34
52
  export const CREATABLE_DOCUMENT_PROPERTY_TYPES = [
35
53
  "text",
36
54
  "number",
@@ -45,6 +63,7 @@ export const CREATABLE_DOCUMENT_PROPERTY_TYPES = [
45
63
  "url",
46
64
  "email",
47
65
  "phone",
66
+ "relation",
48
67
  "blocks",
49
68
  "id",
50
69
  "created_time",
@@ -1,9 +1,17 @@
1
1
  # CRM changelog
2
2
 
3
+ ## 2026-09-29
4
+
5
+ ### Improved
6
+
7
+ - Settings pages now share the account layout, with dated updates that load in batches.
8
+
3
9
  ## 2026-09-28
4
10
 
5
11
  ### Improved
6
12
 
13
+ - The app canvas now meets the right chat sidebar without a dark seam or rounded corner.
14
+ - Settings now opens in the new layout, with Account, Connections, Agent, Organization, and app sections.
7
15
  - Ask CRM now starts with a focused title.
8
16
 
9
17
  ## 2026-09-27
@@ -3,15 +3,48 @@
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-29
7
+
8
+ ### Improved
9
+
10
+ - The browser permission guide points to Allow and the Connection is secure step.
11
+ - The Chrome fallback guide shows the Local network Allow option.
12
+
13
+ ### Fixed
14
+
15
+ - Clearing a prompt removes its final character
16
+ - Design tweak controls stay intact when an edit contains invalid definitions
17
+ - Live previews no longer stay stuck when the running app reloads.
18
+ - Live previews stay ready when a delayed connection check completes.
19
+ - Local visual edits use the copy prompt handoff for signed-in editors too.
20
+ - Duplicated screens keep the same spacing as other screens on the board.
21
+
6
22
  ## 2026-09-28
7
23
 
8
24
  ### Improved
9
25
 
26
+ - The app canvas now meets the right chat sidebar without a dark seam or rounded corner.
27
+ - Center the home search field and keep prompt submission responsive while AI readiness is checked.
28
+ - Corner radius handles stay with the drag and appear only on supported shapes
29
+ - Corner radius is now disabled for plain text layers, matching Figma, and stays available when text has a fill, border, shadow, or background blur
30
+ - Layer rows select from anywhere, with consistent blue highlights and tighter nesting
31
+ - Local screen setup is clearer, failed connections stop showing a loading state, and signed-out sharing stays compact.
32
+ - Settings now opens in the new layout, with Account, Connections, Agent, Organization, and app sections.
33
+ - Design settings pages now share a consistent width and layout, while What's new displays date-grouped updates in batches of ten.
34
+ - The Appearance panel now shows a Blend mode row with the active mode and a remove button whenever a layer uses a blend mode
10
35
  - Starter prompts now sit below the chat composer
11
36
  - The design list search and import controls stay legible on narrow screens.
12
37
 
13
38
  ### Fixed
14
39
 
40
+ - Corner-radius handles appear as soon as a shape is selected under the pointer.
41
+ - Design editor prompts stay visible while the composer loads
42
+ - Existing designs open to Recent, and the home prompt stays interactive while AI readiness is checked.
43
+ - PNG, JPG, WebP, and PDF exports and the export preview now keep layers flipped or rotated with the scale, rotate, and translate properties
44
+ - Selecting a layer now keeps its name visible in the Layers panel.
45
+ - Shader previews now appear on the selected screen in Design overview
46
+ - The home Figma import control no longer clips its label
47
+ - The Layers panel has a cleaner divider, and responsive preview controls share one continuous toolbar border.
15
48
  - Canvas navigation and undo keep working when connected apps focus controls automatically
16
49
  - The model picker shows available models when AI is configured.
17
50
  - Template menus stay available when you hover over a card.
@@ -10,6 +10,7 @@ import { z } from "zod";
10
10
 
11
11
  import { mutateDesignData } from "../server/lib/design-data-mutation.js";
12
12
  import { saveImportedDesignFiles } from "../server/lib/import-design-files.js";
13
+ import { tweakDefinitionsSchema } from "../shared/tweak-definition-schema.js";
13
14
  import createDesign from "./create-design.js";
14
15
 
15
16
  const payloadSchema = z.object({
@@ -62,6 +63,17 @@ export default defineAction({
62
63
  "Governed design clone payload failed integrity verification.",
63
64
  );
64
65
  const payload = payloadSchema.parse(JSON.parse(body));
66
+ const capturedData = z
67
+ .record(z.string(), z.unknown())
68
+ .parse(JSON.parse(payload.designData));
69
+ if (
70
+ capturedData.tweaks !== undefined &&
71
+ !tweakDefinitionsSchema.safeParse(capturedData.tweaks).success
72
+ ) {
73
+ throw new Error(
74
+ "Governed design clone payload has invalid tweak definitions.",
75
+ );
76
+ }
65
77
  const created = await createDesign.run({
66
78
  title: args.title?.trim() || "Context design",
67
79
  projectType: "prototype",
@@ -79,9 +91,6 @@ export default defineAction({
79
91
  });
80
92
  if (saved.files.length !== payload.files.length)
81
93
  throw new Error("Design clone did not persist every saved file.");
82
- const capturedData = z
83
- .record(z.string(), z.unknown())
84
- .parse(JSON.parse(payload.designData));
85
94
  const {
86
95
  canvasFrames: _capturedFrames,
87
96
  screenMetadata: _capturedMetadata,
@@ -68,6 +68,7 @@ import {
68
68
  visibleBreakpointWidths,
69
69
  } from "../shared/responsive-frame-layout.js";
70
70
  import { annotateScreenHtmlForPersist } from "../shared/screen-annotation.js";
71
+ import { tweakDefinitionsSchema } from "../shared/tweak-definition-schema.js";
71
72
 
72
73
  function designDeepLink(designId: string, screenId?: string): string {
73
74
  return buildDeepLink({
@@ -536,35 +537,7 @@ const generateDesignAction = defineAction({
536
537
  tweaks: z
537
538
  .preprocess(
538
539
  (v) => (typeof v === "string" ? JSON.parse(v) : v),
539
- z
540
- .array(
541
- z.object({
542
- id: z.string(),
543
- label: z.string(),
544
- type: z.enum([
545
- "color-swatch",
546
- "color-swatches",
547
- "segment",
548
- "slider",
549
- "toggle",
550
- ]),
551
- options: z
552
- .array(
553
- z.object({
554
- label: z.string(),
555
- value: z.string(),
556
- color: z.string().optional(),
557
- }),
558
- )
559
- .optional(),
560
- min: z.number().optional(),
561
- max: z.number().optional(),
562
- step: z.number().optional(),
563
- defaultValue: z.union([z.string(), z.number(), z.boolean()]),
564
- cssVar: z.string().optional(),
565
- }),
566
- )
567
- .optional(),
540
+ tweakDefinitionsSchema.optional(),
568
541
  )
569
542
  .optional()
570
543
  .describe(
@@ -6,6 +6,7 @@ import { z } from "zod";
6
6
  import { getDb, schema } from "../server/db/index.js";
7
7
  import { snapshotDesignBeforeAgentEdit } from "../server/lib/design-versions.js";
8
8
  import { numericDesignDataWriteError } from "../shared/canvas-frames.js";
9
+ import { tweakDefinitionsSchema } from "../shared/tweak-definition-schema.js";
9
10
 
10
11
  const MAX_DATA_CAS_ATTEMPTS = 5;
11
12
  const MAX_DATA_OPERATION_SOURCES = 128;
@@ -20,6 +21,21 @@ const FORBIDDEN_DATA_PATH_SEGMENTS = new Set([
20
21
  "prototype",
21
22
  ]);
22
23
 
24
+ function tweakDefinitionsWriteError(value: unknown): string | null {
25
+ return tweakDefinitionsSchema.safeParse(value).success
26
+ ? null
27
+ : "tweaks must be an array of valid definitions";
28
+ }
29
+
30
+ function designDataWriteError(path: string[], value: unknown): string | null {
31
+ const geometryError = numericDesignDataWriteError(path, value);
32
+ if (geometryError) return geometryError;
33
+ if (path.length === 1 && path[0] === "tweaks") {
34
+ return tweakDefinitionsWriteError(value);
35
+ }
36
+ return null;
37
+ }
38
+
23
39
  const dataPathSchema = z
24
40
  .array(
25
41
  z
@@ -47,10 +63,7 @@ const dataOperationSchema = z
47
63
  ])
48
64
  .superRefine((operation, context) => {
49
65
  if (operation.op !== "set") return;
50
- const message = numericDesignDataWriteError(
51
- operation.path,
52
- operation.value,
53
- );
66
+ const message = designDataWriteError(operation.path, operation.value);
54
67
  if (message) {
55
68
  context.addIssue({ code: "custom", path: ["value"], message });
56
69
  }
@@ -82,10 +95,7 @@ const agentDataOperationSchema = z
82
95
  ])
83
96
  .superRefine((operation, context) => {
84
97
  if (operation.op !== "set") return;
85
- const message = numericDesignDataWriteError(
86
- operation.path,
87
- operation.value,
88
- );
98
+ const message = designDataWriteError(operation.path, operation.value);
89
99
  if (message) {
90
100
  context.addIssue({ code: "custom", path: ["value"], message });
91
101
  }
@@ -228,12 +238,22 @@ function applyDataOperations(
228
238
 
229
239
  function validatePersistedDataSnapshot(
230
240
  raw: string,
241
+ designId: string,
231
242
  touchedMaps?: ReadonlySet<string>,
232
243
  touchedCanvasFrameIds?: ReadonlySet<string>,
233
244
  ): void {
234
245
  const parsed = JSON.parse(raw);
235
246
  if (!isRecord(parsed)) return;
236
247
  for (const [key, value] of Object.entries(parsed)) {
248
+ if (key === "tweaks") {
249
+ if (touchedMaps && !touchedMaps.has(key)) continue;
250
+ if (tweakDefinitionsWriteError(value)) {
251
+ throw new Error(
252
+ `Design ${designId} has invalid tweak definitions. Refusing to save them.`,
253
+ );
254
+ }
255
+ continue;
256
+ }
237
257
  if (
238
258
  touchedMaps &&
239
259
  NUMERIC_DESIGN_DATA_MAPS.has(key) &&
@@ -393,6 +413,10 @@ export default defineAction({
393
413
  }
394
414
  if (isRecord(parsedSnapshot)) {
395
415
  for (const [key, value] of Object.entries(parsedSnapshot)) {
416
+ if (key === "tweaks") {
417
+ const tweakError = tweakDefinitionsWriteError(value);
418
+ if (tweakError) throw new Error(tweakError);
419
+ }
396
420
  const message = numericDesignDataWriteError([key], value);
397
421
  if (message) throw new Error(message);
398
422
  }
@@ -500,6 +524,7 @@ export default defineAction({
500
524
  : undefined;
501
525
  validatePersistedDataSnapshot(
502
526
  nextData,
527
+ id,
503
528
  touchedMaps,
504
529
  touchedCanvasFrameIds,
505
530
  );
@@ -80,8 +80,8 @@ export function LocalNetworkAccessPrompt({
80
80
  src="/local-network-access-settings.png"
81
81
  alt={permissionSettingsInstructions}
82
82
  className="mt-3 block w-full rounded-md border border-border"
83
- width={1686}
84
- height={933}
83
+ width={1660}
84
+ height={948}
85
85
  loading="lazy"
86
86
  />
87
87
  </details>
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-09-29
4
+ ---
5
+
6
+ Design tweak controls stay intact when an edit contains invalid definitions
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-09-29
4
+ ---
5
+
6
+ The Chrome fallback guide shows the Local network Allow option.