@agent-native/core 0.101.7 → 0.101.11

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 (219) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +28 -0
  3. package/corpus/core/docs/content/deployment.mdx +9 -7
  4. package/corpus/core/docs/content/locales/ar-SA/deployment.mdx +9 -7
  5. package/corpus/core/docs/content/locales/de-DE/deployment.mdx +9 -7
  6. package/corpus/core/docs/content/locales/es-ES/deployment.mdx +9 -7
  7. package/corpus/core/docs/content/locales/fr-FR/deployment.mdx +9 -7
  8. package/corpus/core/docs/content/locales/hi-IN/deployment.mdx +9 -7
  9. package/corpus/core/docs/content/locales/ja-JP/deployment.mdx +9 -7
  10. package/corpus/core/docs/content/locales/ko-KR/deployment.mdx +9 -7
  11. package/corpus/core/docs/content/locales/pt-BR/deployment.mdx +9 -7
  12. package/corpus/core/docs/content/locales/zh-CN/deployment.mdx +9 -7
  13. package/corpus/core/docs/content/locales/zh-TW/deployment.mdx +9 -7
  14. package/corpus/core/package.json +1 -1
  15. package/corpus/core/src/a2a/client.ts +23 -1
  16. package/corpus/core/src/a2a/handlers.ts +65 -1
  17. package/corpus/core/src/a2a/server.ts +124 -0
  18. package/corpus/core/src/a2a/task-store.ts +253 -0
  19. package/corpus/core/src/a2a/types.ts +26 -0
  20. package/corpus/core/src/agent/production-agent.ts +5 -2
  21. package/corpus/core/src/client/review/ReviewCommentComposer.tsx +9 -1
  22. package/corpus/core/src/client/review/ReviewThreadPanel.tsx +17 -2
  23. package/corpus/core/src/client/settings/SettingsPanel.tsx +41 -21
  24. package/corpus/core/src/client/settings/useBuilderStatus.ts +28 -9
  25. package/corpus/core/src/client/setup-connections/BuilderConnectCard.tsx +2 -7
  26. package/corpus/core/src/mcp/builtin-tools.ts +25 -1
  27. package/corpus/core/src/scripts/call-agent.ts +22 -3
  28. package/corpus/core/src/server/agent-chat-plugin.ts +86 -1
  29. package/corpus/core/src/server/auth.ts +25 -1
  30. package/corpus/core/src/server/builder-browser.ts +353 -1
  31. package/corpus/core/src/server/core-routes-plugin.ts +601 -223
  32. package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
  33. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +80 -1
  34. package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +28 -3
  35. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +5 -1
  36. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +30 -3
  37. package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +196 -0
  38. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +115 -1
  39. package/corpus/templates/calendar/app/lib/out-of-office.ts +45 -0
  40. package/corpus/templates/calendar/changelog/2026-07-14-out-of-office-blocks-now-sit-behind-meetings-and-event-stack.md +6 -0
  41. package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +23 -0
  42. package/corpus/templates/content/AGENTS.md +3 -2
  43. package/corpus/templates/content/actions/_builder-cms-intent-lookup.ts +87 -0
  44. package/corpus/templates/content/actions/_builder-cms-read-client.ts +173 -25
  45. package/corpus/templates/content/actions/_builder-cms-source-adapter.ts +1 -0
  46. package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +427 -18
  47. package/corpus/templates/content/actions/_builder-cms-write-client.ts +17 -73
  48. package/corpus/templates/content/actions/_builder-cms-write-settings.ts +27 -7
  49. package/corpus/templates/content/actions/_builder-source-execution-claim.ts +124 -0
  50. package/corpus/templates/content/actions/_builder-source-execution-preservation.ts +21 -0
  51. package/corpus/templates/content/actions/_builder-source-timings.ts +57 -0
  52. package/corpus/templates/content/actions/_database-source-utils.ts +1041 -154
  53. package/corpus/templates/content/actions/_database-utils.ts +89 -12
  54. package/corpus/templates/content/actions/_preview-document-draft.ts +31 -0
  55. package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +309 -0
  56. package/corpus/templates/content/actions/execute-builder-source-batch.ts +120 -49
  57. package/corpus/templates/content/actions/execute-builder-source-execution.ts +608 -264
  58. package/corpus/templates/content/actions/get-preview-document-draft.ts +26 -0
  59. package/corpus/templates/content/actions/materialize-builder-required-fields.ts +550 -0
  60. package/corpus/templates/content/actions/prepare-builder-source-execution.ts +112 -65
  61. package/corpus/templates/content/actions/prepare-builder-source-review.ts +463 -170
  62. package/corpus/templates/content/actions/preview-builder-source-review.ts +99 -0
  63. package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +2 -2
  64. package/corpus/templates/content/actions/update-preview-document-draft.ts +153 -0
  65. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +27 -15
  66. package/corpus/templates/content/app/components/editor/MathRenderer.tsx +43 -0
  67. package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +260 -4
  68. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +57 -5
  69. package/corpus/templates/content/app/components/editor/body-hydration.ts +27 -0
  70. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2353 -576
  71. package/corpus/templates/content/app/components/editor/database/settings.tsx +13 -10
  72. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +358 -75
  73. package/corpus/templates/content/app/components/editor/extensions/NotionExtensions.tsx +78 -0
  74. package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +124 -29
  75. package/corpus/templates/content/app/components/editor/previewDocumentSaveRegistry.ts +2 -0
  76. package/corpus/templates/content/app/global.css +71 -1
  77. package/corpus/templates/content/app/hooks/use-content-database.ts +68 -0
  78. package/corpus/templates/content/app/hooks/use-documents.ts +53 -4
  79. package/corpus/templates/content/app/i18n/zh-TW.ts +38 -0
  80. package/corpus/templates/content/app/i18n-data.ts +450 -0
  81. package/corpus/templates/content/app/root.tsx +2 -0
  82. package/corpus/templates/content/changelog/2026-07-10-builder-backed-preview-edits-are-preserved-when-article-bodi.md +6 -0
  83. package/corpus/templates/content/changelog/2026-07-10-builder-cms-sources-can-now-be-enabled-for-guarded-staged-or.md +6 -0
  84. package/corpus/templates/content/changelog/2026-07-13-builder-connection-setup-is-now-available-directly-from-conn.md +6 -0
  85. package/corpus/templates/content/changelog/2026-07-13-builder-reviews-now-hide-already-applied-writes-detect-nativ.md +6 -0
  86. package/corpus/templates/content/changelog/2026-07-13-required-builder-publishing-fields-can-now-be-added-directly.md +6 -0
  87. package/corpus/templates/content/changelog/2026-07-13-you-can-now-cancel-a-prepared-builder-update-safely-before-i.md +6 -0
  88. package/corpus/templates/content/changelog/2026-07-14-builder-account-connection-now-remains-available-when-branch.md +6 -0
  89. package/corpus/templates/content/changelog/2026-07-14-builder-backed-articles-now-preserve-fifth-and-sixth-level-h.md +6 -0
  90. package/corpus/templates/content/changelog/2026-07-14-builder-sources-now-review-and-publish-rich-article-updates-.md +6 -0
  91. package/corpus/templates/content/changelog/2026-07-14-builder-writes-now-stop-retries-when-reconciliation-is-needed.md +6 -0
  92. package/corpus/templates/content/changelog/2026-07-14-file-and-media-links-now-validate-before-saving.md +6 -0
  93. package/corpus/templates/content/changelog/2026-07-14-latex-equations-render-in-documents-public-pages-and-exports.md +6 -0
  94. package/corpus/templates/content/package.json +1 -0
  95. package/corpus/templates/content/parity/matrix.md +27 -25
  96. package/corpus/templates/content/parity/matrix.ts +50 -2
  97. package/corpus/templates/content/server/db/schema.ts +47 -0
  98. package/corpus/templates/content/server/plugins/auth.ts +1 -0
  99. package/corpus/templates/content/server/plugins/db.ts +53 -0
  100. package/corpus/templates/content/shared/api.ts +70 -1
  101. package/corpus/templates/content/shared/builder-mdx.ts +705 -15
  102. package/corpus/templates/content/shared/document-export.ts +296 -25
  103. package/corpus/templates/content/shared/inline-math.ts +128 -0
  104. package/corpus/templates/content/shared/math-rendering.ts +50 -0
  105. package/corpus/templates/content/shared/nfm.ts +20 -17
  106. package/corpus/templates/content/vite.config.ts +0 -1
  107. package/corpus/templates/design/app/components/design/ReadOnlyDesignBanner.tsx +52 -7
  108. package/corpus/templates/design/app/components/design/ReviewCommentsPanel.tsx +36 -4
  109. package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +4 -1
  110. package/corpus/templates/design/app/i18n/ar-SA.ts +1 -0
  111. package/corpus/templates/design/app/i18n/de-DE.ts +1 -0
  112. package/corpus/templates/design/app/i18n/en-US.ts +1 -0
  113. package/corpus/templates/design/app/i18n/es-ES.ts +1 -0
  114. package/corpus/templates/design/app/i18n/fr-FR.ts +1 -0
  115. package/corpus/templates/design/app/i18n/hi-IN.ts +1 -0
  116. package/corpus/templates/design/app/i18n/ja-JP.ts +1 -0
  117. package/corpus/templates/design/app/i18n/ko-KR.ts +1 -0
  118. package/corpus/templates/design/app/i18n/pt-BR.ts +1 -0
  119. package/corpus/templates/design/app/i18n/zh-CN.ts +1 -0
  120. package/corpus/templates/design/app/i18n/zh-TW.ts +1 -0
  121. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +8 -0
  122. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +11 -0
  123. package/corpus/templates/design/changelog/2026-07-14-reviewers-can-now-pin-comments-from-the-canvas-toolbar-targe.md +6 -0
  124. package/corpus/templates/design/changelog/2026-07-15-viewer-comment-controls-now-sit-inside-the-read-only-notice-.md +6 -0
  125. package/corpus/templates/design/shared/review-anchor.ts +48 -0
  126. package/corpus/templates/mail/.agents/skills/email-drafts/SKILL.md +17 -0
  127. package/corpus/templates/mail/AGENTS.md +21 -19
  128. package/corpus/templates/mail/actions/create-attachment-upload.ts +49 -0
  129. package/corpus/templates/mail/changelog/2026-07-14-connected-agents-can-now-securely-upload-local-files-for-mai.md +6 -0
  130. package/corpus/templates/mail/server/handlers/media.ts +84 -61
  131. package/corpus/templates/mail/server/lib/attachment-upload-ticket.ts +234 -0
  132. package/corpus/templates/mail/server/lib/mail-connector-catalog.ts +8 -4
  133. package/corpus/templates/mail/server/lib/media-upload.ts +114 -0
  134. package/corpus/templates/mail/server/plugins/auth.ts +9 -1
  135. package/corpus/templates/mail/server/routes/api/media/attachment-upload/[uploadId].put.ts +1 -0
  136. package/corpus/templates/plan/server/plan-mdx.ts +53 -7
  137. package/dist/a2a/client.d.ts +5 -1
  138. package/dist/a2a/client.d.ts.map +1 -1
  139. package/dist/a2a/client.js +18 -1
  140. package/dist/a2a/client.js.map +1 -1
  141. package/dist/a2a/handlers.d.ts.map +1 -1
  142. package/dist/a2a/handlers.js +57 -8
  143. package/dist/a2a/handlers.js.map +1 -1
  144. package/dist/a2a/server.d.ts.map +1 -1
  145. package/dist/a2a/server.js +87 -1
  146. package/dist/a2a/server.js.map +1 -1
  147. package/dist/a2a/task-store.d.ts +27 -0
  148. package/dist/a2a/task-store.d.ts.map +1 -1
  149. package/dist/a2a/task-store.js +216 -0
  150. package/dist/a2a/task-store.js.map +1 -1
  151. package/dist/a2a/types.d.ts +24 -0
  152. package/dist/a2a/types.d.ts.map +1 -1
  153. package/dist/a2a/types.js.map +1 -1
  154. package/dist/agent/production-agent.d.ts +1 -0
  155. package/dist/agent/production-agent.d.ts.map +1 -1
  156. package/dist/agent/production-agent.js +5 -2
  157. package/dist/agent/production-agent.js.map +1 -1
  158. package/dist/client/review/ReviewCommentComposer.d.ts +2 -1
  159. package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -1
  160. package/dist/client/review/ReviewCommentComposer.js +3 -3
  161. package/dist/client/review/ReviewCommentComposer.js.map +1 -1
  162. package/dist/client/review/ReviewThreadPanel.d.ts +9 -1
  163. package/dist/client/review/ReviewThreadPanel.d.ts.map +1 -1
  164. package/dist/client/review/ReviewThreadPanel.js +6 -4
  165. package/dist/client/review/ReviewThreadPanel.js.map +1 -1
  166. package/dist/client/settings/SettingsPanel.d.ts +3 -0
  167. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  168. package/dist/client/settings/SettingsPanel.js +11 -4
  169. package/dist/client/settings/SettingsPanel.js.map +1 -1
  170. package/dist/client/settings/useBuilderStatus.d.ts +8 -4
  171. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  172. package/dist/client/settings/useBuilderStatus.js +19 -6
  173. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  174. package/dist/client/setup-connections/BuilderConnectCard.d.ts.map +1 -1
  175. package/dist/client/setup-connections/BuilderConnectCard.js +2 -5
  176. package/dist/client/setup-connections/BuilderConnectCard.js.map +1 -1
  177. package/dist/collab/awareness.d.ts +2 -2
  178. package/dist/collab/awareness.d.ts.map +1 -1
  179. package/dist/collab/struct-routes.d.ts +1 -1
  180. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  181. package/dist/mcp/builtin-tools.d.ts.map +1 -1
  182. package/dist/mcp/builtin-tools.js +22 -3
  183. package/dist/mcp/builtin-tools.js.map +1 -1
  184. package/dist/notifications/routes.d.ts +3 -3
  185. package/dist/resources/handlers.d.ts +2 -2
  186. package/dist/scripts/call-agent.d.ts +1 -1
  187. package/dist/scripts/call-agent.d.ts.map +1 -1
  188. package/dist/scripts/call-agent.js +19 -1
  189. package/dist/scripts/call-agent.js.map +1 -1
  190. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  191. package/dist/server/agent-chat-plugin.js +71 -2
  192. package/dist/server/agent-chat-plugin.js.map +1 -1
  193. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  194. package/dist/server/auth.d.ts +1 -0
  195. package/dist/server/auth.d.ts.map +1 -1
  196. package/dist/server/auth.js +19 -2
  197. package/dist/server/auth.js.map +1 -1
  198. package/dist/server/builder-browser.d.ts +78 -0
  199. package/dist/server/builder-browser.d.ts.map +1 -1
  200. package/dist/server/builder-browser.js +246 -1
  201. package/dist/server/builder-browser.js.map +1 -1
  202. package/dist/server/core-routes-plugin.d.ts +41 -0
  203. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  204. package/dist/server/core-routes-plugin.js +260 -14
  205. package/dist/server/core-routes-plugin.js.map +1 -1
  206. package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
  207. package/docs/content/deployment.mdx +9 -7
  208. package/docs/content/locales/ar-SA/deployment.mdx +9 -7
  209. package/docs/content/locales/de-DE/deployment.mdx +9 -7
  210. package/docs/content/locales/es-ES/deployment.mdx +9 -7
  211. package/docs/content/locales/fr-FR/deployment.mdx +9 -7
  212. package/docs/content/locales/hi-IN/deployment.mdx +9 -7
  213. package/docs/content/locales/ja-JP/deployment.mdx +9 -7
  214. package/docs/content/locales/ko-KR/deployment.mdx +9 -7
  215. package/docs/content/locales/pt-BR/deployment.mdx +9 -7
  216. package/docs/content/locales/zh-CN/deployment.mdx +9 -7
  217. package/docs/content/locales/zh-TW/deployment.mdx +9 -7
  218. package/package.json +2 -2
  219. package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
