@agent-native/core 0.91.1 → 0.91.2

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 (209) hide show
  1. package/blueprints/action/crud.md +1 -1
  2. package/blueprints/channel/discord.md +1 -1
  3. package/blueprints/provider/stripe.md +1 -1
  4. package/blueprints/sandbox/docker.md +1 -1
  5. package/corpus/README.md +1 -1
  6. package/corpus/core/CHANGELOG.md +14 -0
  7. package/corpus/core/blueprints/action/crud.md +1 -1
  8. package/corpus/core/blueprints/channel/discord.md +1 -1
  9. package/corpus/core/blueprints/provider/stripe.md +1 -1
  10. package/corpus/core/blueprints/sandbox/docker.md +1 -1
  11. package/corpus/core/package.json +6 -5
  12. package/corpus/core/src/agent/run-manager.ts +23 -2
  13. package/corpus/core/src/agent/run-store.ts +2 -2
  14. package/corpus/core/src/cli/add.ts +1 -1
  15. package/corpus/core/src/cli/create.ts +8 -3
  16. package/corpus/core/src/cli/index.ts +5 -4
  17. package/corpus/core/src/client/extensions/ExtensionSlot.tsx +7 -0
  18. package/corpus/core/src/client/session-replay.ts +97 -3
  19. package/corpus/core/src/jobs/scheduler.ts +30 -1
  20. package/corpus/core/src/localization/default-messages.ts +21 -0
  21. package/corpus/core/src/templates/default/app/i18n/ar-SA.ts +21 -0
  22. package/corpus/core/src/templates/default/app/i18n/de-DE.ts +21 -0
  23. package/corpus/core/src/templates/default/app/i18n/en-US.ts +21 -0
  24. package/corpus/core/src/templates/default/app/i18n/es-ES.ts +21 -0
  25. package/corpus/core/src/templates/default/app/i18n/fr-FR.ts +21 -0
  26. package/corpus/core/src/templates/default/app/i18n/hi-IN.ts +20 -0
  27. package/corpus/core/src/templates/default/app/i18n/ja-JP.ts +21 -0
  28. package/corpus/core/src/templates/default/app/i18n/ko-KR.ts +20 -0
  29. package/corpus/core/src/templates/default/app/i18n/pt-BR.ts +21 -0
  30. package/corpus/core/src/templates/default/app/i18n/zh-CN.ts +20 -0
  31. package/corpus/core/src/templates/default/app/i18n/zh-TW.ts +19 -0
  32. package/corpus/core/src/templates/default/package.json +2 -2
  33. package/corpus/core/src/templates/headless/package.json +3 -3
  34. package/corpus/core/src/templates/workspace-core/package.json +6 -6
  35. package/corpus/core/src/templates/workspace-root/package.json +2 -2
  36. package/corpus/core/src/triggers/dispatcher.ts +30 -2
  37. package/corpus/core/src/vite/client.ts +96 -1
  38. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +21 -2
  39. package/corpus/templates/analytics/app/global.css +26 -10
  40. package/corpus/templates/analytics/app/i18n/zh-TW.ts +2 -0
  41. package/corpus/templates/analytics/app/i18n-data.ts +25 -0
  42. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +548 -237
  43. package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +285 -294
  44. package/corpus/templates/analytics/changelog/2026-07-08-console-details-expand-inline-under-each-row-and-the-event-t.md +6 -0
  45. package/corpus/templates/analytics/changelog/2026-07-08-session-replay-clamps-ultra-wide-viewports-while-keeping-the.md +6 -0
  46. package/corpus/templates/analytics/changelog/2026-07-08-session-replay-keeps-a-usable-stage-when-dev-tools-is-open-a.md +6 -0
  47. package/corpus/templates/analytics/changelog/2026-07-08-session-replay-resumes-after-scrubbing-and-expands-console-r.md +6 -0
  48. package/corpus/templates/analytics/changelog/2026-07-08-session-replays-wait-for-full-load-keep-a-normal-viewport-an.md +6 -0
  49. package/corpus/templates/analytics/package.json +2 -2
  50. package/corpus/templates/assets/package.json +2 -2
  51. package/corpus/templates/brain/package.json +2 -2
  52. package/corpus/templates/calendar/AGENTS.md +10 -0
  53. package/corpus/templates/calendar/actions/create-event.ts +5 -1
  54. package/corpus/templates/calendar/actions/event-action-helpers.ts +41 -0
  55. package/corpus/templates/calendar/actions/get-attendee-timezones.ts +28 -0
  56. package/corpus/templates/calendar/actions/set-attendee-timezone.ts +68 -0
  57. package/corpus/templates/calendar/app/components/calendar/EventAttendeesSection.tsx +150 -11
  58. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +36 -1
  59. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +32 -0
  60. package/corpus/templates/calendar/app/hooks/use-attendee-timezones.ts +24 -0
  61. package/corpus/templates/calendar/app/i18n/zh-TW.ts +2 -0
  62. package/corpus/templates/calendar/app/i18n-data.ts +20 -0
  63. package/corpus/templates/calendar/app/lib/attendee-local-time.ts +122 -0
  64. package/corpus/templates/calendar/changelog/2026-07-08-event-details-now-support-extension-widgets-and-you-can-see-.md +6 -0
  65. package/corpus/templates/calendar/changelog/2026-07-08-when-you-invite-guests-to-a-new-event-you-now-appear-in.md +6 -0
  66. package/corpus/templates/calendar/package.json +2 -2
  67. package/corpus/templates/calendar/server/handlers/events.ts +5 -1
  68. package/corpus/templates/calendar/shared/api.ts +6 -0
  69. package/corpus/templates/chat/package.json +2 -2
  70. package/corpus/templates/clips/.agents/skills/meetings/SKILL.md +6 -2
  71. package/corpus/templates/clips/actions/create-meeting.ts +8 -3
  72. package/corpus/templates/clips/actions/list-meetings.ts +31 -3
  73. package/corpus/templates/clips/changelog/2026-07-08-after-finishing-a-desktop-recording-reopening-clips-restor.md +6 -0
  74. package/corpus/templates/clips/changelog/2026-07-08-clips-now-pops-up-to-take-notes-when-you-join-an-adhoc-zoom-.md +6 -0
  75. package/corpus/templates/clips/changelog/2026-07-08-desktop-clip-recordings-are-louder-after-capture-mic-only.md +6 -0
  76. package/corpus/templates/clips/changelog/2026-07-08-meeting-reminders-now-appear-1-minute-before-start-stay-unti.md +6 -0
  77. package/corpus/templates/clips/changelog/2026-07-08-the-desktop-meetings-panel-has-room-for-its-drop-shadow-and-.md +6 -0
  78. package/corpus/templates/clips/chrome-extension/package.json +2 -2
  79. package/corpus/templates/clips/desktop/package.json +2 -2
  80. package/corpus/templates/clips/desktop/src/app.tsx +91 -34
  81. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +15 -5
  82. package/corpus/templates/clips/desktop/src/lib/meeting-notification-timing.ts +83 -0
  83. package/corpus/templates/clips/desktop/src/lib/native-recording-warm.ts +21 -0
  84. package/corpus/templates/clips/desktop/src/lib/recorder.ts +98 -20
  85. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +174 -50
  86. package/corpus/templates/clips/desktop/src/styles.css +128 -53
  87. package/corpus/templates/clips/desktop/src-tauri/src/adhoc_meetings_watcher.rs +364 -0
  88. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +1 -1
  89. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +5 -0
  90. package/corpus/templates/clips/desktop/src-tauri/src/meetings_watcher.rs +91 -11
  91. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +69 -19
  92. package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +36 -1
  93. package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +62 -10
  94. package/corpus/templates/clips/desktop/src-tauri/src/silence_detector.rs +1 -24
  95. package/corpus/templates/clips/desktop/src-tauri/src/util.rs +36 -1
  96. package/corpus/templates/clips/package.json +2 -2
  97. package/corpus/templates/content/actions/_content-database-personal-view.ts +80 -0
  98. package/corpus/templates/content/actions/get-content-database-personal-view.ts +27 -0
  99. package/corpus/templates/content/actions/update-content-database-personal-view.ts +31 -0
  100. package/corpus/templates/content/actions/update-content-database-view.ts +3 -0
  101. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +1 -1
  102. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2584 -546
  103. package/corpus/templates/content/app/components/editor/database/filter-sort.ts +220 -18
  104. package/corpus/templates/content/app/hooks/use-content-database.ts +32 -0
  105. package/corpus/templates/content/app/i18n/zh-TW.ts +16 -0
  106. package/corpus/templates/content/app/i18n-data.ts +154 -0
  107. package/corpus/templates/content/changelog/2026-07-08-database-filters-now-support-notion-style-reset-save-for-eve.md +6 -0
  108. package/corpus/templates/content/changelog/2026-07-08-date-filters-can-now-target-a-between-range.md +6 -0
  109. package/corpus/templates/content/package.json +2 -2
  110. package/corpus/templates/content/parity/matrix.md +1 -1
  111. package/corpus/templates/content/parity/matrix.ts +2 -0
  112. package/corpus/templates/content/shared/api.ts +24 -0
  113. package/corpus/templates/design/package.json +2 -2
  114. package/corpus/templates/dispatch/app/i18n/zh-TW.ts +34 -1
  115. package/corpus/templates/dispatch/app/i18n-data.ts +387 -10
  116. package/corpus/templates/dispatch/app/routes/automations.tsx +1 -0
  117. package/corpus/templates/dispatch/changelog/2026-07-08-added-an-automations-page-to-review-pause-and-create.md +6 -0
  118. package/corpus/templates/dispatch/changelog/2026-07-08-destinations-now-shows-outbound-delivery-queue-health.md +6 -0
  119. package/corpus/templates/dispatch/package.json +2 -2
  120. package/corpus/templates/forms/package.json +2 -2
  121. package/corpus/templates/macros/package.json +2 -2
  122. package/corpus/templates/mail/app/hooks/use-emails.ts +56 -44
  123. package/corpus/templates/mail/app/lib/gmail-mutation-queue.ts +325 -0
  124. package/corpus/templates/mail/changelog/2026-07-08-rapid-archive-and-mark-read-now-batch-gmail-updates-so-the-i.md +6 -0
  125. package/corpus/templates/mail/package.json +2 -2
  126. package/corpus/templates/plan/package.json +2 -2
  127. package/corpus/templates/slides/app/components/editor/ImageDropPromptPopover.tsx +46 -29
  128. package/corpus/templates/slides/app/context/DeckContext.tsx +45 -18
  129. package/corpus/templates/slides/app/i18n/ar-SA.ts +4 -0
  130. package/corpus/templates/slides/app/i18n/de-DE.ts +4 -0
  131. package/corpus/templates/slides/app/i18n/en-US.ts +4 -0
  132. package/corpus/templates/slides/app/i18n/es-ES.ts +4 -0
  133. package/corpus/templates/slides/app/i18n/fr-FR.ts +4 -0
  134. package/corpus/templates/slides/app/i18n/hi-IN.ts +4 -0
  135. package/corpus/templates/slides/app/i18n/ja-JP.ts +4 -0
  136. package/corpus/templates/slides/app/i18n/ko-KR.ts +4 -0
  137. package/corpus/templates/slides/app/i18n/pt-BR.ts +4 -0
  138. package/corpus/templates/slides/app/i18n/zh-CN.ts +4 -0
  139. package/corpus/templates/slides/app/i18n/zh-TW.ts +4 -0
  140. package/corpus/templates/slides/app/lib/image-drop-to-agent.ts +115 -0
  141. package/corpus/templates/slides/app/pages/DeckEditor.tsx +22 -13
  142. package/corpus/templates/slides/changelog/2026-07-08-dropping-an-image-onto-a-slide-and-sending-it-to-the-agent-w.md +6 -0
  143. package/corpus/templates/slides/changelog/2026-07-08-undo-works-after-the-agent-edits-a-slide-so-you-can-reverse.md +6 -0
  144. package/corpus/templates/slides/package.json +2 -2
  145. package/dist/agent/run-manager.d.ts.map +1 -1
  146. package/dist/agent/run-manager.js +23 -2
  147. package/dist/agent/run-manager.js.map +1 -1
  148. package/dist/agent/run-store.js +2 -2
  149. package/dist/agent/run-store.js.map +1 -1
  150. package/dist/catalog.json +2 -2
  151. package/dist/cli/add.js +1 -1
  152. package/dist/cli/add.js.map +1 -1
  153. package/dist/cli/create.d.ts.map +1 -1
  154. package/dist/cli/create.js +8 -3
  155. package/dist/cli/create.js.map +1 -1
  156. package/dist/cli/index.js +5 -4
  157. package/dist/cli/index.js.map +1 -1
  158. package/dist/client/extensions/ExtensionSlot.js +6 -0
  159. package/dist/client/extensions/ExtensionSlot.js.map +1 -1
  160. package/dist/client/session-replay.d.ts +8 -0
  161. package/dist/client/session-replay.d.ts.map +1 -1
  162. package/dist/client/session-replay.js +80 -3
  163. package/dist/client/session-replay.js.map +1 -1
  164. package/dist/collab/struct-routes.d.ts +1 -1
  165. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  166. package/dist/jobs/scheduler.js +26 -1
  167. package/dist/jobs/scheduler.js.map +1 -1
  168. package/dist/localization/default-messages.d.ts +19 -0
  169. package/dist/localization/default-messages.d.ts.map +1 -1
  170. package/dist/localization/default-messages.js +19 -0
  171. package/dist/localization/default-messages.js.map +1 -1
  172. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  173. package/dist/resources/handlers.d.ts +2 -2
  174. package/dist/templates/default/app/i18n/ar-SA.ts +21 -0
  175. package/dist/templates/default/app/i18n/de-DE.ts +21 -0
  176. package/dist/templates/default/app/i18n/en-US.ts +21 -0
  177. package/dist/templates/default/app/i18n/es-ES.ts +21 -0
  178. package/dist/templates/default/app/i18n/fr-FR.ts +21 -0
  179. package/dist/templates/default/app/i18n/hi-IN.ts +20 -0
  180. package/dist/templates/default/app/i18n/ja-JP.ts +21 -0
  181. package/dist/templates/default/app/i18n/ko-KR.ts +20 -0
  182. package/dist/templates/default/app/i18n/pt-BR.ts +21 -0
  183. package/dist/templates/default/app/i18n/zh-CN.ts +20 -0
  184. package/dist/templates/default/app/i18n/zh-TW.ts +19 -0
  185. package/dist/templates/default/package.json +2 -2
  186. package/dist/templates/headless/package.json +3 -3
  187. package/dist/templates/workspace-core/package.json +6 -6
  188. package/dist/templates/workspace-root/package.json +2 -2
  189. package/dist/triggers/dispatcher.js +27 -2
  190. package/dist/triggers/dispatcher.js.map +1 -1
  191. package/dist/vite/client.d.ts.map +1 -1
  192. package/dist/vite/client.js +70 -1
  193. package/dist/vite/client.js.map +1 -1
  194. package/package.json +7 -6
  195. package/src/templates/default/app/i18n/ar-SA.ts +21 -0
  196. package/src/templates/default/app/i18n/de-DE.ts +21 -0
  197. package/src/templates/default/app/i18n/en-US.ts +21 -0
  198. package/src/templates/default/app/i18n/es-ES.ts +21 -0
  199. package/src/templates/default/app/i18n/fr-FR.ts +21 -0
  200. package/src/templates/default/app/i18n/hi-IN.ts +20 -0
  201. package/src/templates/default/app/i18n/ja-JP.ts +21 -0
  202. package/src/templates/default/app/i18n/ko-KR.ts +20 -0
  203. package/src/templates/default/app/i18n/pt-BR.ts +21 -0
  204. package/src/templates/default/app/i18n/zh-CN.ts +20 -0
  205. package/src/templates/default/app/i18n/zh-TW.ts +19 -0
  206. package/src/templates/default/package.json +2 -2
  207. package/src/templates/headless/package.json +3 -3
  208. package/src/templates/workspace-core/package.json +6 -6
  209. package/src/templates/workspace-root/package.json +2 -2
