@agent-native/core 0.154.4 → 0.155.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/account-health/SKILL.md +76 -0
  3. package/corpus/templates/analytics/.agents/skills/bigquery/SKILL.md +3 -0
  4. package/corpus/templates/analytics/.agents/skills/cross-source-analysis/SKILL.md +2 -1
  5. package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +6 -5
  6. package/corpus/templates/analytics/AGENTS.md +3 -0
  7. package/corpus/templates/analytics/actions/account-deep-dive.ts +3 -2
  8. package/corpus/templates/analytics/actions/bigquery.ts +1 -1
  9. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +4 -1
  10. package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +1 -0
  11. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/PropertyCombobox.tsx +12 -4
  12. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/PropertyValueCombobox.tsx +5 -2
  13. package/corpus/templates/analytics/app/pages/adhoc/explorer/property-label.ts +32 -0
  14. package/corpus/templates/analytics/changelog/2026-08-13-account-health-readouts-verify-customer-scope-and-contract-metrics.md +7 -0
  15. package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +43 -17
  16. package/corpus/templates/analytics/server/lib/analytics-query-catalog.ts +17 -0
  17. package/corpus/templates/analytics/server/plugins/agent-chat.ts +4 -0
  18. package/corpus/templates/calendar/AGENTS.md +2 -0
  19. package/corpus/templates/calendar/DEVELOPING.md +4 -4
  20. package/corpus/templates/calendar/actions/get-settings.ts +4 -11
  21. package/corpus/templates/calendar/actions/update-availability.ts +15 -4
  22. package/corpus/templates/calendar/actions/update-settings.ts +15 -4
  23. package/corpus/templates/calendar/app/components/booking/DatePicker.tsx +9 -3
  24. package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +21 -19
  25. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +9 -6
  26. package/corpus/templates/calendar/app/components/calendar/FindTimePanel.tsx +25 -3
  27. package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +10 -3
  28. package/corpus/templates/calendar/app/components/calendar/MonthView.tsx +9 -9
  29. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +11 -3
  30. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +10 -4
  31. package/corpus/templates/calendar/app/i18n-data.ts +30 -0
  32. package/corpus/templates/calendar/app/lib/availability-schedule.ts +53 -0
  33. package/corpus/templates/calendar/app/lib/event-form-utils.ts +5 -0
  34. package/corpus/templates/calendar/app/pages/AvailabilitySettings.tsx +90 -28
  35. package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +135 -61
  36. package/corpus/templates/calendar/app/pages/BookingPage.tsx +2 -0
  37. package/corpus/templates/calendar/app/pages/CalendarView.tsx +30 -15
  38. package/corpus/templates/calendar/app/pages/Settings.tsx +60 -1
  39. package/corpus/templates/calendar/changelog/2026-08-13-find-a-time-now-closes-from-a-clear-header-action.md +6 -0
  40. package/corpus/templates/calendar/server/handlers/bookings.ts +2 -1
  41. package/corpus/templates/calendar/server/handlers/settings.ts +12 -16
  42. package/corpus/templates/calendar/shared/api.ts +1 -0
  43. package/corpus/templates/calendar/shared/availability-schedule.ts +64 -0
  44. package/corpus/templates/calendar/shared/calendar-week.ts +21 -0
  45. package/corpus/templates/calendar/shared/settings.ts +44 -0
  46. package/corpus/templates/clips/AGENTS.md +2 -0
  47. package/corpus/templates/clips/actions/add-comment.ts +5 -2
  48. package/corpus/templates/clips/actions/finalize-recording.ts +31 -1
  49. package/corpus/templates/clips/actions/get-recording-player-data.ts +9 -0
  50. package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +21 -1
  51. package/corpus/templates/clips/actions/reply-to-comment.ts +5 -2
  52. package/corpus/templates/clips/actions/update-comment.ts +9 -2
  53. package/corpus/templates/clips/app/components/library/search-bar.tsx +26 -13
  54. package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +135 -3
  55. package/corpus/templates/clips/app/components/player/comments-panel.tsx +5 -99
  56. package/corpus/templates/clips/app/components/player/playback-comment-overlay.tsx +7 -3
  57. package/corpus/templates/clips/app/components/player/video-player.tsx +84 -24
  58. package/corpus/templates/clips/app/components/workspace/notifications-list.tsx +11 -3
  59. package/corpus/templates/clips/app/i18n/en-US.ts +12 -0
  60. package/corpus/templates/clips/app/lib/public-meeting.ts +11 -3
  61. package/corpus/templates/clips/app/lib/search-focus.ts +15 -0
  62. package/corpus/templates/clips/app/root.tsx +2 -1
  63. package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +94 -26
  64. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +24 -7
  65. package/corpus/templates/clips/app/routes/embed.$shareId.tsx +8 -4
  66. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +14 -7
  67. package/corpus/templates/clips/app/routes/share.$shareId.tsx +8 -4
  68. package/corpus/templates/clips/app/routes/share.meeting.$meetingId.tsx +72 -26
  69. package/corpus/templates/clips/changelog/2026-08-13-inline-markdown-comments.md +6 -0
  70. package/corpus/templates/clips/changelog/2026-08-13-private-meeting-agent-links.md +6 -0
  71. package/corpus/templates/clips/desktop/src/app.tsx +116 -19
  72. package/corpus/templates/clips/desktop/src/lib/recorder.ts +13 -29
  73. package/corpus/templates/clips/desktop/src/lib/recording-request.ts +39 -0
  74. package/corpus/templates/clips/desktop/src/lib/settings-navigation.ts +11 -0
  75. package/corpus/templates/clips/desktop/src/lib/whisper-model-picker.ts +7 -0
  76. package/corpus/templates/clips/desktop/src/styles.css +117 -0
  77. package/corpus/templates/clips/server/db/index.ts +10 -0
  78. package/corpus/templates/clips/server/db/schema.ts +1 -0
  79. package/corpus/templates/clips/server/lib/recordings.ts +3 -1
  80. package/corpus/templates/clips/server/lib/seekable-media-state.ts +90 -0
  81. package/corpus/templates/clips/server/plugins/db.ts +5 -0
  82. package/corpus/templates/clips/server/routes/api/public-meeting.get.ts +37 -4
  83. package/corpus/templates/clips/server/routes/api/public-recording.get.ts +16 -5
  84. package/corpus/templates/clips/shared/clips-ai-prefs.ts +3 -0
  85. package/corpus/templates/clips/shared/meeting-agent-access.ts +5 -0
  86. package/corpus/templates/content/.agents/skills/content/SKILL.md +2 -2
  87. package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +20 -4
  88. package/corpus/templates/content/actions/_database-row-mutation.ts +1490 -0
  89. package/corpus/templates/content/actions/_database-utils.ts +44 -0
  90. package/corpus/templates/content/actions/_property-utils.ts +4 -1
  91. package/corpus/templates/content/actions/add-comment.ts +2 -1
  92. package/corpus/templates/content/actions/add-database-item.ts +62 -240
  93. package/corpus/templates/content/actions/configure-document-property.ts +165 -1
  94. package/corpus/templates/content/actions/delete-document-property.ts +18 -0
  95. package/corpus/templates/content/actions/set-document-property.ts +78 -1
  96. package/corpus/templates/content/actions/update-comment.ts +1 -1
  97. package/corpus/templates/content/actions/update-database-item.ts +62 -0
  98. package/corpus/templates/content/actions/upsert-database-item-by-key.ts +43 -796
  99. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +24 -15
  100. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +10 -8
  101. package/corpus/templates/content/app/hooks/use-content-database.ts +33 -32
  102. package/corpus/templates/content/changelog/2026-08-10-database-row-actions-now-validate-exact-schemas-and-safe-ret.md +6 -0
  103. package/corpus/templates/content/parity/matrix.md +1 -1
  104. package/corpus/templates/content/parity/matrix.ts +2 -0
  105. package/corpus/templates/content/server/db/schema.ts +39 -3
  106. package/corpus/templates/content/server/plugins/db.ts +30 -0
  107. package/corpus/templates/content/shared/api.ts +75 -2
  108. package/corpus/templates/design/actions/generate-design.ts +10 -7
  109. package/corpus/templates/design/actions/present-design-variants.ts +35 -6
  110. package/corpus/templates/design/actions/run-design-audit.ts +33 -0
  111. package/corpus/templates/design/changelog/2026-08-13-the-design-audit-now-catches-a-screen-that-renders-behind-an.md +6 -0
  112. package/corpus/templates/design/shared/design-review.ts +1 -0
  113. package/corpus/templates/design/shared/html-integrity.ts +66 -0
  114. package/corpus/templates/dispatch/.env.example +5 -0
  115. package/corpus/templates/dispatch/AGENTS.md +24 -19
  116. package/corpus/templates/dispatch/app/i18n-data.ts +20 -0
  117. package/corpus/templates/dispatch/server/lib/identity-sso.ts +21 -90
  118. package/corpus/templates/dispatch/server/plugins/identity-sso.ts +3 -12
  119. package/corpus/templates/dispatch/shared/feature-flags.ts +4 -0
  120. package/corpus/templates/factory/AGENTS.md +5 -1
  121. package/corpus/templates/factory/README.md +23 -0
  122. package/corpus/templates/factory/actions/view-screen.ts +17 -0
  123. package/corpus/templates/factory/app/components/factory/FactoryAgentsView.tsx +239 -0
  124. package/corpus/templates/factory/app/components/factory/FactoryCanvas.tsx +2 -0
  125. package/corpus/templates/factory/app/components/factory/FactoryInspector.tsx +130 -7
  126. package/corpus/templates/factory/app/i18n/en-US.ts +20 -0
  127. package/corpus/templates/factory/app/routes/factory.tsx +41 -0
  128. package/corpus/templates/factory/package.json +1 -0
  129. package/corpus/templates/factory/server/factory-graph/contracts.ts +2 -0
  130. package/corpus/templates/factory/server/plugins/agent-chat.ts +9 -0
  131. package/corpus/templates/factory/server/plugins/dispatch-db.ts +1 -0
  132. package/corpus/templates/factory/server/plugins/setup-dispatch.ts +3 -0
  133. package/corpus/templates/mail/app/lib/native-mcp-exclusions.ts +3 -0
  134. package/corpus/templates/mail/vite.config.ts +7 -0
  135. package/corpus/templates/plan/actions/reply-to-plan-comment.ts +7 -2
  136. package/corpus/templates/plan/actions/resolve-plan-comment.ts +2 -2
  137. package/corpus/templates/plan/actions/update-visual-plan.ts +1 -1
  138. package/corpus/templates/plan/app/pages/PlansPage.tsx +29 -20
  139. package/corpus/templates/slides/actions/add-slide-comment.ts +1 -1
  140. package/corpus/templates/slides/actions/update-slide-comment.ts +1 -1
  141. package/corpus/templates/slides/app/components/comments/SlideCommentsPanel.tsx +9 -6
  142. package/dist/action.d.ts +22 -0
  143. package/dist/action.js +27 -0
  144. package/dist/agent/production-agent.js +5 -1
  145. package/dist/agent/thread-data-builder.js +17 -1
  146. package/dist/cli/skills-content/help.js +6 -4
  147. package/dist/cli/skills.js +17 -2
  148. package/dist/cli/workspacify.d.ts +5 -0
  149. package/dist/cli/workspacify.js +65 -8
  150. package/dist/client/AgentPanel.d.ts +5 -0
  151. package/dist/client/AgentPanel.js +20 -4
  152. package/dist/client/AssistantChat.d.ts +21 -0
  153. package/dist/client/AssistantChat.js +66 -68
  154. package/dist/client/active-run-state.d.ts +14 -1
  155. package/dist/client/active-run-state.js +0 -0
  156. package/dist/client/agent-chat-adapter.js +107 -5
  157. package/dist/client/analytics.js +9 -2
  158. package/dist/client/chat/tool-call-display.js +4 -4
  159. package/dist/client/chat-first/apps-rail.js +9 -4
  160. package/dist/client/chat-first/primary-nav.d.ts +2 -1
  161. package/dist/client/chat-first/primary-nav.js +3 -3
  162. package/dist/client/chat-first/types.d.ts +5 -1
  163. package/dist/client/markdown/InlineMarkdown.d.ts +23 -0
  164. package/dist/client/markdown/InlineMarkdown.js +65 -0
  165. package/dist/client/markdown/index.d.ts +1 -0
  166. package/dist/client/markdown/index.js +1 -0
  167. package/dist/client/review/ReviewThreadPanel.js +2 -1
  168. package/dist/client/run-stream-ownership.d.ts +39 -0
  169. package/dist/client/run-stream-ownership.js +65 -0
  170. package/dist/collab/routes.d.ts +1 -1
  171. package/dist/collab/struct-routes.d.ts +1 -1
  172. package/dist/feature-flags/index.d.ts +1 -1
  173. package/dist/feature-flags/index.js +1 -1
  174. package/dist/feature-flags/store.d.ts +10 -0
  175. package/dist/feature-flags/store.js +112 -1
  176. package/dist/guards/no-env-credentials.js +4 -0
  177. package/dist/index.browser.d.ts +1 -1
  178. package/dist/index.browser.js +1 -1
  179. package/dist/index.d.ts +2 -2
  180. package/dist/index.js +2 -2
  181. package/dist/integrations/adapters/slack.js +5 -1
  182. package/dist/localization/default-messages.d.ts +1 -0
  183. package/dist/localization/default-messages.js +1 -0
  184. package/dist/notifications/routes.d.ts +1 -1
  185. package/dist/observability/routes.d.ts +1 -1
  186. package/dist/progress/routes.d.ts +1 -1
  187. package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
  188. package/dist/provider-api/actions/provider-api.d.ts +15 -15
  189. package/dist/resources/agents.js +40 -5
  190. package/dist/resources/handlers.d.ts +1 -1
  191. package/dist/resources/metadata.d.ts +10 -0
  192. package/dist/resources/metadata.js +7 -3
  193. package/dist/review/actions/create-review-comment.js +1 -1
  194. package/dist/review/actions/reply-review-comment.js +1 -1
  195. package/dist/review/actions/resolve-review-thread.js +1 -1
  196. package/dist/server/action-routes.js +42 -8
  197. package/dist/server/agent-discovery.js +14 -3
  198. package/dist/server/core-routes-plugin.js +1 -1
  199. package/dist/server/identity-sso-migrations.d.ts +7 -0
  200. package/dist/server/identity-sso-migrations.js +36 -0
  201. package/dist/server/identity-sso-store.d.ts +3 -2
  202. package/dist/server/identity-sso-store.js +8 -3
  203. package/dist/server/realtime-token.d.ts +1 -1
  204. package/dist/server/release-migrations.js +4 -0
  205. package/dist/tracking/providers.js +13 -3
  206. package/dist/workspace-files/tool.js +1 -1
  207. package/package.json +3 -2
