@agent-native/core 0.101.5 → 0.101.7

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 (149) hide show
  1. package/README.md +1 -1
  2. package/corpus/README.md +1 -1
  3. package/corpus/core/CHANGELOG.md +12 -0
  4. package/corpus/core/docs/content/locales/ar-SA/pure-agent-apps.mdx +2 -2
  5. package/corpus/core/docs/content/locales/de-DE/pure-agent-apps.mdx +2 -2
  6. package/corpus/core/docs/content/locales/es-ES/pure-agent-apps.mdx +2 -2
  7. package/corpus/core/docs/content/locales/fr-FR/pure-agent-apps.mdx +2 -2
  8. package/corpus/core/docs/content/locales/hi-IN/pure-agent-apps.mdx +2 -2
  9. package/corpus/core/docs/content/locales/ja-JP/pure-agent-apps.mdx +2 -2
  10. package/corpus/core/docs/content/locales/ko-KR/pure-agent-apps.mdx +2 -2
  11. package/corpus/core/docs/content/locales/pt-BR/pure-agent-apps.mdx +2 -2
  12. package/corpus/core/docs/content/locales/zh-CN/pure-agent-apps.mdx +2 -2
  13. package/corpus/core/docs/content/locales/zh-TW/pure-agent-apps.mdx +2 -2
  14. package/corpus/core/docs/content/pure-agent-apps.mdx +58 -14
  15. package/corpus/core/package.json +1 -1
  16. package/corpus/core/src/a2a/artifact-response.ts +255 -12
  17. package/corpus/core/src/agent/thread-data-builder.ts +84 -11
  18. package/corpus/core/src/integrations/a2a-continuation-processor.ts +99 -7
  19. package/corpus/core/src/integrations/adapters/discord.ts +3 -1
  20. package/corpus/core/src/integrations/adapters/email.ts +4 -1
  21. package/corpus/core/src/integrations/adapters/google-docs.ts +4 -1
  22. package/corpus/core/src/integrations/adapters/microsoft-teams.ts +3 -1
  23. package/corpus/core/src/integrations/adapters/slack.ts +38 -9
  24. package/corpus/core/src/integrations/adapters/telegram.ts +32 -10
  25. package/corpus/core/src/integrations/adapters/whatsapp.ts +5 -1
  26. package/corpus/core/src/integrations/index.ts +1 -0
  27. package/corpus/core/src/integrations/types.ts +9 -2
  28. package/corpus/core/src/integrations/webhook-handler.ts +69 -45
  29. package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +1 -0
  30. package/corpus/templates/analytics/AGENTS.md +6 -0
  31. package/corpus/templates/analytics/README.md +1 -1
  32. package/corpus/templates/analytics/actions/data-source-status.ts +19 -1
  33. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +5 -1
  34. package/corpus/templates/analytics/app/routes/_index.tsx +2 -2
  35. package/corpus/templates/analytics/changelog/2026-07-14-analytics-chat-guides-you-to-connect-a-data-source-with-a-di.md +6 -0
  36. package/corpus/templates/analytics/changelog/2026-07-14-analytics-landing-pages-now-clearly-position-the-app-as-an-o.md +6 -0
  37. package/corpus/templates/analytics/changelog/2026-07-15-daily-dashboard-emails-now-include-the-complete-dashboard-sc.md +6 -0
  38. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +8 -1
  39. package/corpus/templates/analytics/server/lib/dashboard-report.ts +64 -12
  40. package/corpus/templates/analytics/server/lib/real-data-actions.ts +15 -3
  41. package/corpus/templates/analytics/server/plugins/agent-chat.ts +364 -4
  42. package/corpus/templates/clips/app/components/dictate/vocabulary-section.tsx +1 -0
  43. package/corpus/templates/clips/app/components/library/folder-tree.tsx +12 -9
  44. package/corpus/templates/clips/app/components/library/library-layout.tsx +19 -16
  45. package/corpus/templates/clips/app/routes/_app.spaces._index.tsx +13 -9
  46. package/corpus/templates/clips/changelog/2026-07-15-folders-and-sidebar-links-are-clickable-again-immediately-af.md +6 -0
  47. package/corpus/templates/clips/changelog/2026-07-15-hovering-a-truncated-folder-name-now-shows-the-full-name.md +6 -0
  48. package/corpus/templates/clips/changelog/2026-07-15-space-creation-controls-are-now-shown-only-to-organization-a.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-07-15-vocabulary-terms-can-now-be-deleted-from-the-dictation-dicti.md +6 -0
  50. package/corpus/templates/clips/desktop/src/styles.css +23 -1
  51. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +49 -7
  52. package/corpus/templates/clips/server/lib/recordings.ts +5 -2
  53. package/corpus/templates/content/.agents/skills/content/SKILL.md +31 -0
  54. package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +35 -21
  55. package/corpus/templates/content/AGENTS.md +29 -7
  56. package/corpus/templates/content/actions/_database-source-utils.ts +30 -16
  57. package/corpus/templates/content/actions/_database-utils.ts +15 -1
  58. package/corpus/templates/content/actions/_local-file-documents.ts +53 -1
  59. package/corpus/templates/content/actions/_property-utils.ts +7 -1
  60. package/corpus/templates/content/actions/configure-document-property.ts +11 -0
  61. package/corpus/templates/content/actions/create-content-database.ts +11 -0
  62. package/corpus/templates/content/actions/create-document.ts +9 -0
  63. package/corpus/templates/content/actions/create-inline-content-database.ts +3 -0
  64. package/corpus/templates/content/actions/export-content-source.ts +1 -0
  65. package/corpus/templates/content/actions/get-content-database.ts +13 -1
  66. package/corpus/templates/content/actions/get-document.ts +12 -2
  67. package/corpus/templates/content/actions/import-content-source.ts +6 -0
  68. package/corpus/templates/content/actions/list-documents.ts +3 -0
  69. package/corpus/templates/content/actions/pull-document.ts +2 -0
  70. package/corpus/templates/content/actions/search-documents.ts +5 -1
  71. package/corpus/templates/content/actions/update-document.ts +16 -1
  72. package/corpus/templates/content/actions/view-screen.ts +15 -2
  73. package/corpus/templates/content/app/components/editor/DescriptionField.tsx +121 -0
  74. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +14 -0
  75. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +223 -34
  76. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +56 -8
  77. package/corpus/templates/content/app/i18n/zh-TW.ts +5 -0
  78. package/corpus/templates/content/app/i18n-data.ts +52 -0
  79. package/corpus/templates/content/changelog/2026-07-14-pages-and-databases-can-describe-themselves-with-guidance-fo.md +6 -0
  80. package/corpus/templates/content/changelog/2026-07-14-slack-follow-ups-now-retain-created-content-identity-and-per.md +6 -0
  81. package/corpus/templates/content/server/db/schema.ts +4 -0
  82. package/corpus/templates/content/server/lib/document-context.ts +76 -0
  83. package/corpus/templates/content/server/lib/public-documents.ts +3 -0
  84. package/corpus/templates/content/server/plugins/db.ts +6 -0
  85. package/corpus/templates/content/server/routes/api/document-agent-context.json.get.ts +5 -0
  86. package/corpus/templates/content/shared/api.ts +17 -0
  87. package/corpus/templates/content/shared/content-source.ts +7 -0
  88. package/corpus/templates/content/shared/properties.ts +2 -0
  89. package/dist/a2a/artifact-response.d.ts +15 -0
  90. package/dist/a2a/artifact-response.d.ts.map +1 -1
  91. package/dist/a2a/artifact-response.js +204 -5
  92. package/dist/a2a/artifact-response.js.map +1 -1
  93. package/dist/agent/thread-data-builder.d.ts +6 -0
  94. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  95. package/dist/agent/thread-data-builder.js +71 -8
  96. package/dist/agent/thread-data-builder.js.map +1 -1
  97. package/dist/collab/struct-routes.d.ts +1 -1
  98. package/dist/integrations/a2a-continuation-processor.d.ts.map +1 -1
  99. package/dist/integrations/a2a-continuation-processor.js +72 -6
  100. package/dist/integrations/a2a-continuation-processor.js.map +1 -1
  101. package/dist/integrations/adapters/discord.d.ts.map +1 -1
  102. package/dist/integrations/adapters/discord.js +1 -0
  103. package/dist/integrations/adapters/discord.js.map +1 -1
  104. package/dist/integrations/adapters/email.d.ts.map +1 -1
  105. package/dist/integrations/adapters/email.js +2 -0
  106. package/dist/integrations/adapters/email.js.map +1 -1
  107. package/dist/integrations/adapters/google-docs.d.ts.map +1 -1
  108. package/dist/integrations/adapters/google-docs.js +2 -0
  109. package/dist/integrations/adapters/google-docs.js.map +1 -1
  110. package/dist/integrations/adapters/microsoft-teams.d.ts.map +1 -1
  111. package/dist/integrations/adapters/microsoft-teams.js +1 -0
  112. package/dist/integrations/adapters/microsoft-teams.js.map +1 -1
  113. package/dist/integrations/adapters/slack.d.ts.map +1 -1
  114. package/dist/integrations/adapters/slack.js +21 -6
  115. package/dist/integrations/adapters/slack.js.map +1 -1
  116. package/dist/integrations/adapters/telegram.d.ts.map +1 -1
  117. package/dist/integrations/adapters/telegram.js +11 -2
  118. package/dist/integrations/adapters/telegram.js.map +1 -1
  119. package/dist/integrations/adapters/whatsapp.d.ts.map +1 -1
  120. package/dist/integrations/adapters/whatsapp.js +3 -0
  121. package/dist/integrations/adapters/whatsapp.js.map +1 -1
  122. package/dist/integrations/index.d.ts +1 -1
  123. package/dist/integrations/index.d.ts.map +1 -1
  124. package/dist/integrations/index.js.map +1 -1
  125. package/dist/integrations/types.d.ts +8 -2
  126. package/dist/integrations/types.d.ts.map +1 -1
  127. package/dist/integrations/types.js.map +1 -1
  128. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  129. package/dist/integrations/webhook-handler.js +40 -43
  130. package/dist/integrations/webhook-handler.js.map +1 -1
  131. package/dist/progress/routes.d.ts +1 -1
  132. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  133. package/dist/resources/handlers.d.ts +2 -2
  134. package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
  135. package/dist/server/agent-chat/action-filters-a2a.js +1 -0
  136. package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
  137. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  138. package/docs/content/locales/ar-SA/pure-agent-apps.mdx +2 -2
  139. package/docs/content/locales/de-DE/pure-agent-apps.mdx +2 -2
  140. package/docs/content/locales/es-ES/pure-agent-apps.mdx +2 -2
  141. package/docs/content/locales/fr-FR/pure-agent-apps.mdx +2 -2
  142. package/docs/content/locales/hi-IN/pure-agent-apps.mdx +2 -2
  143. package/docs/content/locales/ja-JP/pure-agent-apps.mdx +2 -2
  144. package/docs/content/locales/ko-KR/pure-agent-apps.mdx +2 -2
  145. package/docs/content/locales/pt-BR/pure-agent-apps.mdx +2 -2
  146. package/docs/content/locales/zh-CN/pure-agent-apps.mdx +2 -2
  147. package/docs/content/locales/zh-TW/pure-agent-apps.mdx +2 -2
  148. package/docs/content/pure-agent-apps.mdx +58 -14
  149. package/package.json +3 -3