@@ -147,6 +147,31 @@ for await (const update of client.stream({
147
147
  }
148
148
  ```
149
149
 
150
+ ### Carrying explicit chat authorization
151
+
152
+ When the authenticated caller has an exact consequential action that the user
153
+ explicitly authorized in the originating chat, pass the tool name and complete
154
+ input as `approvedActions`. The receiver accepts these grants only from a
155
+ JWT-verified user identity, converts each one to the same content-addressed key
156
+ as its local approval gate, and consumes it once:
157
+
158
+ ```ts
159
+ await client.send(message, {
160
+ async: true,
161
+ approvedActions: [
162
+ {
163
+ tool: "send-email",
164
+ input: { to, subject, body, attachments },
165
+ },
166
+ ],
167
+ });
168
+ ```
169
+
170
+ Never infer authorization from request prose or broaden the input. A changed
171
+ recipient, body, attachment, or tool produces a different key and follows the
172
+ receiver's normal approval-required path. Static API keys and unsigned callers
173
+ cannot carry these grants.
174
+
150
175
  ## JSON-RPC Methods
151
176
 
152
177
  | Method | Purpose | Auth required |
@@ -30,6 +30,7 @@ import {
30
30
  } from "@/hooks/use-view-preferences";
31
31
  import { partitionAllDayEvents } from "@/lib/all-day-layout";
32
32
  import { getEventDisplayColor, allOtherDeclined } from "@/lib/event-colors";
33
+ import { isOutOfOfficeEvent } from "@/lib/out-of-office";
33
34
  import {
34
35
  shouldSuppressAfterPopoverClose,
35
36
  shouldSuppressCreatePointerDown,
@@ -44,6 +45,7 @@ import {
44
45
  } from "@/lib/working-location";
45
46
 
46
47
  import { EventDetailPopover } from "./EventDetailPopover";
48
+ import { OutOfOfficeEvent } from "./OutOfOfficeEvent";
47
49
 
48
50
  interface DayViewProps {
49
51
  events: CalendarEvent[];
@@ -230,6 +232,7 @@ interface DayEventCardProps {
230
232
  onDraftUpdate?: DayViewProps["onDraftUpdate"];
231
233
  onDraftCreate?: DayViewProps["onDraftCreate"];
232
234
  onDraftDiscard?: DayViewProps["onDraftDiscard"];
235
+ onPopoverOpenChange: (event: CalendarEvent, open: boolean) => void;
233
236
  }
234
237
 
235
238
  /**
@@ -262,6 +265,7 @@ const DayEventCard = memo(function DayEventCard({
262
265
  onDraftUpdate,
263
266
  onDraftCreate,
264
267
  onDraftDiscard,
268
+ onPopoverOpenChange,
265
269
  }: DayEventCardProps) {
266
270
  const t = useT();
267
271
  const workingLocationLabels = createWorkingLocationDisplayLabels(t);
@@ -488,6 +492,7 @@ const DayEventCard = memo(function DayEventCard({
488
492
  onDraftUpdate={onDraftUpdate}
489
493
  onDraftCreate={onDraftCreate}
490
494
  onDraftDiscard={onDraftDiscard}
495
+ onOpenChange={(open) => onPopoverOpenChange(event, open)}
491
496
  >
492
497
  {eventButton}
493
498
  </EventDetailPopover>
@@ -543,6 +548,7 @@ export const DayView = memo(function DayView({
543
548
  const { prefs } = useViewPreferences();
544
549
  const [now, setNow] = useState(new Date());
545
550
  const [focusedEventId, setFocusedEventId] = useState<string | null>(null);
551
+ const focusedEventIdRef = useRef<string | null>(null);
546
552
  const currentTimeRef = useRef<HTMLDivElement>(null);
547
553
  const scrollContainerRef = useRef<HTMLDivElement>(null);
548
554
 
@@ -550,6 +556,7 @@ export const DayView = memo(function DayView({
550
556
  useEffect(() => {
551
557
  function handleKey(e: KeyboardEvent) {
552
558
  if (e.key === "Escape") {
559
+ focusedEventIdRef.current = null;
553
560
  setFocusedEventId(null);
554
561
  setFocusedEvent(null);
555
562
  }
@@ -594,7 +601,14 @@ export const DayView = memo(function DayView({
594
601
  () => partitionAllDayEvents(allDayEvents),
595
602
  [allDayEvents],
596
603
  );
597
- const timedEvents = useMemo(() => events.filter((e) => !e.allDay), [events]);
604
+ const outOfOfficeEvents = useMemo(
605
+ () => events.filter((event) => !event.allDay && isOutOfOfficeEvent(event)),
606
+ [events],
607
+ );
608
+ const timedEvents = useMemo(
609
+ () => events.filter((event) => !event.allDay && !isOutOfOfficeEvent(event)),
610
+ [events],
611
+ );
598
612
  const layout = useMemo(() => computeLayout(timedEvents), [timedEvents]);
599
613
 
600
614
  // Timezone label: prefer the short generic name (e.g. "PT", "ET")
@@ -666,8 +680,25 @@ export const DayView = memo(function DayView({
666
680
 
667
681
  const canDrag = !!onEventTimeChange;
668
682
 
683
+ const handleEventPopoverOpenChange = useCallback(
684
+ (event: CalendarEvent, open: boolean) => {
685
+ if (open) {
686
+ focusedEventIdRef.current = event.id;
687
+ setFocusedEventId(event.id);
688
+ setFocusedEvent(event);
689
+ return;
690
+ }
691
+ if (focusedEventIdRef.current !== event.id) return;
692
+ focusedEventIdRef.current = null;
693
+ setFocusedEventId(null);
694
+ setFocusedEvent(null);
695
+ },
696
+ [setFocusedEvent],
697
+ );
698
+
669
699
  const handleEventPointerDown = useCallback(
670
700
  (e: React.PointerEvent, event: CalendarEvent, isStart: boolean) => {
701
+ focusedEventIdRef.current = event.id;
671
702
  setFocusedEventId(event.id);
672
703
  setFocusedEvent(event);
673
704
  if (
@@ -999,6 +1030,53 @@ export const DayView = memo(function DayView({
999
1030
  </div>
1000
1031
  )}
1001
1032
 
1033
+ {/* Native Google out-of-office context sits behind meetings. */}
1034
+ {!isLoading &&
1035
+ outOfOfficeEvents.map((event, markerIndex) => {
1036
+ const isBeingDragged = dragEventId === event.id;
1037
+ const overrides = getDragOverrides(event.id);
1038
+ return (
1039
+ <OutOfOfficeEvent
1040
+ key={event._tempId ?? event.id}
1041
+ event={event}
1042
+ day={date}
1043
+ hourHeight={HOUR_HEIGHT}
1044
+ color={
1045
+ getEventDisplayColor(event, prefs) ?? "hsl(var(--primary))"
1046
+ }
1047
+ label={t("eventForm.outOfOffice")}
1048
+ markerIndex={markerIndex}
1049
+ canDrag={canDrag}
1050
+ isBeingDragged={isBeingDragged}
1051
+ isDragging={isDragging}
1052
+ isDragTargetDay={isBeingDragged}
1053
+ overrideTop={overrides?.top ?? null}
1054
+ overrideHeight={overrides?.height ?? null}
1055
+ onMovePointerDown={(pointerEvent, startsOnDay) =>
1056
+ handleEventPointerDown(pointerEvent, event, startsOnDay)
1057
+ }
1058
+ onResizeTopPointerDown={(pointerEvent) =>
1059
+ handleResizeTopPointerDown(pointerEvent, event.id)
1060
+ }
1061
+ onResizeBottomPointerDown={(pointerEvent) =>
1062
+ handleResizeBottomPointerDown(pointerEvent, event.id)
1063
+ }
1064
+ shouldSuppressClick={shouldSuppressClick}
1065
+ onDelete={onDeleteEvent}
1066
+ isDraft={draftEventIds.includes(event.id)}
1067
+ defaultOpen={quickEditEventId === event.id}
1068
+ onTitleSave={onQuickEditSave}
1069
+ onDismissNew={onQuickEditCancel}
1070
+ onDraftUpdate={onDraftUpdate}
1071
+ onDraftCreate={onDraftCreate}
1072
+ onDraftDiscard={onDraftDiscard}
1073
+ onOpenChange={(open) =>
1074
+ handleEventPopoverOpenChange(event, open)
1075
+ }
1076
+ />
1077
+ );
1078
+ })}
1079
+
1002
1080
  {/* Skeleton events when loading */}
1003
1081
  {isLoading &&
1004
1082
  DAY_SKELETONS.map(
@@ -1053,6 +1131,7 @@ export const DayView = memo(function DayView({
1053
1131
  onDraftUpdate={onDraftUpdate}
1054
1132
  onDraftCreate={onDraftCreate}
1055
1133
  onDraftDiscard={onDraftDiscard}
1134
+ onPopoverOpenChange={handleEventPopoverOpenChange}
1056
1135
  />
1057
1136
  );
1058
1137
  })}
@@ -1,12 +1,13 @@
1
1
  import { useT } from "@agent-native/core/client";
2
2
  import type { CalendarEvent } from "@shared/api";
3
- import { IconAlertTriangleFilled } from "@tabler/icons-react";
3
+ import { IconAlertTriangleFilled, IconCalendarOff } from "@tabler/icons-react";
4
4
 
5
5
  import {
6
6
  getEventDisplayColor,
7
7
  allOtherDeclined,
8
8
  type CalendarColorPreferences,
9
9
  } from "@/lib/event-colors";
10
+ import { isOutOfOfficeEvent } from "@/lib/out-of-office";
10
11
  import { EventStatusIcon } from "@/lib/rsvp-status";
11
12
  import { cn } from "@/lib/utils";
12
13
  import {
@@ -44,6 +45,7 @@ export function EventCard({
44
45
  const title = getWorkingLocationChipLabel(event, workingLocationLabels);
45
46
  const ariaTitle = getWorkingLocationTitle(event, workingLocationLabels);
46
47
  const isWorkingLocation = isWorkingLocationEvent(event);
48
+ const isOutOfOffice = isOutOfOfficeEvent(event);
47
49
 
48
50
  const handleDragStart = (e: React.DragEvent) => {
49
51
  e.dataTransfer.setData("text/plain", event.id);
@@ -73,7 +75,13 @@ export function EventCard({
73
75
  backgroundColor: `${accentColor}25`,
74
76
  }}
75
77
  >
76
- {allOtherDeclined(event) ? (
78
+ {isOutOfOffice ? (
79
+ <IconCalendarOff
80
+ aria-hidden="true"
81
+ className="size-3 shrink-0"
82
+ style={{ color: accentColor }}
83
+ />
84
+ ) : allOtherDeclined(event) ? (
77
85
  <IconAlertTriangleFilled
78
86
  size={10}
79
87
  className="shrink-0 text-current opacity-70"
@@ -91,6 +99,11 @@ export function EventCard({
91
99
  {t("eventForm.workingLocation")}
92
100
  </span>
93
101
  )}
102
+ {isOutOfOffice && (
103
+ <span className="hidden shrink-0 text-[10px] font-normal text-foreground/65 sm:inline">
104
+ {t("eventForm.outOfOffice")}
105
+ </span>
106
+ )}
94
107
  {event.ownerColor && (
95
108
  <span
96
109
  aria-hidden="true"
@@ -123,7 +136,14 @@ export function EventCard({
123
136
  }}
124
137
  >
125
138
  <div className="flex items-center gap-1 truncate">
126
- {allOtherDeclined(event) && (
139
+ {isOutOfOffice && (
140
+ <IconCalendarOff
141
+ aria-hidden="true"
142
+ className="size-3 shrink-0"
143
+ style={{ color: accentColor }}
144
+ />
145
+ )}
146
+ {!isOutOfOffice && allOtherDeclined(event) && (
127
147
  <IconAlertTriangleFilled
128
148
  size={12}
129
149
  className="shrink-0 text-current opacity-70"
@@ -137,6 +157,11 @@ export function EventCard({
137
157
  {t("eventForm.workingLocation")}
138
158
  </span>
139
159
  )}
160
+ {isOutOfOffice && (
161
+ <span className="truncate text-foreground/70">
162
+ {t("eventForm.outOfOffice")}
163
+ </span>
164
+ )}
140
165
  {!event.allDay && (
141
166
  <span className="text-foreground/70">
142
167
  {new Date(event.start).toLocaleTimeString([], {
@@ -34,6 +34,7 @@ import {
34
34
  } from "@/components/ui/tooltip";
35
35
  import { useUpdateEvent } from "@/hooks/use-events";
36
36
  import { useViewPreferences } from "@/hooks/use-view-preferences";
37
+ import { isOutOfOfficeEvent } from "@/lib/out-of-office";
37
38
  import { cn } from "@/lib/utils";
38
39
  import {
39
40
  buildWorkingLocationUpdate,
@@ -135,6 +136,7 @@ export function EventDetailPanel({
135
136
  useGuestNotificationPrompt();
136
137
  const isOverlay = !!event?.overlayEmail;
137
138
  const isWorkingLocation = event ? isWorkingLocationEvent(event) : false;
139
+ const isOutOfOffice = event ? isOutOfOfficeEvent(event) : false;
138
140
  const isRecurringEvent = !!(
139
141
  event?.recurringEventId || event?.recurrence?.length
140
142
  );
@@ -293,7 +295,9 @@ export function EventDetailPanel({
293
295
  <span className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
294
296
  {isWorkingLocation
295
297
  ? t("eventForm.workingLocation")
296
- : t("eventForm.event")}
298
+ : isOutOfOffice
299
+ ? t("eventForm.outOfOffice")
300
+ : t("eventForm.event")}
297
301
  </span>
298
302
  <div className="flex items-center gap-0.5">
299
303
  <Tooltip>
@@ -97,6 +97,7 @@ import {
97
97
  type ReminderMode,
98
98
  validateAttachmentDrafts,
99
99
  } from "@/lib/event-form-utils";
100
+ import { isOutOfOfficeEvent } from "@/lib/out-of-office";
100
101
  import {
101
102
  createEventDetailPopoverToken,
102
103
  markPopoverInteractOutside,
@@ -473,6 +474,8 @@ interface EventDetailPopoverProps {
473
474
  onTitleSave?: (eventId: string, title: string, accountEmail?: string) => void;
474
475
  /** Called when the popover is dismissed for a new event (to clean up if no title was set) */
475
476
  onDismissNew?: (eventId: string, accountEmail?: string) => void;
477
+ /** Called after the popover's visible open state changes through its normal lifecycle. */
478
+ onOpenChange?: (open: boolean) => void;
476
479
  onDraftUpdate?: (
477
480
  eventId: string,
478
481
  updates: Partial<CalendarEvent> & {
@@ -500,6 +503,7 @@ export function EventDetailPopover({
500
503
  defaultOpen = false,
501
504
  onTitleSave,
502
505
  onDismissNew,
506
+ onOpenChange,
503
507
  onDraftUpdate,
504
508
  onDraftCreate,
505
509
  onDraftDiscard,
@@ -520,11 +524,13 @@ export function EventDetailPopover({
520
524
  }
521
525
  const {
522
526
  eventDetailSidebar,
527
+ sidebarEvent,
523
528
  setEventDetailSidebar,
524
529
  setSidebarEvent,
525
530
  setFocusedEvent,
526
531
  } = useCalendarContext();
527
532
  const isWorkingLocation = isWorkingLocationEvent(event);
533
+ const isOutOfOffice = isOutOfOfficeEvent(event);
528
534
  const isSingleDayWorkingLocation = isWorkingLocation && event.allDay;
529
535
  const editableLocationValue = event.location || "";
530
536
 
@@ -1274,6 +1280,9 @@ export function EventDetailPopover({
1274
1280
  (isRecurringEvent ? t("eventForm.repeats") : null);
1275
1281
  const handleOpenChange = useCallback(
1276
1282
  (newOpen: boolean) => {
1283
+ const isPopoverSuppressed =
1284
+ eventDetailSidebar && !isNewEventRef.current && !isDraft;
1285
+ if (newOpen && isPopoverSuppressed) return;
1277
1286
  if (!newOpen && open) {
1278
1287
  const trimmedTitle = editingTitle.trim();
1279
1288
  let savedPendingChange = false;
@@ -1330,11 +1339,27 @@ export function EventDetailPopover({
1330
1339
  handleSaveMeetingLink,
1331
1340
  handleSaveReminders,
1332
1341
  handleSaveAttachments,
1342
+ eventDetailSidebar,
1343
+ isDraft,
1333
1344
  ],
1334
1345
  );
1335
1346
 
1336
1347
  const popoverOpen =
1337
1348
  eventDetailSidebar && !isNewEventRef.current && !isDraft ? false : open;
1349
+ const sidebarDetailsOpen =
1350
+ eventDetailSidebar &&
1351
+ !isNewEventRef.current &&
1352
+ !isDraft &&
1353
+ sidebarEvent?.id === event.id &&
1354
+ sidebarEvent.accountEmail === event.accountEmail;
1355
+ const detailsOpen = popoverOpen || sidebarDetailsOpen;
1356
+ const previousDetailsOpenRef = useRef(false);
1357
+
1358
+ useEffect(() => {
1359
+ if (previousDetailsOpenRef.current === detailsOpen) return;
1360
+ previousDetailsOpenRef.current = detailsOpen;
1361
+ onOpenChange?.(detailsOpen);
1362
+ }, [detailsOpen, onOpenChange]);
1338
1363
 
1339
1364
  useEffect(() => {
1340
1365
  const token = popoverTokenRef.current;
@@ -1386,9 +1411,11 @@ export function EventDetailPopover({
1386
1411
  <span>
1387
1412
  {isWorkingLocation
1388
1413
  ? t("eventForm.workingLocation")
1389
- : isDraft
1390
- ? t("eventForm.draftEvent")
1391
- : t("eventForm.event")}
1414
+ : isOutOfOffice
1415
+ ? t("eventForm.outOfOffice")
1416
+ : isDraft
1417
+ ? t("eventForm.draftEvent")
1418
+ : t("eventForm.event")}
1392
1419
  </span>
1393
1420
  </div>
1394
1421
  <div className="flex items-center gap-0.5">
@@ -0,0 +1,196 @@
1
+ import type { CalendarEvent } from "@shared/api";
2
+ import { IconCalendarOff } from "@tabler/icons-react";
3
+
4
+ import { getOutOfOfficeSegment } from "@/lib/out-of-office";
5
+
6
+ import { EventDetailPopover } from "./EventDetailPopover";
7
+
8
+ interface OutOfOfficeEventProps {
9
+ event: CalendarEvent;
10
+ day: Date;
11
+ hourHeight: number;
12
+ color: string;
13
+ label: string;
14
+ markerIndex?: number;
15
+ compactMarker?: boolean;
16
+ canDrag?: boolean;
17
+ isBeingDragged?: boolean;
18
+ isDragging?: boolean;
19
+ isDragTargetDay?: boolean;
20
+ overrideTop?: number | null;
21
+ overrideHeight?: number | null;
22
+ onMovePointerDown?: (event: React.PointerEvent, startsOnDay: boolean) => void;
23
+ onResizeTopPointerDown?: (event: React.PointerEvent) => void;
24
+ onResizeBottomPointerDown?: (event: React.PointerEvent) => void;
25
+ shouldSuppressClick?: () => boolean;
26
+ onDelete: (eventId: string) => void;
27
+ isDraft: boolean;
28
+ defaultOpen: boolean;
29
+ onTitleSave?: (eventId: string, title: string, accountEmail?: string) => void;
30
+ onDismissNew?: (eventId: string, accountEmail?: string) => void;
31
+ onDraftUpdate?: (
32
+ eventId: string,
33
+ updates: Partial<CalendarEvent> & {
34
+ addGoogleMeet?: boolean;
35
+ addZoom?: boolean;
36
+ workingLocationType?: "homeOffice" | "officeLocation" | "customLocation";
37
+ workingLocationLabel?: string;
38
+ },
39
+ ) => void;
40
+ onDraftCreate?: (
41
+ eventId: string,
42
+ updates?: Partial<CalendarEvent> & {
43
+ addGoogleMeet?: boolean;
44
+ addZoom?: boolean;
45
+ },
46
+ ) => void;
47
+ onDraftDiscard?: (eventId: string) => void;
48
+ onOpenChange?: (open: boolean) => void;
49
+ }
50
+
51
+ export function OutOfOfficeEvent({
52
+ event,
53
+ day,
54
+ hourHeight,
55
+ color,
56
+ label,
57
+ markerIndex = 0,
58
+ compactMarker = false,
59
+ canDrag = false,
60
+ isBeingDragged = false,
61
+ isDragging = false,
62
+ isDragTargetDay = false,
63
+ overrideTop = null,
64
+ overrideHeight = null,
65
+ onMovePointerDown,
66
+ onResizeTopPointerDown,
67
+ onResizeBottomPointerDown,
68
+ shouldSuppressClick,
69
+ onDelete,
70
+ isDraft,
71
+ defaultOpen,
72
+ onTitleSave,
73
+ onDismissNew,
74
+ onDraftUpdate,
75
+ onDraftCreate,
76
+ onDraftDiscard,
77
+ onOpenChange,
78
+ }: OutOfOfficeEventProps) {
79
+ const segment = getOutOfOfficeSegment(event, day);
80
+ const hasDragOverride =
81
+ isBeingDragged &&
82
+ isDragTargetDay &&
83
+ overrideTop !== null &&
84
+ overrideHeight !== null;
85
+ if (isBeingDragged && !isDragTargetDay) return null;
86
+ if (!segment && !hasDragOverride) return null;
87
+
88
+ const top = hasDragOverride
89
+ ? overrideTop
90
+ : ((segment?.topMinutes ?? 0) / 60) * hourHeight;
91
+ const height = hasDragOverride
92
+ ? overrideHeight
93
+ : ((segment?.durationMinutes ?? 1) / 60) * hourHeight;
94
+ const title = event.title || label;
95
+ const startsOnDay = hasDragOverride || segment?.startsOnDay === true;
96
+ const endsOnDay = hasDragOverride || segment?.endsOnDay === true;
97
+ const canManipulate = canDrag && startsOnDay;
98
+
99
+ return (
100
+ <>
101
+ <div
102
+ data-out-of-office-event={event.id}
103
+ className="pointer-events-none absolute inset-x-0 z-0"
104
+ style={{ top: `${top}px`, height: `${height}px` }}
105
+ >
106
+ <div
107
+ aria-hidden="true"
108
+ className="absolute inset-0"
109
+ style={{
110
+ backgroundColor: `color-mix(in srgb, ${color} 7%, transparent)`,
111
+ boxShadow: `inset 2px 0 0 color-mix(in srgb, ${color} 28%, transparent)`,
112
+ }}
113
+ />
114
+ </div>
115
+ <div
116
+ data-out-of-office-trigger={event.id}
117
+ className={`pointer-events-auto absolute ${
118
+ isBeingDragged && isDragging ? "z-[100]" : "z-40"
119
+ }`}
120
+ style={{
121
+ top: `${top + 4}px`,
122
+ right: `${4 + (compactMarker ? markerIndex * 24 : 0)}px`,
123
+ left: compactMarker ? undefined : `${4 + markerIndex * 12}px`,
124
+ }}
125
+ >
126
+ <EventDetailPopover
127
+ event={event}
128
+ onDelete={onDelete}
129
+ isDraft={isDraft}
130
+ defaultOpen={defaultOpen}
131
+ onTitleSave={onTitleSave}
132
+ onDismissNew={onDismissNew}
133
+ onDraftUpdate={onDraftUpdate}
134
+ onDraftCreate={onDraftCreate}
135
+ onDraftDiscard={onDraftDiscard}
136
+ onOpenChange={onOpenChange}
137
+ >
138
+ <button
139
+ onPointerDown={(pointerEvent) =>
140
+ onMovePointerDown?.(pointerEvent, startsOnDay)
141
+ }
142
+ onClick={(clickEvent) => {
143
+ if (shouldSuppressClick?.()) {
144
+ clickEvent.preventDefault();
145
+ clickEvent.stopPropagation();
146
+ }
147
+ }}
148
+ className={`flex h-5 max-w-full items-center truncate rounded-sm text-[10px] font-medium text-foreground outline-none transition-[filter,box-shadow] hover:brightness-110 focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 ${
149
+ compactMarker
150
+ ? "w-5 justify-center px-0"
151
+ : "gap-1 px-1.5 text-left"
152
+ } ${canManipulate ? "cursor-grab" : ""} ${
153
+ isBeingDragged && isDragging ? "cursor-grabbing shadow-lg" : ""
154
+ }`}
155
+ aria-label={`${label}: ${title}`}
156
+ style={{
157
+ backgroundColor: `color-mix(in srgb, ${color} 20%, hsl(var(--background)))`,
158
+ boxShadow: `0 0 0 1px color-mix(in srgb, ${color} 34%, transparent)`,
159
+ }}
160
+ >
161
+ <IconCalendarOff
162
+ aria-hidden="true"
163
+ className="size-3 shrink-0"
164
+ style={{ color }}
165
+ />
166
+ {!compactMarker && <span className="truncate">{title}</span>}
167
+ </button>
168
+ </EventDetailPopover>
169
+ </div>
170
+ {canManipulate && (
171
+ <div
172
+ data-resize-handle="true"
173
+ data-out-of-office-resize="top"
174
+ onPointerDown={(pointerEvent) => {
175
+ pointerEvent.stopPropagation();
176
+ onResizeTopPointerDown?.(pointerEvent);
177
+ }}
178
+ className="pointer-events-auto absolute right-1 z-40 h-1.5 w-5 cursor-n-resize"
179
+ style={{ top: `${top}px`, touchAction: "none" }}
180
+ />
181
+ )}
182
+ {canManipulate && endsOnDay && (
183
+ <div
184
+ data-resize-handle="true"
185
+ data-out-of-office-resize="bottom"
186
+ onPointerDown={(pointerEvent) => {
187
+ pointerEvent.stopPropagation();
188
+ onResizeBottomPointerDown?.(pointerEvent);
189
+ }}
190
+ className="pointer-events-auto absolute right-1 z-40 h-1.5 w-5 cursor-s-resize"
191
+ style={{ top: `${top + height - 6}px`, touchAction: "none" }}
192
+ />
193
+ )}
194
+ </>
195
+ );
196
+ }