@@ -1,5 +1,6 @@
1
1
  import { useT } from "@agent-native/core/client/i18n";
2
2
  import type { AvailabilityConfig, DaySchedule } from "@shared/api";
3
+ import { IconPlus, IconTrash } from "@tabler/icons-react";
3
4
  import { useState, useEffect } from "react";
4
5
  import { toast } from "sonner";
5
6
 
@@ -21,6 +22,14 @@ import {
21
22
  useUpdateAvailability,
22
23
  } from "@/hooks/use-availability";
23
24
  import { useDbStatus } from "@/hooks/use-db-status";
25
+ import {
26
+ DEFAULT_TIME_SLOT,
27
+ addTimeSlot,
28
+ getEditableTimeSlots,
29
+ removeTimeSlot,
30
+ setDayEnabled,
31
+ updateTimeSlot,
32
+ } from "@/lib/availability-schedule";
24
33
  import { copyTextToClipboard } from "@/lib/clipboard";
25
34
 
26
35
  type DayName = keyof AvailabilityConfig["weeklySchedule"];
@@ -37,7 +46,7 @@ const DAYS: { key: DayName }[] = [
37
46
 
38
47
  const DEFAULT_SCHEDULE: DaySchedule = {
39
48
  enabled: false,
40
- slots: [{ start: "09:00", end: "17:00" }],
49
+ slots: [{ ...DEFAULT_TIME_SLOT }],
41
50
  };
42
51
 
43
52
  export default function AvailabilitySettings() {
@@ -93,17 +102,36 @@ export default function AvailabilitySettings() {
93
102
  function updateDay(day: DayName, updates: Partial<DaySchedule>) {
94
103
  setSchedule((prev) => ({
95
104
  ...prev,
96
- [day]: { ...prev[day], ...updates },
105
+ [day]:
106
+ typeof updates.enabled === "boolean"
107
+ ? setDayEnabled(prev[day], updates.enabled)
108
+ : { ...prev[day], ...updates },
97
109
  }));
98
110
  }
99
111
 
100
- function updateDaySlot(day: DayName, field: "start" | "end", value: string) {
112
+ function updateDaySlot(
113
+ day: DayName,
114
+ slotIndex: number,
115
+ field: "start" | "end",
116
+ value: string,
117
+ ) {
101
118
  setSchedule((prev) => ({
102
119
  ...prev,
103
- [day]: {
104
- ...prev[day],
105
- slots: [{ ...prev[day].slots[0], [field]: value }],
106
- },
120
+ [day]: updateTimeSlot(prev[day], slotIndex, field, value),
121
+ }));
122
+ }
123
+
124
+ function addDaySlot(day: DayName) {
125
+ setSchedule((prev) => ({
126
+ ...prev,
127
+ [day]: addTimeSlot(prev[day]),
128
+ }));
129
+ }
130
+
131
+ function removeDaySlot(day: DayName, slotIndex: number) {
132
+ setSchedule((prev) => ({
133
+ ...prev,
134
+ [day]: removeTimeSlot(prev[day], slotIndex),
107
135
  }));
108
136
  }
109
137
 
@@ -181,7 +209,7 @@ export default function AvailabilitySettings() {
181
209
  </div>
182
210
  {DAYS.map(({ key }) => {
183
211
  const day = schedule[key];
184
- const slot = day.slots[0] ?? { start: "09:00", end: "17:00" };
212
+ const slots = getEditableTimeSlots(day);
185
213
  return (
186
214
  <div
187
215
  key={key}
@@ -198,26 +226,60 @@ export default function AvailabilitySettings() {
198
226
  </div>
199
227
 
200
228
  {day.enabled ? (
201
- <div className="flex items-center gap-2">
202
- <Input
203
- type="time"
204
- value={slot.start}
205
- onChange={(e) =>
206
- updateDaySlot(key, "start", e.target.value)
207
- }
208
- className="w-28 sm:w-32"
209
- />
210
- <span className="text-muted-foreground">
211
- {t("bookingLinks.to")}
212
- </span>
213
- <Input
214
- type="time"
215
- value={slot.end}
216
- onChange={(e) =>
217
- updateDaySlot(key, "end", e.target.value)
218
- }
219
- className="w-28 sm:w-32"
220
- />
229
+ <div className="min-w-0 flex-1 space-y-2">
230
+ {slots.map((slot, slotIndex) => (
231
+ <div
232
+ key={`${key}-${slotIndex}`}
233
+ className="flex flex-wrap items-center gap-2"
234
+ >
235
+ <Input
236
+ type="time"
237
+ value={slot.start}
238
+ onChange={(e) =>
239
+ updateDaySlot(
240
+ key,
241
+ slotIndex,
242
+ "start",
243
+ e.target.value,
244
+ )
245
+ }
246
+ className="w-28 sm:w-32"
247
+ />
248
+ <span className="text-muted-foreground">
249
+ {t("bookingLinks.to")}
250
+ </span>
251
+ <Input
252
+ type="time"
253
+ value={slot.end}
254
+ onChange={(e) =>
255
+ updateDaySlot(key, slotIndex, "end", e.target.value)
256
+ }
257
+ className="w-28 sm:w-32"
258
+ />
259
+ {slots.length > 1 && (
260
+ <Button
261
+ type="button"
262
+ variant="ghost"
263
+ size="sm"
264
+ className="h-8 px-2 text-muted-foreground hover:text-destructive"
265
+ onClick={() => removeDaySlot(key, slotIndex)}
266
+ >
267
+ <IconTrash className="mr-1.5 h-3.5 w-3.5" />
268
+ {t("eventForm.delete")}
269
+ </Button>
270
+ )}
271
+ </div>
272
+ ))}
273
+ <Button
274
+ type="button"
275
+ variant="ghost"
276
+ size="sm"
277
+ className="h-8 px-2"
278
+ onClick={() => addDaySlot(key)}
279
+ >
280
+ <IconPlus className="mr-1.5 h-3.5 w-3.5" />
281
+ {t("bookingLinks.add")}
282
+ </Button>
221
283
  </div>
222
284
  ) : (
223
285
  <span className="text-sm text-muted-foreground">
@@ -14,6 +14,7 @@ import type {
14
14
  CustomField,
15
15
  DaySchedule,
16
16
  } from "@shared/api";
17
+ import { getWeekdayOrder, getWeekStartsOn } from "@shared/calendar-week";
17
18
  import {
18
19
  IconBrandGoogle,
19
20
  IconBrandZoom,
@@ -113,7 +114,16 @@ import {
113
114
  OPTIMISTIC_PREFIX,
114
115
  } from "@/hooks/use-booking-links";
115
116
  import { useGoogleAuthStatus } from "@/hooks/use-google-auth";
117
+ import { useSettings } from "@/hooks/use-settings";
116
118
  import { useZoomStatus, useConnectZoom } from "@/hooks/use-zoom-auth";
119
+ import {
120
+ DEFAULT_TIME_SLOT,
121
+ addTimeSlot,
122
+ getEditableTimeSlots,
123
+ removeTimeSlot,
124
+ setDayEnabled,
125
+ updateTimeSlot,
126
+ } from "@/lib/availability-schedule";
117
127
  import { copyTextToClipboard } from "@/lib/clipboard";
118
128
  import { cn } from "@/lib/utils";
119
129
 
@@ -175,7 +185,7 @@ const DAYS: { key: DayName }[] = [
175
185
 
176
186
  const DEFAULT_SCHEDULE: DaySchedule = {
177
187
  enabled: false,
178
- slots: [{ start: "09:00", end: "17:00" }],
188
+ slots: [{ ...DEFAULT_TIME_SLOT }],
179
189
  };
180
190
 
181
191
  type Tab = "links" | "availability" | "bookings";
@@ -290,11 +300,12 @@ function formatAvailabilitySummary(
290
300
  dayLabel = enabledDays.map((d) => shortNames[d]).join(", ");
291
301
  }
292
302
 
293
- // Find common time range
294
- const slot = ws[enabledDays[0]].slots[0];
295
- if (!slot) return dayLabel;
303
+ const slots = ws[enabledDays[0]].slots;
304
+ if (slots.length === 0) return dayLabel;
296
305
 
297
- return `${dayLabel}, ${formatTime12(slot.start)} - ${formatTime12(slot.end)}`;
306
+ return `${dayLabel}, ${slots
307
+ .map((slot) => `${formatTime12(slot.start)} - ${formatTime12(slot.end)}`)
308
+ .join(", ")}`;
298
309
  }
299
310
 
300
311
  function BookingLinksListSkeleton() {
@@ -697,17 +708,36 @@ export default function BookingLinksPage({
697
708
  function updateDay(day: DayName, updates: Partial<DaySchedule>) {
698
709
  setSchedule((prev) => ({
699
710
  ...prev,
700
- [day]: { ...prev[day], ...updates },
711
+ [day]:
712
+ typeof updates.enabled === "boolean"
713
+ ? setDayEnabled(prev[day], updates.enabled)
714
+ : { ...prev[day], ...updates },
701
715
  }));
702
716
  }
703
717
 
704
- function updateDaySlot(day: DayName, field: "start" | "end", value: string) {
718
+ function updateDaySlot(
719
+ day: DayName,
720
+ slotIndex: number,
721
+ field: "start" | "end",
722
+ value: string,
723
+ ) {
705
724
  setSchedule((prev) => ({
706
725
  ...prev,
707
- [day]: {
708
- ...prev[day],
709
- slots: [{ ...prev[day].slots[0], [field]: value }],
710
- },
726
+ [day]: updateTimeSlot(prev[day], slotIndex, field, value),
727
+ }));
728
+ }
729
+
730
+ function addDaySlot(day: DayName) {
731
+ setSchedule((prev) => ({
732
+ ...prev,
733
+ [day]: addTimeSlot(prev[day]),
734
+ }));
735
+ }
736
+
737
+ function removeDaySlot(day: DayName, slotIndex: number) {
738
+ setSchedule((prev) => ({
739
+ ...prev,
740
+ [day]: removeTimeSlot(prev[day], slotIndex),
711
741
  }));
712
742
  }
713
743
 
@@ -1756,7 +1786,7 @@ export default function BookingLinksPage({
1756
1786
  </div>
1757
1787
  {DAYS.map(({ key }) => {
1758
1788
  const day = schedule[key];
1759
- const slot = day.slots[0] ?? { start: "09:00", end: "17:00" };
1789
+ const slots = getEditableTimeSlots(day);
1760
1790
  const label = t(`bookingLinks.days.${key}`);
1761
1791
  const short = t(`bookingLinks.days.${key}Short`);
1762
1792
  return (
@@ -1778,26 +1808,65 @@ export default function BookingLinksPage({
1778
1808
  </div>
1779
1809
 
1780
1810
  {day.enabled ? (
1781
- <div className="flex items-center gap-2">
1782
- <Input
1783
- type="time"
1784
- value={slot.start}
1785
- onChange={(e) =>
1786
- updateDaySlot(key, "start", e.target.value)
1787
- }
1788
- className="w-28 sm:w-32"
1789
- />
1790
- <span className="text-muted-foreground">
1791
- {t("bookingLinks.to")}
1792
- </span>
1793
- <Input
1794
- type="time"
1795
- value={slot.end}
1796
- onChange={(e) =>
1797
- updateDaySlot(key, "end", e.target.value)
1798
- }
1799
- className="w-28 sm:w-32"
1800
- />
1811
+ <div className="min-w-0 flex-1 space-y-2">
1812
+ {slots.map((slot, slotIndex) => (
1813
+ <div
1814
+ key={`${key}-${slotIndex}`}
1815
+ className="flex flex-wrap items-center gap-2"
1816
+ >
1817
+ <Input
1818
+ type="time"
1819
+ value={slot.start}
1820
+ onChange={(e) =>
1821
+ updateDaySlot(
1822
+ key,
1823
+ slotIndex,
1824
+ "start",
1825
+ e.target.value,
1826
+ )
1827
+ }
1828
+ className="w-28 sm:w-32"
1829
+ />
1830
+ <span className="text-muted-foreground">
1831
+ {t("bookingLinks.to")}
1832
+ </span>
1833
+ <Input
1834
+ type="time"
1835
+ value={slot.end}
1836
+ onChange={(e) =>
1837
+ updateDaySlot(
1838
+ key,
1839
+ slotIndex,
1840
+ "end",
1841
+ e.target.value,
1842
+ )
1843
+ }
1844
+ className="w-28 sm:w-32"
1845
+ />
1846
+ {slots.length > 1 && (
1847
+ <Button
1848
+ type="button"
1849
+ variant="ghost"
1850
+ size="sm"
1851
+ className="h-8 px-2 text-muted-foreground hover:text-destructive"
1852
+ onClick={() => removeDaySlot(key, slotIndex)}
1853
+ >
1854
+ <IconTrash className="mr-1.5 h-3.5 w-3.5" />
1855
+ {t("eventForm.delete")}
1856
+ </Button>
1857
+ )}
1858
+ </div>
1859
+ ))}
1860
+ <Button
1861
+ type="button"
1862
+ variant="ghost"
1863
+ size="sm"
1864
+ className="h-8 px-2"
1865
+ onClick={() => addDaySlot(key)}
1866
+ >
1867
+ <IconPlus className="mr-1.5 h-3.5 w-3.5" />
1868
+ {t("bookingLinks.add")}
1869
+ </Button>
1801
1870
  </div>
1802
1871
  ) : (
1803
1872
  <span className="text-sm text-muted-foreground">
@@ -1971,6 +2040,8 @@ function BookingPreview({
1971
2040
  onCollapse?: () => void;
1972
2041
  }) {
1973
2042
  const t = useT();
2043
+ const { data: settings } = useSettings();
2044
+ const weekStartsOn = getWeekStartsOn(settings?.weekStart);
1974
2045
  const displayTitle = title.trim() || t("bookingLinks.untitledMeeting");
1975
2046
  const hasDurationChoice = durations.length > 1;
1976
2047
  const primaryDuration = durations[0] ?? 30;
@@ -2005,8 +2076,8 @@ function BookingPreview({
2005
2076
  // Calendar data for viewed month
2006
2077
  const monthStart = startOfMonth(viewMonth);
2007
2078
  const monthEnd = endOfMonth(viewMonth);
2008
- const calStart = startOfWeek(monthStart);
2009
- const calEnd = endOfWeek(monthEnd);
2079
+ const calStart = startOfWeek(monthStart, { weekStartsOn });
2080
+ const calEnd = endOfWeek(monthEnd, { weekStartsOn });
2010
2081
  const calDays = eachDayOfInterval({ start: calStart, end: calEnd });
2011
2082
 
2012
2083
  function isDayDisabled(day: Date) {
@@ -2027,28 +2098,28 @@ function BookingPreview({
2027
2098
  const dayName = DAY_MAP[getDay(selectedDate)];
2028
2099
  const daySchedule = availability.weeklySchedule[dayName];
2029
2100
  if (!daySchedule?.enabled) return [];
2030
- const slot = daySchedule.slots[0];
2031
- if (!slot) return [];
2032
2101
 
2033
2102
  const dur = selectedDuration ?? primaryDuration;
2034
- const [startH, startM] = slot.start.split(":").map(Number);
2035
- const [endH, endM] = slot.end.split(":").map(Number);
2036
- const startMin = startH * 60 + startM;
2037
- const endMin = endH * 60 + endM;
2038
2103
  const slots: string[] = [];
2039
- const firstStart =
2040
- Math.ceil(startMin / BOOKING_SLOT_STEP_MINUTES) *
2041
- BOOKING_SLOT_STEP_MINUTES;
2042
- for (
2043
- let m = firstStart;
2044
- m + dur <= endMin;
2045
- m += BOOKING_SLOT_STEP_MINUTES
2046
- ) {
2047
- const h = Math.floor(m / 60);
2048
- const mm = m % 60;
2049
- const ampm = h >= 12 ? "PM" : "AM";
2050
- const h12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
2051
- slots.push(`${h12}:${mm.toString().padStart(2, "0")} ${ampm}`);
2104
+ for (const slot of daySchedule.slots) {
2105
+ const [startH, startM] = slot.start.split(":").map(Number);
2106
+ const [endH, endM] = slot.end.split(":").map(Number);
2107
+ const startMin = startH * 60 + startM;
2108
+ const endMin = endH * 60 + endM;
2109
+ const firstStart =
2110
+ Math.ceil(startMin / BOOKING_SLOT_STEP_MINUTES) *
2111
+ BOOKING_SLOT_STEP_MINUTES;
2112
+ for (
2113
+ let m = firstStart;
2114
+ m + dur <= endMin;
2115
+ m += BOOKING_SLOT_STEP_MINUTES
2116
+ ) {
2117
+ const h = Math.floor(m / 60);
2118
+ const mm = m % 60;
2119
+ const ampm = h >= 12 ? "PM" : "AM";
2120
+ const h12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
2121
+ slots.push(`${h12}:${mm.toString().padStart(2, "0")} ${ampm}`);
2122
+ }
2052
2123
  }
2053
2124
  return slots;
2054
2125
  }, [selectedDate, selectedDuration, primaryDuration, availability]);
@@ -2307,14 +2378,17 @@ function BookingPreview({
2307
2378
 
2308
2379
  {/* Weekday headers */}
2309
2380
  <div className="grid grid-cols-7 mb-0.5">
2310
- {WEEKDAY_HEADER_KEYS.map((dayKey) => (
2311
- <div
2312
- key={dayKey}
2313
- className="py-0.5 text-center text-[10px] font-medium text-muted-foreground/60"
2314
- >
2315
- {t(`bookingLinks.days.${dayKey}`)}
2316
- </div>
2317
- ))}
2381
+ {getWeekdayOrder(weekStartsOn).map((day) => {
2382
+ const dayKey = WEEKDAY_HEADER_KEYS[day];
2383
+ return (
2384
+ <div
2385
+ key={dayKey}
2386
+ className="py-0.5 text-center text-[10px] font-medium text-muted-foreground/60"
2387
+ >
2388
+ {t(`bookingLinks.days.${dayKey}`)}
2389
+ </div>
2390
+ );
2391
+ })}
2318
2392
  </div>
2319
2393
 
2320
2394
  {/* Days grid */}
@@ -5,6 +5,7 @@ import {
5
5
  StarfieldBackground,
6
6
  } from "@agent-native/core/client/ui";
7
7
  import type { Booking } from "@shared/api";
8
+ import { getWeekStartsOn } from "@shared/calendar-week";
8
9
  import { IconAlertTriangle, IconCalendar } from "@tabler/icons-react";
9
10
  import {
10
11
  addMinutes,
@@ -432,6 +433,7 @@ export default function BookingPage() {
432
433
  availabilityLoading={availableDatesLoading}
433
434
  viewMonth={viewMonth}
434
435
  onViewMonthChange={setViewMonth}
436
+ weekStartsOn={getWeekStartsOn(settings?.weekStart)}
435
437
  />
436
438
  </div>
437
439
  )}
@@ -6,6 +6,7 @@ import type {
6
6
  CalendarEventDraft,
7
7
  UpdateEventScope,
8
8
  } from "@shared/api";
9
+ import { getWeekStartsOn } from "@shared/calendar-week";
9
10
  import {
10
11
  IconCheck,
11
12
  IconChevronLeft,
@@ -89,8 +90,8 @@ import {
89
90
  buildEventTitleUpdate,
90
91
  dateTimeInTimezoneToIso,
91
92
  getEditableEventTitle,
92
- getLocalTimezone,
93
93
  UNNAMED_EVENT_TITLE,
94
+ resolveEventTimezone,
94
95
  } from "@/lib/event-form-utils";
95
96
  import { buildDeleteEventMutationInput } from "@/lib/event-mutation-inputs";
96
97
  import { getLocationSuggestions } from "@/lib/location-suggestions";
@@ -373,6 +374,7 @@ export default function CalendarView() {
373
374
  const defaultAccountEmail = googleStatus.data?.accounts?.[0]?.email;
374
375
  const settingsQuery = useSettings();
375
376
  const { data: settings } = settingsQuery;
377
+ const weekStartsOn = getWeekStartsOn(settings?.weekStart);
376
378
  const { data: rawOverlayPeople } = useOverlayPeople();
377
379
  const overlayPeople = Array.isArray(rawOverlayPeople) ? rawOverlayPeople : [];
378
380
  const overlayEmails = useMemo(
@@ -398,14 +400,14 @@ export default function CalendarView() {
398
400
  const ms = startOfMonth(selectedDate);
399
401
  const me = endOfMonth(selectedDate);
400
402
  return {
401
- from: startOfWeek(ms).toISOString(),
402
- to: endOfWeek(me).toISOString(),
403
+ from: startOfWeek(ms, { weekStartsOn }).toISOString(),
404
+ to: endOfWeek(me, { weekStartsOn }).toISOString(),
403
405
  };
404
406
  }
405
407
  case "week": {
406
408
  return {
407
- from: startOfWeek(selectedDate).toISOString(),
408
- to: endOfWeek(selectedDate).toISOString(),
409
+ from: startOfWeek(selectedDate, { weekStartsOn }).toISOString(),
410
+ to: endOfWeek(selectedDate, { weekStartsOn }).toISOString(),
409
411
  };
410
412
  }
411
413
  case "day": {
@@ -416,7 +418,7 @@ export default function CalendarView() {
416
418
  return { from: dayStart.toISOString(), to: dayEnd.toISOString() };
417
419
  }
418
420
  }
419
- }, [viewMode, selectedDate]);
421
+ }, [viewMode, selectedDate, weekStartsOn]);
420
422
 
421
423
  const {
422
424
  data: rawEventsData,
@@ -470,8 +472,8 @@ export default function CalendarView() {
470
472
  const next = addMonths(selectedDate, 1);
471
473
  const prev = subMonths(selectedDate, 1);
472
474
  return [next, prev].map((d) => ({
473
- from: startOfWeek(startOfMonth(d)).toISOString(),
474
- to: endOfWeek(endOfMonth(d)).toISOString(),
475
+ from: startOfWeek(startOfMonth(d), { weekStartsOn }).toISOString(),
476
+ to: endOfWeek(endOfMonth(d), { weekStartsOn }).toISOString(),
475
477
  }));
476
478
  }
477
479
  case "week": {
@@ -480,8 +482,8 @@ export default function CalendarView() {
480
482
  const next2 = addWeeks(selectedDate, 2);
481
483
  const prev = subWeeks(selectedDate, 1);
482
484
  return [next, next2, prev].map((d) => ({
483
- from: startOfWeek(d).toISOString(),
484
- to: endOfWeek(d).toISOString(),
485
+ from: startOfWeek(d, { weekStartsOn }).toISOString(),
486
+ to: endOfWeek(d, { weekStartsOn }).toISOString(),
485
487
  }));
486
488
  }
487
489
  case "day": {
@@ -500,7 +502,14 @@ export default function CalendarView() {
500
502
  for (const range of ranges) {
501
503
  void prefetchEvents(queryClient, range.from, range.to, overlayEmails);
502
504
  }
503
- }, [isLoading, viewMode, selectedDate, overlayEmails, queryClient]);
505
+ }, [
506
+ isLoading,
507
+ viewMode,
508
+ selectedDate,
509
+ overlayEmails,
510
+ queryClient,
511
+ weekStartsOn,
512
+ ]);
504
513
 
505
514
  // Show the skeleton only when there is genuinely nothing to show for the
506
515
  // current date range — the first load, or navigating to a range we have not
@@ -675,7 +684,9 @@ export default function CalendarView() {
675
684
  }
676
685
 
677
686
  const location = draft.location ?? draft.workingLocationLabel ?? "";
678
- const timezone = draft.startTimeZone ?? getLocalTimezone();
687
+ const timezone = resolveEventTimezone(
688
+ draft.startTimeZone ?? draft.endTimeZone,
689
+ );
679
690
  const semanticFullDay =
680
691
  eventType === "outOfOffice" &&
681
692
  draft.fullDay === true &&
@@ -1143,7 +1154,9 @@ export default function CalendarView() {
1143
1154
  if (newEnd.getTime() <= newStart.getTime()) return;
1144
1155
 
1145
1156
  if (calendarDraftIdFromEventId(eventId)) {
1146
- const timezone = settings?.timezone || getLocalTimezone();
1157
+ const timezone = resolveEventTimezone(
1158
+ event.startTimeZone ?? event.endTimeZone,
1159
+ );
1147
1160
  updateDraftEvent(eventId, {
1148
1161
  start: newStart.toISOString(),
1149
1162
  end: newEnd.toISOString(),
@@ -1592,8 +1605,8 @@ export default function CalendarView() {
1592
1605
  ? format(selectedDate, "MMM yyyy")
1593
1606
  : format(selectedDate, "MMMM yyyy");
1594
1607
  case "week": {
1595
- const ws = startOfWeek(selectedDate);
1596
- const we = endOfWeek(selectedDate);
1608
+ const ws = startOfWeek(selectedDate, { weekStartsOn });
1609
+ const we = endOfWeek(selectedDate, { weekStartsOn });
1597
1610
  return isMobile
1598
1611
  ? `${format(ws, "MMM d")} – ${format(we, "d")}`
1599
1612
  : `${format(ws, "MMM d")} – ${format(we, "d, yyyy")}`;
@@ -1801,6 +1814,7 @@ export default function CalendarView() {
1801
1814
  onDraftCreate={createDraftEvent}
1802
1815
  onDraftDiscard={discardDraftEvent}
1803
1816
  isLoading={eventsLoading}
1817
+ weekStartsOn={weekStartsOn}
1804
1818
  />
1805
1819
  )}
1806
1820
  {viewMode === "week" && (
@@ -1819,6 +1833,7 @@ export default function CalendarView() {
1819
1833
  onDraftCreate={createDraftEvent}
1820
1834
  onDraftDiscard={discardDraftEvent}
1821
1835
  isLoading={eventsLoading}
1836
+ weekStartsOn={weekStartsOn}
1822
1837
  />
1823
1838
  )}
1824
1839
  {viewMode === "day" && (