@agent-native/core 0.114.0 → 0.114.1
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 +2 -2
- package/corpus/core/CHANGELOG.md +9 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/client/build-compatibility.ts +77 -0
- package/corpus/core/src/client/route-chunk-recovery/index.ts +1 -0
- package/corpus/core/src/client/use-action.ts +32 -1
- package/corpus/core/src/deploy/build.ts +4 -1
- package/corpus/core/src/org/context.ts +56 -30
- package/corpus/core/src/server/action-routes.ts +52 -2
- package/corpus/core/src/server/auth-plugin.ts +9 -4
- package/corpus/core/src/server/better-auth-instance.ts +5 -1
- package/corpus/core/src/shared/mcp-embed-headers.ts +1 -1
- package/corpus/core/src/vite/client.ts +17 -0
- package/corpus/templates/content/AGENTS.md +2 -0
- package/corpus/templates/content/actions/_content-database-personal-view.ts +49 -5
- package/corpus/templates/content/actions/_content-document-access.ts +21 -0
- package/corpus/templates/content/actions/_content-favorites.ts +91 -0
- package/corpus/templates/content/actions/_content-files.ts +79 -23
- package/corpus/templates/content/actions/_content-sidebar-state.ts +24 -0
- package/corpus/templates/content/actions/_content-space-access.ts +15 -17
- package/corpus/templates/content/actions/_content-space-catalog-guards.ts +20 -0
- package/corpus/templates/content/actions/_content-spaces.ts +364 -167
- package/corpus/templates/content/actions/_database-source-utils.ts +8 -0
- package/corpus/templates/content/actions/_database-utils.ts +249 -15
- package/corpus/templates/content/actions/_delete-content-space.ts +84 -0
- package/corpus/templates/content/actions/_files-system-properties.ts +499 -0
- package/corpus/templates/content/actions/_local-file-documents.ts +0 -1
- package/corpus/templates/content/actions/_property-utils.ts +18 -9
- package/corpus/templates/content/actions/bind-content-database-source-field.ts +3 -0
- package/corpus/templates/content/actions/configure-document-property.ts +3 -0
- package/corpus/templates/content/actions/create-content-database.ts +2 -2
- package/corpus/templates/content/actions/create-content-space.ts +34 -0
- package/corpus/templates/content/actions/create-document.ts +4 -1
- package/corpus/templates/content/actions/delete-content-space.ts +19 -0
- package/corpus/templates/content/actions/delete-database-items.ts +28 -1
- package/corpus/templates/content/actions/delete-document-property.ts +3 -0
- package/corpus/templates/content/actions/delete-document.ts +39 -2
- package/corpus/templates/content/actions/duplicate-database-item.ts +6 -0
- package/corpus/templates/content/actions/duplicate-database-items.ts +6 -0
- package/corpus/templates/content/actions/duplicate-document-property.ts +3 -0
- package/corpus/templates/content/actions/get-content-database.ts +11 -2
- package/corpus/templates/content/actions/get-content-sidebar-state.ts +23 -0
- package/corpus/templates/content/actions/get-document.ts +32 -7
- package/corpus/templates/content/actions/import-content-source.ts +33 -1
- package/corpus/templates/content/actions/list-content-spaces.ts +34 -2
- package/corpus/templates/content/actions/list-documents.ts +39 -7
- package/corpus/templates/content/actions/reorder-document-property.ts +9 -0
- package/corpus/templates/content/actions/set-document-property.ts +41 -23
- package/corpus/templates/content/actions/share-local-file-document.ts +15 -0
- package/corpus/templates/content/actions/submit-content-database-form.ts +7 -0
- package/corpus/templates/content/actions/sync-local-folder-source.ts +14 -0
- package/corpus/templates/content/actions/update-content-database-view.ts +1 -0
- package/corpus/templates/content/actions/update-content-sidebar-state.ts +27 -0
- package/corpus/templates/content/actions/update-document.ts +158 -66
- package/corpus/templates/content/app/components/EmptyState.tsx +4 -57
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +8 -1
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +519 -178
- package/corpus/templates/content/app/components/editor/DocumentInfoPanel.tsx +39 -0
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +1 -1
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +266 -7
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +430 -152
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +467 -66
- package/corpus/templates/content/app/components/editor/database/view-config.ts +9 -3
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +720 -196
- package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +173 -1
- package/corpus/templates/content/app/components/sidebar/document-sidebar-sections.ts +1 -1
- package/corpus/templates/content/app/components/sidebar/select-content-space.ts +91 -16
- package/corpus/templates/content/app/entry.client.tsx +5 -1
- package/corpus/templates/content/app/hooks/use-content-database.ts +84 -3
- package/corpus/templates/content/app/hooks/use-content-spaces.ts +60 -2
- package/corpus/templates/content/app/hooks/use-create-page.ts +30 -0
- package/corpus/templates/content/app/hooks/use-documents.ts +270 -19
- package/corpus/templates/content/app/i18n-data.ts +243 -20
- package/corpus/templates/content/app/lib/local-folder-picker-safety.ts +54 -0
- package/corpus/templates/content/app/lib/local-folder-picker-support.ts +18 -0
- package/corpus/templates/content/app/routes/_app.favorites.tsx +30 -0
- package/corpus/templates/content/app/routes/_app.local-files.tsx +18 -6
- package/corpus/templates/content/changelog/2026-07-17-workspace-files-now-stay-in-a-loading-state-until-automatic-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-17-workspace-switching-now-opens-the-selected-files-database-pr.md +6 -0
- package/corpus/templates/content/changelog/2026-07-17-workspaces-now-expand-above-their-own-files-list-without-dup.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-create-named-workspaces-each-with-its-own-private-files-data.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-favorites-align-with-workspace-and-file-rows-in-the-sidebar.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-local-folder-selection-no-longer-invokes-the-unsafe-native-p.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-made-content-workspaces-independently-expandable-restored-or.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-page-details-now-live-in-a-shared-info-and-comments-rail-dat.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-workspace-database-views-now-control-sidebar-workspace-navig.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-workspace-navigation-now-aligns-favorites-with-workspace-pag.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-favorites-now-open-as-a-table-and-compact-breadcrumbs-make-w.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-files-databases-now-start-unfiltered-sidebar-rows-exactly-fo.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-sidebar-page-trees-now-open-only-when-expanded-remember-thei.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-workspace-file-databases-now-use-the-workspace-name-open-as-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-workspace-navigation-now-stays-stable-on-hover-uses-folder-i.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-workspace-rows-now-use-folder-icons-and-deleting-a-user-crea.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-workspaces-can-be-created-without-leaving-the-workspaces-dat.md +6 -0
- package/corpus/templates/content/e2e/global-setup.ts +3 -2
- package/corpus/templates/content/parity/matrix.md +28 -28
- package/corpus/templates/content/parity/matrix.ts +8 -2
- package/corpus/templates/content/server/db/index.ts +1 -0
- package/corpus/templates/content/server/db/schema.ts +10 -0
- package/corpus/templates/content/server/lib/documents.ts +12 -7
- package/corpus/templates/content/server/plugins/db.ts +17 -0
- package/corpus/templates/content/shared/api.ts +9 -0
- package/corpus/templates/content/shared/database-form.ts +11 -2
- package/corpus/templates/content/vite.config.ts +1 -0
- package/dist/client/build-compatibility.d.ts +11 -0
- package/dist/client/build-compatibility.d.ts.map +1 -0
- package/dist/client/build-compatibility.js +56 -0
- package/dist/client/build-compatibility.js.map +1 -0
- package/dist/client/route-chunk-recovery/index.d.ts +1 -0
- package/dist/client/route-chunk-recovery/index.d.ts.map +1 -1
- package/dist/client/route-chunk-recovery/index.js +1 -0
- package/dist/client/route-chunk-recovery/index.js.map +1 -1
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +19 -1
- package/dist/client/use-action.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.js +2 -1
- package/dist/deploy/build.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/org/context.d.ts +4 -2
- package/dist/org/context.d.ts.map +1 -1
- package/dist/org/context.js +44 -22
- package/dist/org/context.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +10 -10
- package/dist/provider-api/actions/provider-api.d.ts +12 -12
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +32 -2
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth-plugin.d.ts.map +1 -1
- package/dist/server/auth-plugin.js +9 -5
- package/dist/server/auth-plugin.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +4 -1
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/mcp-embed-headers.d.ts +1 -1
- package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
- package/dist/shared/mcp-embed-headers.js +1 -1
- package/dist/shared/mcp-embed-headers.js.map +1 -1
- package/dist/vite/client.d.ts +6 -0
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +8 -0
- package/dist/vite/client.js.map +1 -1
- package/package.json +1 -1
- package/src/client/build-compatibility.ts +77 -0
- package/src/client/route-chunk-recovery/index.ts +1 -0
- package/src/client/use-action.ts +32 -1
- package/src/deploy/build.ts +4 -1
- package/src/org/context.ts +56 -30
- package/src/server/action-routes.ts +52 -2
- package/src/server/auth-plugin.ts +9 -4
- package/src/server/better-auth-instance.ts +5 -1
- package/src/shared/mcp-embed-headers.ts +1 -1
- package/src/vite/client.ts +17 -0
|
@@ -13,11 +13,12 @@ import {
|
|
|
13
13
|
parsePropertyOptions,
|
|
14
14
|
type DocumentPropertyType,
|
|
15
15
|
} from "../shared/properties.js";
|
|
16
|
+
import { resolveContentDocumentAccess } from "./_content-document-access.js";
|
|
16
17
|
import {
|
|
17
|
-
|
|
18
|
+
getDatabaseById,
|
|
19
|
+
listPropertiesForDatabaseDocuments,
|
|
18
20
|
nanoid,
|
|
19
21
|
normalizedValueJson,
|
|
20
|
-
resolvePropertyDatabaseForDocument,
|
|
21
22
|
writeBlockFieldContent,
|
|
22
23
|
writePrimaryBlocksContent,
|
|
23
24
|
} from "./_property-utils.js";
|
|
@@ -30,27 +31,34 @@ export default defineAction({
|
|
|
30
31
|
value: z.unknown().describe("Value for the property type"),
|
|
31
32
|
}),
|
|
32
33
|
run: async ({ documentId, propertyId, value }) => {
|
|
33
|
-
const access = await assertAccess("document", documentId, "editor");
|
|
34
|
-
const document = access.resource;
|
|
35
34
|
const db = getDb();
|
|
36
|
-
const database = await resolvePropertyDatabaseForDocument(document);
|
|
37
|
-
if (!database) throw new Error("Document is not part of a database.");
|
|
38
|
-
|
|
39
35
|
const [definition] = await db
|
|
40
36
|
.select()
|
|
41
37
|
.from(schema.documentPropertyDefinitions)
|
|
38
|
+
.where(eq(schema.documentPropertyDefinitions.id, propertyId));
|
|
39
|
+
if (!definition) throw new Error(`Property "${propertyId}" not found`);
|
|
40
|
+
if (!definition.databaseId) {
|
|
41
|
+
throw new Error(`Property "${propertyId}" is not attached to a database`);
|
|
42
|
+
}
|
|
43
|
+
const database = await getDatabaseById(definition.databaseId);
|
|
44
|
+
if (!database) throw new Error("Document database not found.");
|
|
45
|
+
await assertAccess("document", database.documentId, "editor");
|
|
46
|
+
if (definition.systemRole) {
|
|
47
|
+
throw new Error("System properties are derived and cannot be edited.");
|
|
48
|
+
}
|
|
49
|
+
const access = await resolveContentDocumentAccess(documentId);
|
|
50
|
+
if (!access) throw new Error(`Document "${documentId}" not found`);
|
|
51
|
+
const document = access.resource;
|
|
52
|
+
const [membership] = await db
|
|
53
|
+
.select({ id: schema.contentDatabaseItems.id })
|
|
54
|
+
.from(schema.contentDatabaseItems)
|
|
42
55
|
.where(
|
|
43
56
|
and(
|
|
44
|
-
eq(schema.
|
|
45
|
-
eq(
|
|
46
|
-
schema.documentPropertyDefinitions.ownerEmail,
|
|
47
|
-
document.ownerEmail,
|
|
48
|
-
),
|
|
49
|
-
eq(schema.documentPropertyDefinitions.databaseId, database.id),
|
|
57
|
+
eq(schema.contentDatabaseItems.databaseId, database.id),
|
|
58
|
+
eq(schema.contentDatabaseItems.documentId, documentId),
|
|
50
59
|
),
|
|
51
60
|
);
|
|
52
|
-
if (!
|
|
53
|
-
|
|
61
|
+
if (!membership) throw new Error("Document is not part of this database.");
|
|
54
62
|
const type = definition.type as DocumentPropertyType;
|
|
55
63
|
if (isComputedPropertyType(type)) {
|
|
56
64
|
throw new Error("Computed properties cannot be edited.");
|
|
@@ -62,6 +70,7 @@ export default defineAction({
|
|
|
62
70
|
// primary "Content" field writes to the document body; additional Blocks
|
|
63
71
|
// fields write to their own independent store.
|
|
64
72
|
if (isBlocksPropertyType(type)) {
|
|
73
|
+
await assertAccess("document", documentId, "editor");
|
|
65
74
|
const normalized = normalizePropertyValue(type, value);
|
|
66
75
|
const content = typeof normalized === "string" ? normalized : "";
|
|
67
76
|
const target = blocksStorageTarget(
|
|
@@ -73,7 +82,7 @@ export default defineAction({
|
|
|
73
82
|
await writeBlockFieldContent({
|
|
74
83
|
documentId,
|
|
75
84
|
propertyId,
|
|
76
|
-
ownerEmail:
|
|
85
|
+
ownerEmail: database.ownerEmail,
|
|
77
86
|
content,
|
|
78
87
|
now,
|
|
79
88
|
});
|
|
@@ -82,11 +91,17 @@ export default defineAction({
|
|
|
82
91
|
return {
|
|
83
92
|
documentId,
|
|
84
93
|
databaseId: database.id,
|
|
85
|
-
properties:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
94
|
+
properties:
|
|
95
|
+
(
|
|
96
|
+
await listPropertiesForDatabaseDocuments(database.id, [
|
|
97
|
+
{
|
|
98
|
+
...document,
|
|
99
|
+
content:
|
|
100
|
+
target === "document_body" ? content : document.content,
|
|
101
|
+
updatedAt: now,
|
|
102
|
+
},
|
|
103
|
+
])
|
|
104
|
+
).get(documentId) ?? [],
|
|
90
105
|
};
|
|
91
106
|
}
|
|
92
107
|
|
|
@@ -109,7 +124,7 @@ export default defineAction({
|
|
|
109
124
|
} else {
|
|
110
125
|
await db.insert(schema.documentPropertyValues).values({
|
|
111
126
|
id: nanoid(),
|
|
112
|
-
ownerEmail:
|
|
127
|
+
ownerEmail: database.ownerEmail,
|
|
113
128
|
documentId,
|
|
114
129
|
propertyId,
|
|
115
130
|
valueJson,
|
|
@@ -123,7 +138,10 @@ export default defineAction({
|
|
|
123
138
|
return {
|
|
124
139
|
documentId,
|
|
125
140
|
databaseId: database.id,
|
|
126
|
-
properties:
|
|
141
|
+
properties:
|
|
142
|
+
(await listPropertiesForDatabaseDocuments(database.id, [document])).get(
|
|
143
|
+
documentId,
|
|
144
|
+
) ?? [],
|
|
127
145
|
};
|
|
128
146
|
},
|
|
129
147
|
});
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
parseDocumentFavorite,
|
|
14
14
|
parseDocumentHideFromSearch,
|
|
15
15
|
} from "../server/lib/documents.js";
|
|
16
|
+
import { setFavoriteMembership } from "./_content-favorites.js";
|
|
16
17
|
import { ensureDocumentFilesMembership } from "./_content-files.js";
|
|
17
18
|
import {
|
|
18
19
|
organizationContentSpaceId,
|
|
@@ -136,6 +137,13 @@ export default defineAction({
|
|
|
136
137
|
.where(eq(schema.documents.id, existing.id));
|
|
137
138
|
|
|
138
139
|
await ensureDocumentFilesMembership(db, existing.id, now);
|
|
140
|
+
await setFavoriteMembership({
|
|
141
|
+
db,
|
|
142
|
+
userEmail,
|
|
143
|
+
documentId: existing.id,
|
|
144
|
+
favorite: localDocument.isFavorite,
|
|
145
|
+
now,
|
|
146
|
+
});
|
|
139
147
|
|
|
140
148
|
const [row] = await db
|
|
141
149
|
.select()
|
|
@@ -182,6 +190,13 @@ export default defineAction({
|
|
|
182
190
|
updatedAt: now,
|
|
183
191
|
});
|
|
184
192
|
await ensureDocumentFilesMembership(db, documentId, now);
|
|
193
|
+
await setFavoriteMembership({
|
|
194
|
+
db,
|
|
195
|
+
userEmail,
|
|
196
|
+
documentId,
|
|
197
|
+
favorite: localDocument.isFavorite,
|
|
198
|
+
now,
|
|
199
|
+
});
|
|
185
200
|
},
|
|
186
201
|
);
|
|
187
202
|
|
|
@@ -9,6 +9,7 @@ import { z } from "zod";
|
|
|
9
9
|
import { getDb, schema } from "../server/db/index.js";
|
|
10
10
|
import type {
|
|
11
11
|
ContentDatabaseView,
|
|
12
|
+
DocumentPropertySystemRole,
|
|
12
13
|
SubmitContentDatabaseFormResponse,
|
|
13
14
|
} from "../shared/api.js";
|
|
14
15
|
import { contentDatabaseFormQuestions } from "../shared/database-form.js";
|
|
@@ -143,6 +144,11 @@ function resolveSubmittedProperties(
|
|
|
143
144
|
}
|
|
144
145
|
const definition = exact ?? named[0];
|
|
145
146
|
if (!definition) throw new Error(`Unknown form property "${inputKey}".`);
|
|
147
|
+
if (definition.systemRole) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
`System property "${definition.name}" cannot be submitted.`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
146
152
|
if (!enabledPropertyIds.has(definition.id)) {
|
|
147
153
|
throw new Error(
|
|
148
154
|
`Property "${definition.name}" is not enabled in this form.`,
|
|
@@ -235,6 +241,7 @@ export default defineAction({
|
|
|
235
241
|
definition: {
|
|
236
242
|
id: definition.id,
|
|
237
243
|
type: definition.type as DocumentPropertyType,
|
|
244
|
+
systemRole: definition.systemRole as DocumentPropertySystemRole | null,
|
|
238
245
|
},
|
|
239
246
|
}));
|
|
240
247
|
const questions = contentDatabaseFormQuestions(formView, properties);
|
|
@@ -14,8 +14,10 @@ import {
|
|
|
14
14
|
isContentSourcePath,
|
|
15
15
|
parseContentSourceFile,
|
|
16
16
|
} from "../shared/content-source.js";
|
|
17
|
+
import { setFavoriteMembership } from "./_content-favorites.js";
|
|
17
18
|
import { ensureDocumentsFilesMembership } from "./_content-files.js";
|
|
18
19
|
import { resolveContentSpaceAccess } from "./_content-space-access.js";
|
|
20
|
+
import { provisionContentSpaces } from "./_content-spaces.js";
|
|
19
21
|
import { LOCAL_FOLDER_SOURCE_TYPE } from "./_local-folder-source.js";
|
|
20
22
|
|
|
21
23
|
const MAX_SOURCE_FILES = 500;
|
|
@@ -175,6 +177,7 @@ export default defineAction({
|
|
|
175
177
|
throw new Error(`Local folder source "${sourceId}" not found`);
|
|
176
178
|
}
|
|
177
179
|
await resolveContentSpaceAccess(target.database.spaceId, "editor");
|
|
180
|
+
if (!dryRun) await provisionContentSpaces(db, userEmail);
|
|
178
181
|
const targetSpaceId = target.database.spaceId;
|
|
179
182
|
|
|
180
183
|
const parsed = entries.map(([path, value]) =>
|
|
@@ -572,6 +575,17 @@ export default defineAction({
|
|
|
572
575
|
}
|
|
573
576
|
}
|
|
574
577
|
|
|
578
|
+
for (const plan of plans) {
|
|
579
|
+
if (plan.conflict || plan.existing || !plan.file.isFavorite) continue;
|
|
580
|
+
await setFavoriteMembership({
|
|
581
|
+
db: tx,
|
|
582
|
+
userEmail,
|
|
583
|
+
documentId: plan.id,
|
|
584
|
+
favorite: true,
|
|
585
|
+
now,
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
|
|
575
589
|
const materializedIds = plans
|
|
576
590
|
.filter((plan) => !plan.conflict)
|
|
577
591
|
.map((plan) => plan.id);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core/action";
|
|
2
|
+
import { putUserSetting } from "@agent-native/core/settings";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
CONTENT_SIDEBAR_STATE_SETTING_KEY,
|
|
6
|
+
contentSidebarStateSchema,
|
|
7
|
+
} from "./_content-sidebar-state.js";
|
|
8
|
+
|
|
9
|
+
export default defineAction({
|
|
10
|
+
description: "Persist the current user's Content sidebar expansion state.",
|
|
11
|
+
schema: contentSidebarStateSchema,
|
|
12
|
+
agentTool: false,
|
|
13
|
+
run: async (state, ctx) => {
|
|
14
|
+
if (!ctx?.userEmail) throw new Error("Not authenticated.");
|
|
15
|
+
const normalized = {
|
|
16
|
+
...state,
|
|
17
|
+
expandedWorkspaceIds: [...new Set(state.expandedWorkspaceIds)],
|
|
18
|
+
expandedDocumentIds: [...new Set(state.expandedDocumentIds)],
|
|
19
|
+
};
|
|
20
|
+
await putUserSetting(
|
|
21
|
+
ctx.userEmail,
|
|
22
|
+
CONTENT_SIDEBAR_STATE_SETTING_KEY,
|
|
23
|
+
normalized,
|
|
24
|
+
);
|
|
25
|
+
return { state: normalized };
|
|
26
|
+
},
|
|
27
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
2
|
import { writeAppState } from "@agent-native/core/application-state";
|
|
3
3
|
import { agentTouchDocument } from "@agent-native/core/collab";
|
|
4
|
+
import { getRequestUserEmail } from "@agent-native/core/server/request-context";
|
|
4
5
|
import { assertAccess } from "@agent-native/core/sharing";
|
|
5
6
|
import {
|
|
6
7
|
getGenerationCreativeContext,
|
|
@@ -9,7 +10,7 @@ import {
|
|
|
9
10
|
validateGenerationCreativeContext,
|
|
10
11
|
} from "@agent-native/creative-context/server";
|
|
11
12
|
import type { CreativeContextReuseLabel } from "@agent-native/creative-context/types";
|
|
12
|
-
import { and, eq, desc } from "drizzle-orm";
|
|
13
|
+
import { and, eq, desc, inArray } from "drizzle-orm";
|
|
13
14
|
import { z } from "zod";
|
|
14
15
|
|
|
15
16
|
import { getDb, schema } from "../server/db/index.js";
|
|
@@ -20,6 +21,12 @@ import {
|
|
|
20
21
|
import type { DocumentUpdateResponse } from "../shared/api.js";
|
|
21
22
|
import { BUILDER_CMS_BODY_CONTENT_KEY } from "./_builder-cms-source-adapter.js";
|
|
22
23
|
import { reconcileInlineDatabasesForDocument } from "./_content-database-lifecycle.js";
|
|
24
|
+
import { resolveContentDocumentAccess } from "./_content-document-access.js";
|
|
25
|
+
import {
|
|
26
|
+
favoriteDocumentIds,
|
|
27
|
+
setFavoriteMembership,
|
|
28
|
+
} from "./_content-favorites.js";
|
|
29
|
+
import { provisionContentSpaces } from "./_content-spaces.js";
|
|
23
30
|
import { serializeDocumentSource } from "./_document-source.js";
|
|
24
31
|
|
|
25
32
|
// Not (yet) part of the shared API surface — kept local to avoid touching
|
|
@@ -137,6 +144,10 @@ function canManageRole(role: string) {
|
|
|
137
144
|
return role === "owner" || role === "admin";
|
|
138
145
|
}
|
|
139
146
|
|
|
147
|
+
function canEditRole(role: string) {
|
|
148
|
+
return role === "owner" || role === "admin" || role === "editor";
|
|
149
|
+
}
|
|
150
|
+
|
|
140
151
|
function builderBodyWithoutImageSourceComponentMarkers(
|
|
141
152
|
content: string | null | undefined,
|
|
142
153
|
) {
|
|
@@ -304,11 +315,30 @@ export default defineAction({
|
|
|
304
315
|
const isAgentCaller =
|
|
305
316
|
ctx?.caller === "tool" || ctx?.caller === "mcp" || ctx?.caller === "a2a";
|
|
306
317
|
|
|
307
|
-
const
|
|
318
|
+
const favoriteOnly =
|
|
319
|
+
args.isFavorite !== undefined &&
|
|
320
|
+
args.title === undefined &&
|
|
321
|
+
args.content === undefined &&
|
|
322
|
+
args.description === undefined &&
|
|
323
|
+
args.icon === undefined;
|
|
324
|
+
const access = favoriteOnly
|
|
325
|
+
? await resolveContentDocumentAccess(id)
|
|
326
|
+
: await assertAccess("document", id, "editor");
|
|
327
|
+
if (!access) throw new Error(`Document "${id}" not found`);
|
|
308
328
|
const existing = access.resource;
|
|
309
329
|
const ownerEmail = existing.ownerEmail as string;
|
|
310
330
|
|
|
311
331
|
const db = getDb();
|
|
332
|
+
const requestUserEmail = getRequestUserEmail();
|
|
333
|
+
if (args.isFavorite !== undefined && !requestUserEmail) {
|
|
334
|
+
throw new Error("no authenticated user");
|
|
335
|
+
}
|
|
336
|
+
if (args.isFavorite !== undefined) {
|
|
337
|
+
await provisionContentSpaces(db, requestUserEmail as string);
|
|
338
|
+
}
|
|
339
|
+
const currentFavorite = requestUserEmail
|
|
340
|
+
? (await favoriteDocumentIds(db, requestUserEmail, [id])).has(id)
|
|
341
|
+
: parseDocumentFavorite(existing.isFavorite);
|
|
312
342
|
|
|
313
343
|
// Strip leading H1 that duplicates the title
|
|
314
344
|
let content = args.content;
|
|
@@ -383,11 +413,12 @@ export default defineAction({
|
|
|
383
413
|
content !== undefined && content !== existing.content;
|
|
384
414
|
const iconChanged = args.icon !== undefined && args.icon !== existing.icon;
|
|
385
415
|
const favoriteChanged =
|
|
386
|
-
args.isFavorite !== undefined &&
|
|
387
|
-
(args.isFavorite ? 1 : 0) !== (existing.isFavorite ?? 0);
|
|
416
|
+
args.isFavorite !== undefined && args.isFavorite !== currentFavorite;
|
|
388
417
|
const descriptionChanged =
|
|
389
418
|
args.description !== undefined &&
|
|
390
419
|
args.description.trim() !== existing.description;
|
|
420
|
+
const documentFieldsChanged =
|
|
421
|
+
titleChanged || contentChanged || iconChanged || descriptionChanged;
|
|
391
422
|
const anyChange =
|
|
392
423
|
titleChanged ||
|
|
393
424
|
contentChanged ||
|
|
@@ -452,69 +483,127 @@ export default defineAction({
|
|
|
452
483
|
updates.description = args.description.trim();
|
|
453
484
|
if (contentChanged) updates.content = content;
|
|
454
485
|
if (iconChanged) updates.icon = args.icon;
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
),
|
|
466
|
-
)
|
|
467
|
-
.returning({ id: schema.documents.id });
|
|
468
|
-
|
|
469
|
-
if (!applied || applied.length === 0) {
|
|
470
|
-
// Someone else's write landed after the caller's snapshot. Don't
|
|
471
|
-
// apply this save at all (title/icon/favorite included — a partial
|
|
472
|
-
// apply would desync the fields from what the caller believes it
|
|
473
|
-
// just sent) and hand back the current server row instead so the
|
|
474
|
-
// caller can reconcile.
|
|
475
|
-
const [current] = await db
|
|
476
|
-
.select()
|
|
477
|
-
.from(schema.documents)
|
|
478
|
-
.where(eq(schema.documents.id, id));
|
|
479
|
-
return {
|
|
480
|
-
conflict: true,
|
|
481
|
-
id,
|
|
482
|
-
document: {
|
|
483
|
-
id: current.id,
|
|
484
|
-
urlPath: `/page/${current.id}`,
|
|
485
|
-
parentId: current.parentId,
|
|
486
|
-
title: current.title,
|
|
487
|
-
content: current.content,
|
|
488
|
-
description: current.description,
|
|
489
|
-
icon: current.icon,
|
|
490
|
-
position: current.position,
|
|
491
|
-
isFavorite: parseDocumentFavorite(current.isFavorite),
|
|
492
|
-
hideFromSearch: parseDocumentHideFromSearch(
|
|
493
|
-
current.hideFromSearch,
|
|
486
|
+
let contentCasConflict = false;
|
|
487
|
+
await db.transaction(async (tx) => {
|
|
488
|
+
if (useContentCas) {
|
|
489
|
+
const applied = await tx
|
|
490
|
+
.update(schema.documents)
|
|
491
|
+
.set(updates)
|
|
492
|
+
.where(
|
|
493
|
+
and(
|
|
494
|
+
eq(schema.documents.id, id),
|
|
495
|
+
eq(schema.documents.updatedAt, args.baseUpdatedAt as string),
|
|
494
496
|
),
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
497
|
+
)
|
|
498
|
+
.returning({ id: schema.documents.id });
|
|
499
|
+
if (!applied || applied.length === 0) {
|
|
500
|
+
contentCasConflict = true;
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
} else if (documentFieldsChanged) {
|
|
504
|
+
await tx
|
|
505
|
+
.update(schema.documents)
|
|
506
|
+
.set(updates)
|
|
507
|
+
.where(eq(schema.documents.id, id));
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (favoriteChanged) {
|
|
511
|
+
await setFavoriteMembership({
|
|
512
|
+
db: tx,
|
|
513
|
+
userEmail: requestUserEmail as string,
|
|
514
|
+
documentId: id,
|
|
515
|
+
favorite: args.isFavorite as boolean,
|
|
516
|
+
now: updates.updatedAt as string,
|
|
517
|
+
});
|
|
505
518
|
}
|
|
506
|
-
} else {
|
|
507
|
-
await db
|
|
508
|
-
.update(schema.documents)
|
|
509
|
-
.set(updates)
|
|
510
|
-
.where(eq(schema.documents.id, id));
|
|
511
|
-
}
|
|
512
519
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
520
|
+
if (titleChanged && args.title !== undefined) {
|
|
521
|
+
const [database] = await tx
|
|
522
|
+
.select({
|
|
523
|
+
id: schema.contentDatabases.id,
|
|
524
|
+
spaceId: schema.contentDatabases.spaceId,
|
|
525
|
+
systemRole: schema.contentDatabases.systemRole,
|
|
526
|
+
})
|
|
527
|
+
.from(schema.contentDatabases)
|
|
528
|
+
.where(eq(schema.contentDatabases.documentId, id));
|
|
529
|
+
if (database) {
|
|
530
|
+
const title =
|
|
531
|
+
database.systemRole === "files"
|
|
532
|
+
? args.title.trim() || "Untitled"
|
|
533
|
+
: args.title;
|
|
534
|
+
await tx
|
|
535
|
+
.update(schema.contentDatabases)
|
|
536
|
+
.set({ title, updatedAt: updates.updatedAt as string })
|
|
537
|
+
.where(eq(schema.contentDatabases.id, database.id));
|
|
538
|
+
if (database.systemRole === "files" && database.spaceId) {
|
|
539
|
+
await tx
|
|
540
|
+
.update(schema.documents)
|
|
541
|
+
.set({ title, updatedAt: updates.updatedAt as string })
|
|
542
|
+
.where(eq(schema.documents.id, id));
|
|
543
|
+
await tx
|
|
544
|
+
.update(schema.contentSpaces)
|
|
545
|
+
.set({ name: title, updatedAt: updates.updatedAt as string })
|
|
546
|
+
.where(eq(schema.contentSpaces.id, database.spaceId));
|
|
547
|
+
const catalogReferences = await tx
|
|
548
|
+
.select({
|
|
549
|
+
documentId: schema.contentSpaceCatalogItems.documentId,
|
|
550
|
+
})
|
|
551
|
+
.from(schema.contentSpaceCatalogItems)
|
|
552
|
+
.where(
|
|
553
|
+
eq(schema.contentSpaceCatalogItems.spaceId, database.spaceId),
|
|
554
|
+
);
|
|
555
|
+
if (catalogReferences.length > 0) {
|
|
556
|
+
await tx
|
|
557
|
+
.update(schema.documents)
|
|
558
|
+
.set({ title, updatedAt: updates.updatedAt as string })
|
|
559
|
+
.where(
|
|
560
|
+
inArray(
|
|
561
|
+
schema.documents.id,
|
|
562
|
+
catalogReferences.map(
|
|
563
|
+
(reference) => reference.documentId,
|
|
564
|
+
),
|
|
565
|
+
),
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
if (contentCasConflict) {
|
|
574
|
+
// Someone else's write landed after the caller's snapshot. Don't
|
|
575
|
+
// apply this save at all (title/icon/favorite included — a partial
|
|
576
|
+
// apply would desync the fields from what the caller believes it
|
|
577
|
+
// just sent) and hand back the current server row instead so the
|
|
578
|
+
// caller can reconcile.
|
|
579
|
+
const [current] = await db
|
|
580
|
+
.select()
|
|
581
|
+
.from(schema.documents)
|
|
582
|
+
.where(eq(schema.documents.id, id));
|
|
583
|
+
return {
|
|
584
|
+
conflict: true,
|
|
585
|
+
id,
|
|
586
|
+
document: {
|
|
587
|
+
id: current.id,
|
|
588
|
+
urlPath: `/page/${current.id}`,
|
|
589
|
+
parentId: current.parentId,
|
|
590
|
+
title: current.title,
|
|
591
|
+
content: current.content,
|
|
592
|
+
description: current.description,
|
|
593
|
+
icon: current.icon,
|
|
594
|
+
position: current.position,
|
|
595
|
+
isFavorite: currentFavorite,
|
|
596
|
+
hideFromSearch: parseDocumentHideFromSearch(current.hideFromSearch),
|
|
597
|
+
visibility: current.visibility,
|
|
598
|
+
accessRole: access.role,
|
|
599
|
+
canEdit: canEditRole(access.role),
|
|
600
|
+
canManage: canManageRole(access.role),
|
|
601
|
+
createdAt: current.createdAt,
|
|
602
|
+
updatedAt: current.updatedAt,
|
|
603
|
+
source: serializeDocumentSource(current),
|
|
604
|
+
softDeletedDatabaseIds: [],
|
|
605
|
+
},
|
|
606
|
+
};
|
|
518
607
|
}
|
|
519
608
|
|
|
520
609
|
if (contentChanged) {
|
|
@@ -570,6 +659,9 @@ export default defineAction({
|
|
|
570
659
|
.select()
|
|
571
660
|
.from(schema.documents)
|
|
572
661
|
.where(eq(schema.documents.id, id));
|
|
662
|
+
const finalFavorite = requestUserEmail
|
|
663
|
+
? (await favoriteDocumentIds(db, requestUserEmail, [id])).has(id)
|
|
664
|
+
: parseDocumentFavorite(doc.isFavorite);
|
|
573
665
|
|
|
574
666
|
await writeAppState("refresh-signal", { ts: Date.now() });
|
|
575
667
|
|
|
@@ -582,11 +674,11 @@ export default defineAction({
|
|
|
582
674
|
description: doc.description,
|
|
583
675
|
icon: doc.icon,
|
|
584
676
|
position: doc.position,
|
|
585
|
-
isFavorite:
|
|
677
|
+
isFavorite: finalFavorite,
|
|
586
678
|
hideFromSearch: parseDocumentHideFromSearch(doc.hideFromSearch),
|
|
587
679
|
visibility: doc.visibility,
|
|
588
680
|
accessRole: access.role,
|
|
589
|
-
canEdit:
|
|
681
|
+
canEdit: canEditRole(access.role),
|
|
590
682
|
canManage: canManageRole(access.role),
|
|
591
683
|
createdAt: doc.createdAt,
|
|
592
684
|
updatedAt: doc.updatedAt,
|
|
@@ -1,68 +1,15 @@
|
|
|
1
1
|
import { useT } from "@agent-native/core/client/i18n";
|
|
2
|
-
import type { Document } from "@shared/api";
|
|
3
2
|
import { IconFileText, IconPlus } from "@tabler/icons-react";
|
|
4
|
-
import { useQueryClient } from "@tanstack/react-query";
|
|
5
|
-
import { useNavigate } from "react-router";
|
|
6
|
-
import { toast } from "sonner";
|
|
7
3
|
|
|
8
4
|
import { Button } from "@/components/ui/button";
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
function nanoid(size = 12): string {
|
|
12
|
-
const chars =
|
|
13
|
-
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
14
|
-
const bytes = crypto.getRandomValues(new Uint8Array(size));
|
|
15
|
-
return Array.from(bytes, (b) => chars[b % chars.length]).join("");
|
|
16
|
-
}
|
|
5
|
+
import { useCreatePage } from "@/hooks/use-create-page";
|
|
17
6
|
|
|
18
7
|
export function EmptyState() {
|
|
19
|
-
const
|
|
20
|
-
const queryClient = useQueryClient();
|
|
21
|
-
const createDocument = useCreateDocument();
|
|
8
|
+
const createPage = useCreatePage();
|
|
22
9
|
const t = useT();
|
|
23
10
|
|
|
24
|
-
const handleCreate =
|
|
25
|
-
|
|
26
|
-
const now = new Date().toISOString();
|
|
27
|
-
const tempDoc: Document = {
|
|
28
|
-
id,
|
|
29
|
-
parentId: null,
|
|
30
|
-
title: "",
|
|
31
|
-
content: "",
|
|
32
|
-
icon: null,
|
|
33
|
-
position: 9999,
|
|
34
|
-
isFavorite: false,
|
|
35
|
-
hideFromSearch: false,
|
|
36
|
-
visibility: "private",
|
|
37
|
-
createdAt: now,
|
|
38
|
-
updatedAt: now,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
// Optimistically inject into cache and navigate immediately
|
|
42
|
-
queryClient.setQueryData(
|
|
43
|
-
["action", "list-documents", undefined],
|
|
44
|
-
(old: any) => {
|
|
45
|
-
const docs: Document[] =
|
|
46
|
-
old?.documents ?? (Array.isArray(old) ? old : []);
|
|
47
|
-
return { documents: [...docs, tempDoc] };
|
|
48
|
-
},
|
|
49
|
-
);
|
|
50
|
-
queryClient.setQueryData(["action", "get-document", { id }], tempDoc);
|
|
51
|
-
navigate(`/page/${id}`, { flushSync: true });
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
await createDocument.mutateAsync({ id, title: "" });
|
|
55
|
-
} catch (err) {
|
|
56
|
-
queryClient.invalidateQueries({ queryKey: ["action", "list-documents"] });
|
|
57
|
-
queryClient.removeQueries({
|
|
58
|
-
queryKey: ["action", "get-document", { id }],
|
|
59
|
-
});
|
|
60
|
-
navigate("/");
|
|
61
|
-
toast.error(t("empty.createFailed"), {
|
|
62
|
-
description:
|
|
63
|
-
err instanceof Error ? err.message : t("empty.genericError"),
|
|
64
|
-
});
|
|
65
|
-
}
|
|
11
|
+
const handleCreate = () => {
|
|
12
|
+
void createPage().catch(() => undefined);
|
|
66
13
|
};
|
|
67
14
|
|
|
68
15
|
return (
|
|
@@ -188,6 +188,7 @@ interface CommentsSidebarProps {
|
|
|
188
188
|
onSelectedThreadChange?: (id: string | null) => void;
|
|
189
189
|
onHoveredThreadChange?: (id: string | null) => void;
|
|
190
190
|
currentUserEmail?: string;
|
|
191
|
+
forceVisible?: boolean;
|
|
191
192
|
}
|
|
192
193
|
|
|
193
194
|
export function CommentsSidebar({
|
|
@@ -202,6 +203,7 @@ export function CommentsSidebar({
|
|
|
202
203
|
onSelectedThreadChange,
|
|
203
204
|
onHoveredThreadChange,
|
|
204
205
|
currentUserEmail,
|
|
206
|
+
forceVisible = false,
|
|
205
207
|
}: CommentsSidebarProps) {
|
|
206
208
|
const t = useT();
|
|
207
209
|
const { data: members = [] } = useMentionMembers();
|
|
@@ -433,7 +435,7 @@ export function CommentsSidebar({
|
|
|
433
435
|
|
|
434
436
|
const hasContent =
|
|
435
437
|
openThreads.length > 0 || !!pendingComment || resolvedThreads.length > 0;
|
|
436
|
-
if (!hasContent && !isLoading) return null;
|
|
438
|
+
if (!hasContent && !isLoading && !forceVisible) return null;
|
|
437
439
|
|
|
438
440
|
// Sort open threads by their position in the document.
|
|
439
441
|
const sortedThreads = [...openThreads].sort((a, b) => {
|
|
@@ -488,6 +490,11 @@ export function CommentsSidebar({
|
|
|
488
490
|
className="relative w-80 shrink-0 pb-16"
|
|
489
491
|
data-comments-sidebar
|
|
490
492
|
>
|
|
493
|
+
{!hasContent && !isLoading ? (
|
|
494
|
+
<div className="px-4 py-8 text-sm text-muted-foreground">
|
|
495
|
+
{t("comments.empty")}
|
|
496
|
+
</div>
|
|
497
|
+
) : null}
|
|
491
498
|
{/* Pending new comment — positioned at the selection Y offset */}
|
|
492
499
|
{pendingComment && (
|
|
493
500
|
<div
|