@@ -28,6 +28,10 @@ const createContentDatabaseSchema = z.object({
28
28
  .nullish()
29
29
  .describe("Parent document for a new database page"),
30
30
  title: z.string().optional().describe("Database title"),
31
+ description: z
32
+ .string()
33
+ .optional()
34
+ .describe("Stable guidance describing what belongs in this database"),
31
35
  });
32
36
 
33
37
  export default defineAction({
@@ -110,6 +114,12 @@ export async function createContentDatabaseRecord(
110
114
  .set({ title, updatedAt: now })
111
115
  .where(eq(schema.documents.id, documentId));
112
116
  }
117
+ if (args.description !== undefined) {
118
+ await db
119
+ .update(schema.documents)
120
+ .set({ description: args.description.trim(), updatedAt: now })
121
+ .where(eq(schema.documents.id, documentId));
122
+ }
113
123
  } else {
114
124
  title = databaseTitleForPage(title);
115
125
  const parentId = args.parentId || null;
@@ -163,6 +173,7 @@ export async function createContentDatabaseRecord(
163
173
  parentId,
164
174
  title,
165
175
  content: "",
176
+ description: args.description?.trim() ?? "",
166
177
  icon: null,
167
178
  position: (maxPos?.max ?? -1) + 1,
168
179
  isFavorite: 0,
@@ -45,6 +45,12 @@ export default defineAction({
45
45
  .describe("Pre-generated document ID (for optimistic UI)"),
46
46
  title: z.string().describe("Document title"),
47
47
  content: z.string().optional().describe("Markdown content"),
48
+ description: z
49
+ .string()
50
+ .optional()
51
+ .describe(
52
+ "Stable guidance describing why this page exists and what belongs in it",
53
+ ),
48
54
  parentId: z.string().nullish().describe("Parent document ID for nesting"),
49
55
  icon: z.string().optional().describe("Emoji icon"),
50
56
  }),
@@ -78,6 +84,7 @@ export default defineAction({
78
84
  const title = args.title;
79
85
 
80
86
  let content = args.content || "";
87
+ const description = args.description?.trim() ?? "";
81
88
  // Strip leading H1 that duplicates the title
82
89
  if (title && content) {
83
90
  const h1Match = content.match(/^#\s+(.+?)(\r?\n|$)/);
@@ -154,6 +161,7 @@ export default defineAction({
154
161
  parentId,
155
162
  title,
156
163
  content,
164
+ description,
157
165
  icon,
158
166
  position,
159
167
  isFavorite: 0,
@@ -202,6 +210,7 @@ export default defineAction({
202
210
  parentId: doc.parentId,
203
211
  title: doc.title,
204
212
  content: doc.content,
213
+ description: doc.description,
205
214
  icon: doc.icon,
206
215
  position: doc.position,
207
216
  isFavorite: parseDocumentFavorite(doc.isFavorite),
@@ -22,10 +22,12 @@ export default defineAction({
22
22
  schema: z.object({
23
23
  hostDocumentId: z.string().describe("Host page document ID"),
24
24
  title: z.string().optional().describe("Database title"),
25
+ description: z.string().optional().describe("Stable database guidance"),
25
26
  }),
26
27
  run: async ({
27
28
  hostDocumentId,
28
29
  title,
30
+ description,
29
31
  }): Promise<CreateInlineDatabaseResponse> => {
30
32
  const db = getDb();
31
33
  const ownerBlockId = createInlineDatabaseBlockId();
@@ -37,6 +39,7 @@ export default defineAction({
37
39
  {
38
40
  parentId: hostDocumentId,
39
41
  title: databaseTitleForPage(title),
42
+ description,
40
43
  },
41
44
  { db: tx },
42
45
  );
@@ -39,6 +39,7 @@ export default defineAction({
39
39
  parentId: doc.parentId,
40
40
  title: doc.title,
41
41
  content: doc.content,
42
+ description: doc.description,
42
43
  icon: doc.icon,
43
44
  position: doc.position,
44
45
  isFavorite: parseDocumentFavorite(doc.isFavorite),
@@ -11,6 +11,7 @@ import type {
11
11
  import {
12
12
  CONTENT_DATABASE_MAX_READ_LIMIT,
13
13
  getContentDatabaseResponse,
14
+ getDocumentContextPath,
14
15
  } from "./_database-utils.js";
15
16
 
16
17
  export default defineAction({
@@ -78,6 +79,17 @@ export default defineAction({
78
79
  };
79
80
  }
80
81
 
81
- return getContentDatabaseResponse(resolvedDatabaseId, { limit, offset });
82
+ const response = await getContentDatabaseResponse(resolvedDatabaseId, {
83
+ limit,
84
+ offset,
85
+ });
86
+ const [document] = await db
87
+ .select()
88
+ .from(schema.documents)
89
+ .where(eq(schema.documents.id, database.documentId));
90
+ return {
91
+ ...response,
92
+ contextPath: document ? await getDocumentContextPath(document) : [],
93
+ };
82
94
  },
83
95
  });
@@ -9,6 +9,7 @@ import {
9
9
  } from "../server/lib/documents.js";
10
10
  import {
11
11
  getDatabaseByDocumentId,
12
+ getDocumentContextPath,
12
13
  getDatabaseItemByDocumentId,
13
14
  isSoftDeletedDatabaseDocument,
14
15
  serializeDatabaseMembership,
@@ -16,6 +17,7 @@ import {
16
17
  import { serializeDocumentSource } from "./_document-source.js";
17
18
  import {
18
19
  getLocalFileDocument,
20
+ getLocalDocumentContextPath,
19
21
  isLocalDocumentId,
20
22
  isContentLocalFileMode,
21
23
  } from "./_local-file-documents.js";
@@ -45,7 +47,11 @@ export default defineAction({
45
47
  if (!args.id) throw new Error("--id is required");
46
48
 
47
49
  if ((await isContentLocalFileMode()) && isLocalDocumentId(args.id)) {
48
- return getLocalFileDocument(args.id);
50
+ const document = await getLocalFileDocument(args.id);
51
+ return {
52
+ ...document,
53
+ contextPath: await getLocalDocumentContextPath(args.id),
54
+ };
49
55
  }
50
56
 
51
57
  const access = await resolveAccess("document", args.id);
@@ -76,6 +82,7 @@ export default defineAction({
76
82
  parentId: doc.parentId,
77
83
  title: doc.title,
78
84
  content: doc.content,
85
+ description: doc.description,
79
86
  icon: doc.icon,
80
87
  position: doc.position,
81
88
  isFavorite: parseDocumentFavorite(doc.isFavorite),
@@ -85,13 +92,16 @@ export default defineAction({
85
92
  accessRole: access.role,
86
93
  canEdit: canEditRole(access.role),
87
94
  canManage: canManageRole(access.role),
88
- database: database ? serializeDatabase(database) : undefined,
95
+ database: database
96
+ ? serializeDatabase(database, doc.description)
97
+ : undefined,
89
98
  databaseMembership: databaseMembership
90
99
  ? serializeDatabaseMembership(databaseMembership)
91
100
  : undefined,
92
101
  createdAt: doc.createdAt,
93
102
  updatedAt: doc.updatedAt,
94
103
  properties: await listPropertiesForDocument(doc),
104
+ contextPath: await getDocumentContextPath(doc),
95
105
  };
96
106
  },
97
107
  link: ({ result }) => {
@@ -282,6 +282,9 @@ export default defineAction({
282
282
  }
283
283
  const titleChanged = file.title !== existing.title;
284
284
  const contentChanged = file.content !== existing.content;
285
+ const descriptionChanged =
286
+ file.description !== undefined &&
287
+ file.description !== existing.description;
285
288
  const iconChanged =
286
289
  file.icon !== undefined && file.icon !== existing.icon;
287
290
  const favoriteChanged =
@@ -299,6 +302,7 @@ export default defineAction({
299
302
  const anyChange =
300
303
  titleChanged ||
301
304
  contentChanged ||
305
+ descriptionChanged ||
302
306
  iconChanged ||
303
307
  favoriteChanged ||
304
308
  discoverabilityChanged ||
@@ -322,6 +326,7 @@ export default defineAction({
322
326
  const updates: Record<string, unknown> = { updatedAt: now };
323
327
  if (titleChanged) updates.title = file.title;
324
328
  if (contentChanged) updates.content = file.content;
329
+ if (descriptionChanged) updates.description = file.description;
325
330
  if (iconChanged) updates.icon = file.icon ?? null;
326
331
  if (favoriteChanged) updates.isFavorite = boolToInt(file.isFavorite);
327
332
  if (discoverabilityChanged) {
@@ -347,6 +352,7 @@ export default defineAction({
347
352
  orgId: currentOrgId,
348
353
  parentId: null,
349
354
  title: file.title,
355
+ description: file.description ?? "",
350
356
  content: file.content,
351
357
  icon: file.icon ?? null,
352
358
  position: file.position ?? index,
@@ -70,6 +70,7 @@ export default defineAction({
70
70
  id: schema.documents.id,
71
71
  parentId: schema.documents.parentId,
72
72
  title: schema.documents.title,
73
+ description: schema.documents.description,
73
74
  contentSnippet: sql<string>`substr(${schema.documents.content}, 1, 400)`,
74
75
  contentLength: sql<number>`length(${schema.documents.content})`,
75
76
  icon: schema.documents.icon,
@@ -282,6 +283,7 @@ export default defineAction({
282
283
  id: d.id,
283
284
  parentId: d.parentId,
284
285
  title: d.title,
286
+ description: d.description,
285
287
  contentPreview: contentPreview(d.contentSnippet),
286
288
  contentLength: Number(d.contentLength) || 0,
287
289
  icon: d.icon,
@@ -299,6 +301,7 @@ export default defineAction({
299
301
  id: database.id,
300
302
  documentId: database.documentId,
301
303
  title: database.title,
304
+ description: d.description,
302
305
  viewConfig: parseDatabaseViewConfig(database.viewConfigJson),
303
306
  createdAt: database.createdAt,
304
307
  updatedAt: database.updatedAt,
@@ -73,6 +73,7 @@ export default defineAction({
73
73
  return {
74
74
  id: doc.id,
75
75
  title: doc.title,
76
+ description: doc.description ?? "",
76
77
  content: formatDocumentContent(doc.content ?? "", format),
77
78
  format,
78
79
  deepLink: buildDeepLink({
@@ -104,6 +105,7 @@ export default defineAction({
104
105
  return {
105
106
  id: doc.id,
106
107
  title: doc.title,
108
+ description: (doc.description as string | null | undefined) ?? "",
107
109
  content,
108
110
  format,
109
111
  deepLink: buildDeepLink({
@@ -59,6 +59,7 @@ export default defineAction({
59
59
  (doc) =>
60
60
  !normalizedQuery ||
61
61
  doc.title.toLowerCase().includes(normalizedQuery) ||
62
+ (doc.description ?? "").toLowerCase().includes(normalizedQuery) ||
62
63
  doc.content.toLowerCase().includes(normalizedQuery),
63
64
  )
64
65
  .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))
@@ -69,6 +70,7 @@ export default defineAction({
69
70
  id: doc.id,
70
71
  parentId: doc.parentId,
71
72
  title: doc.title,
73
+ description: doc.description,
72
74
  icon: doc.icon,
73
75
  snippet: makeSnippet(doc.content, query),
74
76
  contentLength: doc.content.length,
@@ -95,6 +97,7 @@ export default defineAction({
95
97
  id: schema.documents.id,
96
98
  parentId: schema.documents.parentId,
97
99
  title: schema.documents.title,
100
+ description: schema.documents.description,
98
101
  icon: schema.documents.icon,
99
102
  contentPreview: sql<string>`substr(${schema.documents.content}, 1, 5000)`,
100
103
  contentLength: sql<number>`length(${schema.documents.content})`,
@@ -106,7 +109,7 @@ export default defineAction({
106
109
  and(
107
110
  accessFilter(schema.documents, schema.documentShares),
108
111
  documentDiscoveryFilter(),
109
- sql`(${schema.documents.title} LIKE ${pattern} ESCAPE '\\' OR ${schema.documents.content} LIKE ${pattern} ESCAPE '\\')`,
112
+ sql`(${schema.documents.title} LIKE ${pattern} ESCAPE '\\' OR ${schema.documents.description} LIKE ${pattern} ESCAPE '\\' OR ${schema.documents.content} LIKE ${pattern} ESCAPE '\\')`,
110
113
  ),
111
114
  )
112
115
  .orderBy(sql`${schema.documents.updatedAt} DESC`)
@@ -117,6 +120,7 @@ export default defineAction({
117
120
  id: doc.id,
118
121
  parentId: doc.parentId,
119
122
  title: doc.title,
123
+ description: doc.description,
120
124
  icon: doc.icon,
121
125
  snippet: makeSnippet(doc.contentPreview, query),
122
126
  contentLength: Number(doc.contentLength) || 0,
@@ -152,6 +152,10 @@ export default defineAction({
152
152
  id: z.string().optional().describe("Document ID (required)"),
153
153
  title: z.string().optional().describe("New title"),
154
154
  content: z.string().optional().describe("New markdown content"),
155
+ description: z
156
+ .string()
157
+ .optional()
158
+ .describe("Stable page guidance; this does not alter page content"),
155
159
  icon: z.string().nullable().optional().describe("New emoji icon"),
156
160
  isFavorite: z.coerce
157
161
  .boolean()
@@ -285,8 +289,15 @@ export default defineAction({
285
289
  const favoriteChanged =
286
290
  args.isFavorite !== undefined &&
287
291
  (args.isFavorite ? 1 : 0) !== (existing.isFavorite ?? 0);
292
+ const descriptionChanged =
293
+ args.description !== undefined &&
294
+ args.description.trim() !== existing.description;
288
295
  const anyChange =
289
- titleChanged || contentChanged || iconChanged || favoriteChanged;
296
+ titleChanged ||
297
+ contentChanged ||
298
+ iconChanged ||
299
+ favoriteChanged ||
300
+ descriptionChanged;
290
301
 
291
302
  // Snapshot the current state before applying content/title changes.
292
303
  // Versions are scoped to the document owner, not the caller — an editor
@@ -338,6 +349,8 @@ export default defineAction({
338
349
  };
339
350
 
340
351
  if (titleChanged) updates.title = args.title;
352
+ if (args.description !== undefined)
353
+ updates.description = args.description.trim();
341
354
  if (contentChanged) updates.content = content;
342
355
  if (iconChanged) updates.icon = args.icon;
343
356
  if (favoriteChanged) updates.isFavorite = args.isFavorite ? 1 : 0;
@@ -373,6 +386,7 @@ export default defineAction({
373
386
  parentId: current.parentId,
374
387
  title: current.title,
375
388
  content: current.content,
389
+ description: current.description,
376
390
  icon: current.icon,
377
391
  position: current.position,
378
392
  isFavorite: parseDocumentFavorite(current.isFavorite),
@@ -449,6 +463,7 @@ export default defineAction({
449
463
  parentId: doc.parentId,
450
464
  title: doc.title,
451
465
  content: doc.content,
466
+ description: doc.description,
452
467
  icon: doc.icon,
453
468
  position: doc.position,
454
469
  isFavorite: parseDocumentFavorite(doc.isFavorite),
@@ -34,11 +34,13 @@ import {
34
34
  getContentDatabaseResponse,
35
35
  getDatabaseByDocumentId,
36
36
  getDatabaseItemByDocumentId,
37
+ getDocumentContextPath,
37
38
  serializeDatabaseMembership,
38
39
  } from "./_database-utils.js";
39
40
  import { serializeDocumentSource } from "./_document-source.js";
40
41
  import {
41
42
  getLocalFileDocument,
43
+ getLocalDocumentContextPath,
42
44
  isContentLocalFileMode,
43
45
  isLocalDocumentId,
44
46
  localContentViewScreenSummary,
@@ -675,7 +677,10 @@ export default defineAction({
675
677
  ],
676
678
  };
677
679
  if (nav?.documentId && isLocalDocumentId(nav.documentId)) {
678
- screen.document = await getLocalFileDocument(nav.documentId);
680
+ screen.document = {
681
+ ...(await getLocalFileDocument(nav.documentId)),
682
+ contextPath: await getLocalDocumentContextPath(nav.documentId),
683
+ };
679
684
  } else if (nav?.documentId) {
680
685
  const access = await resolveAccess("document", nav.documentId);
681
686
  if (access) {
@@ -685,6 +690,7 @@ export default defineAction({
685
690
  parentId: doc.parentId,
686
691
  title: doc.title,
687
692
  content: doc.content,
693
+ description: doc.description,
688
694
  icon: doc.icon,
689
695
  position: doc.position,
690
696
  isFavorite: parseDocumentFavorite(doc.isFavorite),
@@ -693,6 +699,7 @@ export default defineAction({
693
699
  source: serializeDocumentSource(doc),
694
700
  createdAt: doc.createdAt,
695
701
  updatedAt: doc.updatedAt,
702
+ contextPath: await getDocumentContextPath(doc),
696
703
  };
697
704
  }
698
705
  }
@@ -725,18 +732,22 @@ export default defineAction({
725
732
  parentId: doc.parentId,
726
733
  title: doc.title,
727
734
  content: doc.content,
735
+ description: doc.description,
728
736
  icon: doc.icon,
729
737
  position: doc.position,
730
738
  isFavorite: parseDocumentFavorite(doc.isFavorite),
731
739
  hideFromSearch: parseDocumentHideFromSearch(doc.hideFromSearch),
732
740
  visibility: doc.visibility,
733
- database: database ? serializeDatabase(database) : undefined,
741
+ database: database
742
+ ? serializeDatabase(database, doc.description)
743
+ : undefined,
734
744
  databaseMembership: databaseMembership
735
745
  ? serializeDatabaseMembership(databaseMembership)
736
746
  : undefined,
737
747
  properties: await listPropertiesForDocument(doc),
738
748
  createdAt: doc.createdAt,
739
749
  updatedAt: doc.updatedAt,
750
+ contextPath: await getDocumentContextPath(doc),
740
751
  };
741
752
  if (database) {
742
753
  const databaseResponse = await getContentDatabaseResponse(
@@ -774,6 +785,7 @@ export default defineAction({
774
785
  parentId: previewDoc.parentId,
775
786
  title: previewDoc.title,
776
787
  content: previewDoc.content,
788
+ description: previewDoc.description,
777
789
  icon: previewDoc.icon,
778
790
  position: previewDoc.position,
779
791
  isFavorite: parseDocumentFavorite(previewDoc.isFavorite),
@@ -786,6 +798,7 @@ export default defineAction({
786
798
  properties: await listPropertiesForDocument(previewDoc),
787
799
  createdAt: previewDoc.createdAt,
788
800
  updatedAt: previewDoc.updatedAt,
801
+ contextPath: await getDocumentContextPath(previewDoc),
789
802
  },
790
803
  };
791
804
  }
@@ -0,0 +1,121 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+
3
+ import { Textarea } from "@/components/ui/textarea";
4
+ import { cn } from "@/lib/utils";
5
+
6
+ export function descriptionFieldSavedValue(
7
+ draft: string,
8
+ savedDescription: string | null | undefined,
9
+ ) {
10
+ const next = draft.trim();
11
+ return next === (savedDescription ?? "") ? null : next;
12
+ }
13
+
14
+ export function descriptionFieldEscapeDraft(
15
+ savedDescription: string | null | undefined,
16
+ ) {
17
+ return savedDescription ?? "";
18
+ }
19
+
20
+ /**
21
+ * Quiet, stable guidance attached to a page or database. This intentionally
22
+ * renders the owned description only; ancestor context is assembled for agent
23
+ * reads rather than copied into the surface.
24
+ */
25
+ export function DescriptionField({
26
+ description,
27
+ canEdit,
28
+ label = "Description",
29
+ placeholder = "Add a description…",
30
+ className,
31
+ onSave,
32
+ }: {
33
+ description: string | null | undefined;
34
+ canEdit: boolean;
35
+ label?: string;
36
+ placeholder?: string;
37
+ className?: string;
38
+ onSave: (description: string) => Promise<unknown> | unknown;
39
+ }) {
40
+ const [draft, setDraft] = useState(description ?? "");
41
+ const [editing, setEditing] = useState(false);
42
+ const skipNextBlurSaveRef = useRef(false);
43
+ const saveQueueRef = useRef<Promise<void>>(Promise.resolve());
44
+ const saveRevisionRef = useRef(0);
45
+ const confirmedDescriptionRef = useRef(description ?? "");
46
+
47
+ useEffect(() => {
48
+ confirmedDescriptionRef.current = description ?? "";
49
+ }, [description]);
50
+
51
+ useEffect(() => {
52
+ if (!editing) setDraft(description ?? "");
53
+ }, [description, editing]);
54
+
55
+ const save = async () => {
56
+ const next = descriptionFieldSavedValue(draft, description);
57
+ if (next === null) return;
58
+
59
+ const revision = ++saveRevisionRef.current;
60
+ const request = saveQueueRef.current.then(() => onSave(next));
61
+ saveQueueRef.current = request.then(
62
+ () => undefined,
63
+ () => undefined,
64
+ );
65
+
66
+ try {
67
+ await request;
68
+ confirmedDescriptionRef.current = next;
69
+ } catch {
70
+ if (saveRevisionRef.current === revision) {
71
+ setDraft(confirmedDescriptionRef.current);
72
+ }
73
+ }
74
+ };
75
+
76
+ if (!canEdit && !description) return null;
77
+
78
+ if (!canEdit) {
79
+ return (
80
+ <p
81
+ className={cn(
82
+ "mt-2 text-sm leading-6 text-muted-foreground",
83
+ className,
84
+ )}
85
+ >
86
+ {description}
87
+ </p>
88
+ );
89
+ }
90
+
91
+ return (
92
+ <Textarea
93
+ aria-label={label}
94
+ rows={editing || draft ? 2 : 1}
95
+ value={draft}
96
+ placeholder={placeholder}
97
+ onFocus={() => setEditing(true)}
98
+ onChange={(event) => setDraft(event.target.value)}
99
+ onBlur={() => {
100
+ setEditing(false);
101
+ if (skipNextBlurSaveRef.current) {
102
+ skipNextBlurSaveRef.current = false;
103
+ return;
104
+ }
105
+ void save();
106
+ }}
107
+ onKeyDown={(event) => {
108
+ if (event.key === "Escape") {
109
+ event.preventDefault();
110
+ skipNextBlurSaveRef.current = true;
111
+ setDraft(descriptionFieldEscapeDraft(description));
112
+ event.currentTarget.blur();
113
+ }
114
+ }}
115
+ className={cn(
116
+ "mt-2 block w-full resize-none overflow-hidden rounded border-0 bg-transparent px-0 py-0 text-sm leading-6 text-muted-foreground outline-none placeholder:text-muted-foreground/45 hover:bg-muted/30 focus:bg-muted/30 focus:px-1 focus:outline-none focus:ring-1 focus:ring-ring",
117
+ className,
118
+ )}
119
+ />
120
+ );
121
+ }
@@ -61,6 +61,7 @@ import {
61
61
  import { BuilderBodySyncingNotice } from "./BuilderBodySyncingNotice";
62
62
  import type { CommentTextAnchor } from "./comment-anchors";
63
63
  import { CommentsSidebar } from "./CommentsSidebar";
64
+ import { DescriptionField } from "./DescriptionField";
64
65
  import { DocumentBlockFields } from "./DocumentBlockFields";
65
66
  import { DocumentDatabase } from "./DocumentDatabase";
66
67
  import { DocumentEditorSkeleton } from "./DocumentEditorSkeleton";
@@ -629,6 +630,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
629
630
  updates: {
630
631
  title?: string;
631
632
  content?: string;
633
+ description?: string;
632
634
  icon?: string | null;
633
635
  },
634
636
  options: DocumentSaveOptions = {},
@@ -645,6 +647,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
645
647
  ...document,
646
648
  title: updates.title ?? localTitleRef.current,
647
649
  content: updates.content ?? localContentRef.current,
650
+ description: updates.description ?? document.description,
648
651
  icon: updates.icon !== undefined ? updates.icon : document.icon,
649
652
  updatedAt: nextSavedAt,
650
653
  source: localSource,
@@ -1431,6 +1434,17 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
1431
1434
  isDatabasePage ? "text-3xl" : "text-3xl md:text-4xl",
1432
1435
  )}
1433
1436
  />
1437
+ {!document.database ? (
1438
+ <DescriptionField
1439
+ description={document.description}
1440
+ canEdit={editorCanEdit}
1441
+ label={t("editor.properties.description")}
1442
+ placeholder={t("editor.properties.addPageDescription")}
1443
+ onSave={(description) =>
1444
+ persistDocumentUpdates({ description })
1445
+ }
1446
+ />
1447
+ ) : null}
1434
1448
  {document.databaseMembership && !isLocalFileDocument ? (
1435
1449
  <DocumentProperties
1436
1450
  documentId={documentId}