@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.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/CHANGELOG.md +10 -0
- package/corpus/templates/assets/CHANGELOG.md +8 -0
- package/corpus/templates/calendar/CHANGELOG.md +18 -0
- package/corpus/templates/clips/CHANGELOG.md +19 -0
- package/corpus/templates/content/CHANGELOG.md +16 -0
- package/corpus/templates/content/actions/_database-property-setup.ts +33 -2
- package/corpus/templates/content/actions/_database-setup-discovery.ts +4 -4
- package/corpus/templates/content/actions/_property-utils.ts +9 -1
- package/corpus/templates/content/actions/_relation-values.ts +291 -0
- package/corpus/templates/content/actions/configure-document-property.ts +53 -1
- package/corpus/templates/content/actions/search-content-database-rows.ts +124 -0
- package/corpus/templates/content/actions/set-document-property.ts +21 -1
- package/corpus/templates/content/agent-native.config.ts +5 -0
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +892 -189
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +7 -2
- package/corpus/templates/content/app/components/editor/database/navigation-state.ts +4 -1
- package/corpus/templates/content/app/hooks/use-content-database.ts +16 -2
- package/corpus/templates/content/app/hooks/use-document-properties.ts +65 -5
- package/corpus/templates/content/app/i18n-data.ts +28 -0
- package/corpus/templates/content/changelog/2026-09-28-link-rows-across-collections-with-relation-properties.md +6 -0
- package/corpus/templates/content/changelog/2026-09-29-related-pages-can-be-loaded-past-the-first-25-results.md +6 -0
- package/corpus/templates/content/parity/matrix.md +1 -1
- package/corpus/templates/content/parity/matrix.ts +1 -0
- package/corpus/templates/content/shared/api.ts +21 -0
- package/corpus/templates/content/shared/properties.ts +19 -0
- package/corpus/templates/crm/CHANGELOG.md +8 -0
- package/corpus/templates/design/CHANGELOG.md +33 -0
- package/corpus/templates/design/actions/clone-creative-context-design-native.ts +12 -3
- package/corpus/templates/design/actions/generate-design.ts +2 -29
- package/corpus/templates/design/actions/update-design.ts +33 -8
- package/corpus/templates/design/app/components/design/design-canvas/LocalNetworkAccessPrompt.tsx +2 -2
- package/corpus/templates/design/changelog/2026-09-29-design-tweak-controls-stay-intact-when-an-edit-contains-inva.md +6 -0
- package/corpus/templates/design/changelog/2026-09-29-the-chrome-fallback-guide-shows-the-local-network-allow-opti.md +6 -0
- package/corpus/templates/design/shared/tweak-definition-schema.ts +30 -0
- package/corpus/templates/dispatch/CHANGELOG.md +4 -0
- package/corpus/templates/mail/CHANGELOG.md +34 -0
- package/corpus/templates/mail/app/components/email/EmailList.tsx +29 -11
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +4 -4
- package/corpus/templates/mail/app/pages/InboxPage.tsx +34 -10
- package/corpus/templates/mail/changelog/2026-09-29-mail-loading-layout.md +6 -0
- package/corpus/templates/slides/CHANGELOG.md +19 -0
- package/dist/automation/index.d.ts +1 -1
- package/dist/client/AppShellSkeleton.js +97 -54
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +7 -7
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
- package/dist/triggers/actions/manage-automation.d.ts +2 -2
- package/docs/content/versioning-and-stability.mdx +3 -2
- 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 &&
|
|
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 &&
|
|
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
|
-
? {
|
|
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
|
-
{
|
|
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 {
|
|
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
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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",
|
|
@@ -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`
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
);
|
package/corpus/templates/design/app/components/design/design-canvas/LocalNetworkAccessPrompt.tsx
CHANGED
|
@@ -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={
|
|
84
|
-
height={
|
|
83
|
+
width={1660}
|
|
84
|
+
height={948}
|
|
85
85
|
loading="lazy"
|
|
86
86
|
/>
|
|
87
87
|
</details>
|