@@ -53,13 +53,12 @@ export function applyDatabaseView(
53
53
  const activeFilters = filters.filter(isActiveFilter);
54
54
  const filtered = activeFilters.length
55
55
  ? searched.filter((item) =>
56
- filterMode === "or"
57
- ? activeFilters.some((filter) =>
58
- databaseItemMatchesFilter(item, properties, filter),
59
- )
60
- : activeFilters.every((filter) =>
61
- databaseItemMatchesFilter(item, properties, filter),
62
- ),
56
+ databaseItemMatchesFilterTree(
57
+ item,
58
+ properties,
59
+ activeFilters,
60
+ filterMode,
61
+ ),
63
62
  )
64
63
  : searched;
65
64
 
@@ -79,6 +78,53 @@ export function applyDatabaseView(
79
78
  });
80
79
  }
81
80
 
81
+ function databaseItemMatchesFilterTree(
82
+ item: ContentDatabaseItem,
83
+ properties: DocumentProperty[],
84
+ filters: DatabaseFilter[],
85
+ filterMode: DatabaseFilterMode,
86
+ ) {
87
+ const rootFilters = filters.filter((filter) => !filter.parentFilterGroupId);
88
+ const nestedGroups = nestedDatabaseFilterGroups(filters);
89
+ const matches = [
90
+ ...rootFilters.map((filter) =>
91
+ databaseItemMatchesFilter(item, properties, filter),
92
+ ),
93
+ ...nestedGroups.map((group) =>
94
+ combineDatabaseFilterMatches(
95
+ group.map((filter) =>
96
+ databaseItemMatchesFilter(item, properties, filter),
97
+ ),
98
+ filterMode,
99
+ ),
100
+ ),
101
+ ];
102
+
103
+ return combineDatabaseFilterMatches(matches, filterMode);
104
+ }
105
+
106
+ function nestedDatabaseFilterGroups(filters: DatabaseFilter[]) {
107
+ const groups = new Map<string, DatabaseFilter[]>();
108
+ for (const filter of filters) {
109
+ if (!filter.parentFilterGroupId || !filter.filterGroupId) continue;
110
+ groups.set(filter.filterGroupId, [
111
+ ...(groups.get(filter.filterGroupId) ?? []),
112
+ filter,
113
+ ]);
114
+ }
115
+ return [...groups.values()].filter((group) => group.length > 0);
116
+ }
117
+
118
+ function combineDatabaseFilterMatches(
119
+ matches: boolean[],
120
+ filterMode: DatabaseFilterMode,
121
+ ) {
122
+ if (matches.length === 0) return true;
123
+ return filterMode === "or"
124
+ ? matches.some((matched) => matched)
125
+ : matches.every((matched) => matched);
126
+ }
127
+
82
128
  export function defaultDatabaseSort(): DatabaseSort {
83
129
  return {
84
130
  key: "name",
@@ -284,15 +330,27 @@ function databaseFilterDefaultValueForNewItem(
284
330
  if (filter.operator === "is_unchecked") {
285
331
  return property.definition.type === "checkbox" ? false : undefined;
286
332
  }
333
+ if (property.definition.type === "multi_select") {
334
+ if (filter.operator !== "equals" && filter.operator !== "contains") {
335
+ return undefined;
336
+ }
337
+ const values = databaseFilterSelectedValues(filter.value);
338
+ if (values.length === 0) return undefined;
339
+ return values.map(
340
+ (value) =>
341
+ databasePropertyOptionIdForFilterValue(property, value) ?? value,
342
+ );
343
+ }
344
+ if (property.definition.type === "person" && filter.operator === "contains") {
345
+ const values = databaseFilterSelectedValues(filter.value);
346
+ return values.length > 0 ? values : undefined;
347
+ }
287
348
  if (filter.operator !== "equals") return undefined;
288
349
 
289
350
  const value = filter.value.trim();
290
351
  if (!value) return undefined;
291
352
 
292
353
  const optionValue = databasePropertyOptionIdForFilterValue(property, value);
293
- if (property.definition.type === "multi_select") {
294
- return [optionValue ?? value];
295
- }
296
354
  if (
297
355
  property.definition.type === "select" ||
298
356
  property.definition.type === "status"
@@ -320,6 +378,47 @@ function databasePropertyOptionIdForFilterValue(
320
378
  )?.id;
321
379
  }
322
380
 
381
+ function databaseFilterSelectedValues(value: string) {
382
+ const trimmed = value.trim();
383
+ if (!trimmed) return [];
384
+ try {
385
+ const parsed = JSON.parse(trimmed) as unknown;
386
+ if (!Array.isArray(parsed)) return [trimmed];
387
+ return [
388
+ ...new Set(
389
+ parsed
390
+ .filter((item): item is string => typeof item === "string")
391
+ .map((item) => item.trim())
392
+ .filter(Boolean),
393
+ ),
394
+ ];
395
+ } catch {
396
+ return [trimmed];
397
+ }
398
+ }
399
+
400
+ function databaseFilterDateRangeValues(value: string): [string, string] {
401
+ const trimmed = value.trim();
402
+ if (!trimmed) return ["", ""];
403
+ try {
404
+ const parsed = JSON.parse(trimmed) as unknown;
405
+ if (!Array.isArray(parsed)) return [trimmed, ""];
406
+ const start = typeof parsed[0] === "string" ? parsed[0].trim() : "";
407
+ const end = typeof parsed[1] === "string" ? parsed[1].trim() : "";
408
+ return [start, end];
409
+ } catch {
410
+ return [trimmed, ""];
411
+ }
412
+ }
413
+
414
+ function databaseFilterDateRangeValue(value: string): [number, number] | null {
415
+ const [startValue, endValue] = databaseFilterDateRangeValues(value);
416
+ const start = new Date(startValue).getTime();
417
+ const end = new Date(endValue).getTime();
418
+ if (!Number.isFinite(start) || !Number.isFinite(end)) return null;
419
+ return start <= end ? [start, end] : [end, start];
420
+ }
421
+
323
422
  function databaseItemMatchesFilter(
324
423
  item: ContentDatabaseItem,
325
424
  properties: DocumentProperty[],
@@ -343,10 +442,20 @@ function databaseItemMatchesFilter(
343
442
  : current < target;
344
443
  }
345
444
 
346
- if (filter.operator === "before" || filter.operator === "after") {
445
+ if (
446
+ filter.operator === "before" ||
447
+ filter.operator === "after" ||
448
+ filter.operator === "between"
449
+ ) {
347
450
  const current = propertyDateValue(property);
451
+ if (!Number.isFinite(current)) return false;
452
+ if (filter.operator === "between") {
453
+ const range = databaseFilterDateRangeValue(filter.value);
454
+ if (!range) return false;
455
+ return current >= range[0] && current <= range[1];
456
+ }
348
457
  const target = new Date(filter.value.trim()).getTime();
349
- if (!Number.isFinite(current) || !Number.isFinite(target)) return false;
458
+ if (!Number.isFinite(target)) return false;
350
459
  return filter.operator === "before" ? current < target : current > target;
351
460
  }
352
461
 
@@ -355,8 +464,31 @@ function databaseItemMatchesFilter(
355
464
  properties,
356
465
  filter.key,
357
466
  ).map((candidate) => candidate.trim().toLowerCase());
467
+ const selectedFilterValues = databaseFilterSelectedValues(filter.value).map(
468
+ (candidate) => candidate.trim().toLowerCase(),
469
+ );
358
470
  const normalizedValue = value.trim().toLowerCase();
359
- const normalizedFilter = filter.value.trim().toLowerCase();
471
+ const normalizedFilter = selectedFilterValues[0] ?? "";
472
+ const usesDiscreteValues =
473
+ property?.definition.type === "select" ||
474
+ property?.definition.type === "status" ||
475
+ property?.definition.type === "multi_select" ||
476
+ property?.definition.type === "person";
477
+
478
+ if (
479
+ usesDiscreteValues &&
480
+ (filter.operator === "equals" || filter.operator === "contains")
481
+ ) {
482
+ return selectedFilterValues.some((filterValue) =>
483
+ candidateValues.includes(filterValue),
484
+ );
485
+ }
486
+ if (usesDiscreteValues && filter.operator === "does_not_equal") {
487
+ return selectedFilterValues.every(
488
+ (filterValue) => !candidateValues.includes(filterValue),
489
+ );
490
+ }
491
+
360
492
  if (filter.operator === "equals") {
361
493
  return candidateValues.includes(normalizedFilter);
362
494
  }
@@ -529,9 +661,66 @@ export function propertyNumberValue(
529
661
  export function databaseFilterOptionChoices(
530
662
  key: string,
531
663
  properties: DocumentProperty[],
664
+ items: ContentDatabaseItem[] = [],
532
665
  ) {
533
- const property = databaseFilterOptionPropertyForKey(key, properties);
534
- return property?.definition.options.options ?? [];
666
+ const property = databaseFilterPropertyForKey(key, properties);
667
+ if (property?.definition.type === "person") {
668
+ return databaseFilterPersonChoices(property.definition.id, items);
669
+ }
670
+ const optionProperty = databaseFilterOptionPropertyForKey(key, properties);
671
+ return optionProperty?.definition.options.options ?? [];
672
+ }
673
+
674
+ function databaseFilterPersonChoices(
675
+ propertyId: string,
676
+ items: ContentDatabaseItem[],
677
+ ) {
678
+ const people = new Map<string, string>();
679
+ for (const item of items) {
680
+ const property = item.properties.find(
681
+ (candidate) => candidate.definition.id === propertyId,
682
+ );
683
+ for (const person of databasePersonItems(property?.value ?? null)) {
684
+ const key = person.trim().toLowerCase();
685
+ if (!people.has(key)) people.set(key, person);
686
+ }
687
+ }
688
+ return Array.from(people.values()).map((person) => ({
689
+ id: person,
690
+ name: databasePersonLabel(person),
691
+ color: "gray" as const,
692
+ }));
693
+ }
694
+
695
+ function databasePersonItems(value: DocumentProperty["value"]) {
696
+ const rawItems = Array.isArray(value)
697
+ ? value
698
+ : typeof value === "string"
699
+ ? value.split(/[\n,]+/)
700
+ : [];
701
+ const seen = new Set<string>();
702
+ return rawItems
703
+ .map((item) => item.trim())
704
+ .filter((item) => {
705
+ if (!item) return false;
706
+ const key = item.toLowerCase();
707
+ if (seen.has(key)) return false;
708
+ seen.add(key);
709
+ return true;
710
+ });
711
+ }
712
+
713
+ function databasePersonLabel(value: string) {
714
+ const trimmed = value.trim();
715
+ if (!trimmed) return "Empty";
716
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)
717
+ ? trimmed
718
+ .split("@")[0]
719
+ .split(/[._-]+/)
720
+ .filter(Boolean)
721
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
722
+ .join(" ")
723
+ : trimmed;
535
724
  }
536
725
 
537
726
  export function databaseFilterOptionPropertyForKey(
@@ -543,7 +732,8 @@ export function databaseFilterOptionPropertyForKey(
543
732
  if (
544
733
  property.definition.type !== "select" &&
545
734
  property.definition.type !== "status" &&
546
- property.definition.type !== "multi_select"
735
+ property.definition.type !== "multi_select" &&
736
+ property.definition.type !== "person"
547
737
  ) {
548
738
  return null;
549
739
  }
@@ -554,6 +744,11 @@ function databaseFilterValueLabel(
554
744
  filter: DatabaseFilter,
555
745
  properties: DocumentProperty[],
556
746
  ) {
747
+ if (filter.operator === "between") {
748
+ const [start, end] = databaseFilterDateRangeValues(filter.value);
749
+ if (start && end) return `${start} to ${end}`;
750
+ return start || end || "Choose dates";
751
+ }
557
752
  const option = databaseFilterOptionChoices(filter.key, properties).find(
558
753
  (candidate) =>
559
754
  candidate.id === filter.value || candidate.name === filter.value,
@@ -593,6 +788,7 @@ export const FILTER_OPERATOR_LABELS: Record<FilterOperator, string> = {
593
788
  less_than: "Less than",
594
789
  before: "Before",
595
790
  after: "After",
791
+ between: "Between",
596
792
  is_checked: "Checked",
597
793
  is_unchecked: "Unchecked",
598
794
  is_empty: "Is empty",
@@ -609,8 +805,13 @@ export function filterOperatorsForKey(
609
805
  return ["is_checked", "is_unchecked"];
610
806
  }
611
807
 
612
- if (type === "select" || type === "status" || type === "multi_select") {
613
- return ["equals", "does_not_equal", "is_empty", "is_not_empty"];
808
+ if (
809
+ type === "select" ||
810
+ type === "status" ||
811
+ type === "multi_select" ||
812
+ type === "person"
813
+ ) {
814
+ return ["contains", "does_not_equal", "is_empty", "is_not_empty"];
614
815
  }
615
816
 
616
817
  if (type === "number") {
@@ -634,6 +835,7 @@ export function filterOperatorsForKey(
634
835
  "does_not_equal",
635
836
  "before",
636
837
  "after",
838
+ "between",
637
839
  "is_empty",
638
840
  "is_not_empty",
639
841
  ];
@@ -6,6 +6,7 @@ import type {
6
6
  BuilderCmsModelsResponse,
7
7
  ChangeContentDatabaseSourceRoleRequest,
8
8
  ContentDatabaseResponse,
9
+ ContentDatabasePersonalViewResponse,
9
10
  ContentDatabaseSourceFieldMapping,
10
11
  CreateInlineDatabaseRequest,
11
12
  CreateInlineDatabaseResponse,
@@ -34,6 +35,7 @@ import type {
34
35
  StageBuilderSourceBulkUpdateResponse,
35
36
  StageBuilderRevisionRequest,
36
37
  SuggestSourceJoinKeyResponse,
38
+ UpdateContentDatabasePersonalViewRequest,
37
39
  UpdateContentDatabaseViewRequest,
38
40
  ValidateBuilderSourceExecutionRequest,
39
41
  } from "@shared/api";
@@ -614,6 +616,36 @@ export function useUpdateContentDatabaseView(documentId: string) {
614
616
  });
615
617
  }
616
618
 
619
+ export function useContentDatabasePersonalView(databaseId: string | null) {
620
+ return useActionQuery<ContentDatabasePersonalViewResponse>(
621
+ "get-content-database-personal-view",
622
+ databaseId ? { databaseId } : undefined,
623
+ {
624
+ enabled: !!databaseId,
625
+ retry: false,
626
+ placeholderData: (previous) => previous,
627
+ },
628
+ );
629
+ }
630
+
631
+ export function useUpdateContentDatabasePersonalView(
632
+ databaseId: string | null,
633
+ ) {
634
+ const queryClient = useQueryClient();
635
+ return useActionMutation<
636
+ ContentDatabasePersonalViewResponse,
637
+ UpdateContentDatabasePersonalViewRequest
638
+ >("update-content-database-personal-view", {
639
+ onSuccess: (data) => {
640
+ if (!databaseId) return;
641
+ queryClient.setQueryData(
642
+ ["action", "get-content-database-personal-view", { databaseId }],
643
+ data,
644
+ );
645
+ },
646
+ });
647
+ }
648
+
617
649
  export function useAttachContentDatabaseSource(documentId: string) {
618
650
  const queryClient = useQueryClient();
619
651
  return useActionMutation<
@@ -655,9 +655,18 @@ const messages = {
655
655
  failedToMoveRow: "行動行失敗",
656
656
  failedToSavePageIcon: "儲存頁面圖示失敗",
657
657
  failedToSavePagePreview: "儲存頁面預覽失敗",
658
+ failedToSaveView: "儲存檢視失敗",
658
659
  failedToUpdateCheckbox: "更新核取方塊失敗",
659
660
  failedToUpdateEverySelectedRow: "未能更新所有選取行",
660
661
  filter: "篩選",
662
+ filterBy: "篩選條件...",
663
+ addAdvancedFilter: "新增進階篩選器",
664
+ addFilterRule: "新增篩選規則",
665
+ addFilterGroup: "新增篩選群組",
666
+ nestedFilterGroupUnavailable: "巢狀篩選群組需要資料模型更新",
667
+ deleteFilter: "刪除篩選器",
668
+ where: "條件",
669
+ sortBy: "排序依據...",
661
670
  group: "分組",
662
671
  click: "點選",
663
672
  column: "列",
@@ -665,6 +674,7 @@ const messages = {
665
674
  clearColumn: "清空列",
666
675
  clearRow: "清空行",
667
676
  deleteColumn: "刪除列",
677
+ deleteSort: "刪除排序",
668
678
  drag: "拖動",
669
679
  headerColumn: "標題列",
670
680
  headerRow: "標題行",
@@ -675,6 +685,8 @@ const messages = {
675
685
  insertRight: "在右側插入",
676
686
  layout: "布局",
677
687
  matchExistingItemsToDetails: "將現有項目比對到詳細資料",
688
+ moveFilterDown: "下移",
689
+ moveFilterUp: "上移",
678
690
  name: "名稱",
679
691
  nameColumnMenu: "名稱列選單",
680
692
  newBoardGroupName: "新看板分組名稱",
@@ -691,19 +703,23 @@ const messages = {
691
703
  noRowsMatchThisView: "沒有行匹配此檢視",
692
704
  editFilesForSelectedRows: "編輯所選行的檔案",
693
705
  oneFileOrMediaLinkPerLine: "每行一個檔案或媒體連結",
706
+ personalViewChanges: "僅自己可見",
694
707
  previewPageTitle: "預覽頁面標題",
695
708
  previousDatabasePage: "上一頁面資料庫頁面",
696
709
  previousMonth: "上個月",
697
710
  previousTimelineRange: "上一個時間線範圍",
698
711
  propertyVisibility: "屬性可見性",
712
+ reset: "重設",
699
713
  resizeNameColumn: "調整名稱列寬",
700
714
  row: "行",
701
715
  rowOptions: "行選項",
702
716
  search: "搜尋",
717
+ saveForEveryone: "為所有人儲存",
703
718
  searchBuilderModels: "搜尋 Builder 模型",
704
719
  searchGroupProperties: "搜尋分組屬性",
705
720
  searchModels: "搜尋模型",
706
721
  searchOptions: "搜尋選項",
722
+ searchForAProperty: "搜尋屬性...",
707
723
  searchProperties: "搜尋屬性",
708
724
  somethingWentWrong: "出了點問題",
709
725
  sort: "排序",