@agent-native/core 0.148.1 → 0.149.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/changelog/2026-08-10-wide-tables-in-analytics-chat-now-scroll-horizontally-within.md +6 -0
  3. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +13 -53
  4. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +11 -70
  5. package/corpus/templates/calendar/app/lib/event-layout.ts +122 -0
  6. package/corpus/templates/calendar/app/routes/_app.availability.tsx +1 -1
  7. package/corpus/templates/calendar/changelog/2026-08-10-opening-availability-settings-from-the-agent-lands-on-the-av.md +6 -0
  8. package/corpus/templates/calendar/vitest.config.ts +6 -1
  9. package/corpus/templates/clips/actions/lib/audio-only-transcription.ts +3 -1
  10. package/corpus/templates/clips/app/components/player/video-player.tsx +44 -4
  11. package/corpus/templates/clips/changelog/2026-08-10-fixed-transcription-failures-for-recordings-without-captured.md +6 -0
  12. package/corpus/templates/clips/changelog/2026-08-10-mobile-clips-can-now-enter-fullscreen-on-iphone-safari.md +6 -0
  13. package/corpus/templates/content/actions/_property-utils.ts +11 -1
  14. package/corpus/templates/content/actions/view-screen.ts +62 -12
  15. package/corpus/templates/content/server/plugins/agent-chat.ts +2 -0
  16. package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +22 -0
  17. package/corpus/templates/design/AGENTS.md +3 -0
  18. package/corpus/templates/design/actions/create-design-from-template.ts +19 -0
  19. package/corpus/templates/design/actions/get-design-snapshot.ts +18 -0
  20. package/corpus/templates/design/actions/get-design-template.ts +178 -0
  21. package/corpus/templates/design/actions/list-designs.ts +3 -1
  22. package/corpus/templates/design/actions/view-screen.ts +31 -0
  23. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +225 -214
  24. package/corpus/templates/design/app/components/design/multi-screen/types.ts +3 -0
  25. package/corpus/templates/design/app/i18n-data.ts +78 -0
  26. package/corpus/templates/design/app/lib/design-authors.ts +64 -0
  27. package/corpus/templates/design/app/pages/Index.tsx +114 -4
  28. package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +1 -0
  29. package/corpus/templates/design/changelog/2026-08-07-alt-dragging-an-element-to-copy-it-now-keeps-the-copy-visibl.md +6 -0
  30. package/corpus/templates/design/changelog/2026-08-07-design-cards-now-show-who-created-each-design-with-a-filter-.md +6 -0
  31. package/corpus/templates/design/changelog/2026-08-07-drawing-a-shape-on-the-overview-canvas-now-keeps-the-shape-s.md +6 -0
  32. package/corpus/templates/design/changelog/2026-08-07-hold-option-alt-while-dragging-a-frame-on-the-overview-canva.md +6 -0
  33. package/corpus/templates/design/server/lib/design-template-data.ts +143 -0
  34. package/corpus/templates/design/server/plugins/agent-chat.ts +1 -0
  35. package/corpus/templates/forms/AGENTS.md +2 -2
  36. package/corpus/templates/forms/actions/create-form.ts +1 -1
  37. package/corpus/templates/forms/actions/update-form.ts +5 -5
  38. package/corpus/templates/forms/changelog/2026-08-10-forms-now-keep-agent-applied-field-changes-visible-after-an-.md +6 -0
  39. package/corpus/templates/forms/changelog/2026-08-10-published-form-links-always-use-the-working-public-form-url.md +6 -0
  40. package/corpus/templates/forms/server/plugins/agent-chat.ts +2 -2
  41. package/corpus/templates/slides/AGENTS.md +4 -3
  42. package/corpus/templates/slides/actions/import-google-slides-reference.ts +56 -10
  43. package/corpus/templates/slides/actions/patch-deck.ts +18 -2
  44. package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +46 -0
  45. package/corpus/templates/slides/app/components/editor/BlockBubbleMenu.tsx +194 -3
  46. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +22 -75
  47. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +5 -3
  48. package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +5 -1
  49. package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +15 -4
  50. package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +116 -55
  51. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +27 -4
  52. package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +45 -0
  53. package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +143 -18
  54. package/corpus/templates/slides/app/i18n/en-US.ts +6 -0
  55. package/corpus/templates/slides/app/lib/export-pdf-client.ts +84 -25
  56. package/corpus/templates/slides/app/lib/generation-state.ts +7 -1
  57. package/corpus/templates/slides/app/lib/import-uploaded-deck.ts +11 -0
  58. package/corpus/templates/slides/app/pages/DeckEditor.tsx +44 -14
  59. package/corpus/templates/slides/app/pages/Index.tsx +124 -135
  60. package/corpus/templates/slides/changelog/2026-08-08-google-slides-export-now-tells-you-why-a-deck-fell-back-to-a.md +6 -0
  61. package/corpus/templates/slides/changelog/2026-08-09-delete-key-now-removes-a-selected-image.md +6 -0
  62. package/corpus/templates/slides/changelog/2026-08-09-editing-a-slide-in-an-imported-deck-no-longer-fails-to-save.md +6 -0
  63. package/corpus/templates/slides/changelog/2026-08-09-fixed-pdf-and-custom-size-powerpoint-imports-rendering-with-.md +6 -0
  64. package/corpus/templates/slides/changelog/2026-08-09-images-from-sites-without-cors-no-longer-export-blank.md +6 -0
  65. package/corpus/templates/slides/changelog/2026-08-09-imported-pdf-headings-no-longer-lose-the-space-between-color.md +6 -0
  66. package/corpus/templates/slides/changelog/2026-08-09-imported-slide-text-no-longer-renders-too-large-for-its-box.md +6 -0
  67. package/corpus/templates/slides/changelog/2026-08-09-large-decks-no-longer-render-slowly-or-glitch-in-the-editor.md +6 -0
  68. package/corpus/templates/slides/changelog/2026-08-09-revise-highlighted-slide-text-with-ai.md +6 -0
  69. package/corpus/templates/slides/changelog/2026-08-09-the-deck-editor-no-longer-stays-dimmed-after-the-window-narro.md +6 -0
  70. package/corpus/templates/slides/changelog/2026-08-10-imported-reference-decks-now-show-clear-success-and-stay-sel.md +6 -0
  71. package/corpus/templates/slides/changelog/2026-08-10-pasted-google-slides-links-import-after-reconnecting-google-.md +6 -0
  72. package/corpus/templates/slides/server/handlers/google-docs-auth.ts +4 -0
  73. package/corpus/templates/slides/server/handlers/import/html-converter.ts +115 -22
  74. package/corpus/templates/slides/server/handlers/import/pdf-fidelity-parser.ts +11 -2
  75. package/corpus/templates/slides/server/lib/fetch-remote-image.ts +192 -0
  76. package/corpus/templates/slides/server/lib/google-docs-oauth.ts +13 -0
  77. package/corpus/templates/slides/server/lib/image-proxy-url.ts +192 -0
  78. package/corpus/templates/slides/server/plugins/agent-chat.ts +1 -1
  79. package/corpus/templates/slides/server/routes/api/image-proxy.get.ts +65 -0
  80. package/dist/agent/engine/registry.d.ts +8 -2
  81. package/dist/agent/engine/registry.js +63 -29
  82. package/dist/agent/production-agent.js +7 -0
  83. package/dist/agent/run-manager.d.ts +5 -4
  84. package/dist/agent/run-manager.js +6 -5
  85. package/dist/cli/claude-code-participant.d.ts +2 -1
  86. package/dist/cli/claude-code-participant.js +19 -0
  87. package/dist/cli/code-agent-executor.js +223 -0
  88. package/dist/cli/create-workspace.js +3 -3
  89. package/dist/cli/create.d.ts +3 -2
  90. package/dist/cli/create.js +20 -24
  91. package/dist/client/AgentPanel.js +8 -4
  92. package/dist/client/AssistantChat.js +34 -1
  93. package/dist/client/agent-chat-adapter.d.ts +1 -0
  94. package/dist/client/agent-chat-adapter.js +10 -2
  95. package/dist/client/chat/markdown-renderer.d.ts +1 -0
  96. package/dist/client/chat/markdown-renderer.js +4 -0
  97. package/dist/client/chat/message-components.d.ts +1 -0
  98. package/dist/client/chat/message-components.js +9 -1
  99. package/dist/client/chat/tool-call-display.d.ts +8 -0
  100. package/dist/client/chat/tool-call-display.js +30 -21
  101. package/dist/client/chat-first/app-pane.js +1 -1
  102. package/dist/client/chat-first/apps-rail.d.ts +1 -1
  103. package/dist/client/chat-first/apps-rail.js +11 -11
  104. package/dist/client/chat-first/chat-history.d.ts +1 -1
  105. package/dist/client/chat-first/chat-history.js +2 -0
  106. package/dist/client/chat-first/primary-nav.js +1 -1
  107. package/dist/client/progress/RunsTray.js +14 -1
  108. package/dist/client/resources/McpConnectionSuggestion.js +2 -2
  109. package/dist/client/settings/SettingsTabsPage.d.ts +3 -1
  110. package/dist/client/settings/SettingsTabsPage.js +2 -2
  111. package/dist/client/sse-event-processor.d.ts +8 -0
  112. package/dist/client/sse-event-processor.js +13 -6
  113. package/dist/collab/awareness.d.ts +2 -2
  114. package/dist/collab/routes.d.ts +1 -1
  115. package/dist/deploy/build.d.ts +5 -7
  116. package/dist/deploy/build.js +19 -12
  117. package/dist/localization/default-messages.js +1 -1
  118. package/dist/notifications/routes.d.ts +3 -3
  119. package/dist/observability/routes.d.ts +6 -6
  120. package/dist/provider-api/actions/provider-api.d.ts +2 -2
  121. package/dist/scripts/call-agent.js +3 -3
  122. package/dist/scripts/describe-workspace-apps.js +1 -1
  123. package/dist/server/agent-capabilities.js +0 -0
  124. package/dist/server/agent-chat/context-tools.js +1 -1
  125. package/dist/server/agent-chat/prompt-resources.js +1 -1
  126. package/dist/server/agent-chat-plugin.js +1 -1
  127. package/dist/server/auth.js +190 -42
  128. package/dist/server/better-auth-migrations.js +53 -0
  129. package/dist/server/http-response-telemetry.js +7 -1
  130. package/dist/server/onboarding-html.js +119 -33
  131. package/dist/server/transcribe-voice.d.ts +1 -1
  132. package/dist/shared/password-policy.d.ts +2 -1
  133. package/dist/shared/password-policy.js +2 -1
  134. package/dist/styles/agent-conversation.css +20 -3
  135. package/dist/styles/agent-native.css +14 -2
  136. package/dist/templates/default/app/i18n/ar-SA.ts +1 -1
  137. package/dist/templates/default/app/i18n/de-DE.ts +1 -1
  138. package/dist/templates/default/app/i18n/en-US.ts +1 -1
  139. package/dist/templates/default/app/i18n/es-ES.ts +1 -1
  140. package/dist/templates/default/app/i18n/fr-FR.ts +1 -1
  141. package/dist/templates/default/app/i18n/hi-IN.ts +1 -1
  142. package/dist/templates/default/app/i18n/ja-JP.ts +1 -1
  143. package/dist/templates/default/app/i18n/ko-KR.ts +1 -1
  144. package/dist/templates/default/app/i18n/pt-BR.ts +1 -1
  145. package/dist/templates/default/app/i18n/zh-CN.ts +1 -1
  146. package/dist/templates/default/app/i18n/zh-TW.ts +1 -1
  147. package/dist/templates/default/pnpm-workspace.yaml +1 -0
  148. package/dist/templates/workspace-root/pnpm-workspace.yaml +2 -0
  149. package/docs/content/cloneable-saas.mdx +6 -6
  150. package/docs/content/deployment.mdx +10 -8
  151. package/docs/content/environment-variables.mdx +4 -3
  152. package/package.json +2 -2
  153. package/src/templates/default/app/i18n/ar-SA.ts +1 -1
  154. package/src/templates/default/app/i18n/de-DE.ts +1 -1
  155. package/src/templates/default/app/i18n/en-US.ts +1 -1
  156. package/src/templates/default/app/i18n/es-ES.ts +1 -1
  157. package/src/templates/default/app/i18n/fr-FR.ts +1 -1
  158. package/src/templates/default/app/i18n/hi-IN.ts +1 -1
  159. package/src/templates/default/app/i18n/ja-JP.ts +1 -1
  160. package/src/templates/default/app/i18n/ko-KR.ts +1 -1
  161. package/src/templates/default/app/i18n/pt-BR.ts +1 -1
  162. package/src/templates/default/app/i18n/zh-CN.ts +1 -1
  163. package/src/templates/default/app/i18n/zh-TW.ts +1 -1
  164. package/src/templates/default/pnpm-workspace.yaml +1 -0
  165. package/src/templates/workspace-root/pnpm-workspace.yaml +2 -0
package/corpus/README.md CHANGED
@@ -31,4 +31,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
31
31
 
32
32
  ## Generated Counts
33
33
 
34
- - template files: 7931
34
+ - template files: 7959
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-10
4
+ ---
5
+
6
+ Wide tables in Analytics chat now scroll horizontally within the panel
@@ -30,6 +30,10 @@ 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 {
34
+ computeTimedEventLayout,
35
+ type TimedEventLayout,
36
+ } from "@/lib/event-layout";
33
37
  import {
34
38
  fullDayOutOfOfficeCoversDate,
35
39
  isFullDayOutOfOfficeEvent,
@@ -138,54 +142,6 @@ function formatEventTime(start: Date, end: Date): string {
138
142
  return `${startWithAmPm}–${endStr}`;
139
143
  }
140
144
 
141
- interface LayoutInfo {
142
- left: number; // percentage 0-100
143
- width: number; // percentage 0-100
144
- col: number;
145
- totalCols: number;
146
- }
147
-
148
- function computeLayout(dayEvents: CalendarEvent[]): Map<string, LayoutInfo> {
149
- const result = new Map<string, LayoutInfo>();
150
- if (dayEvents.length === 0) return result;
151
-
152
- const sorted = [...dayEvents].sort((a, b) => {
153
- const aStart = parseISO(a.start).getTime();
154
- const bStart = parseISO(b.start).getTime();
155
- if (aStart !== bStart) return aStart - bStart;
156
- return parseISO(b.end).getTime() - parseISO(a.end).getTime();
157
- });
158
-
159
- const times = new Map<string, { start: number; end: number }>();
160
- for (const ev of sorted) {
161
- times.set(ev.id, {
162
- start: parseISO(ev.start).getTime(),
163
- end: parseISO(ev.end).getTime(),
164
- });
165
- }
166
-
167
- const INDENT_PX = 20; // DayView has wider columns, more indent room
168
-
169
- for (const ev of sorted) {
170
- let depth = 0;
171
- for (const other of sorted) {
172
- if (other.id === ev.id) break;
173
- const ta = times.get(other.id)!;
174
- const tb = times.get(ev.id)!;
175
- if (ta.start < tb.end && tb.start < ta.end) depth++;
176
- }
177
-
178
- result.set(ev.id, {
179
- left: depth * INDENT_PX,
180
- width: 0,
181
- col: depth,
182
- totalCols: depth + 1,
183
- });
184
- }
185
-
186
- return result;
187
- }
188
-
189
145
  function getEventStyleForDate(event: CalendarEvent, date: Date) {
190
146
  const start = parseISO(event.start);
191
147
  const end = parseISO(event.end);
@@ -207,7 +163,7 @@ function getEventStyleForDate(event: CalendarEvent, date: Date) {
207
163
  interface DayEventCardProps {
208
164
  event: CalendarEvent;
209
165
  date: Date;
210
- layout: Map<string, LayoutInfo>;
166
+ layout: Map<string, TimedEventLayout>;
211
167
  now: Date;
212
168
  prefs: ViewPreferences;
213
169
  focusedEventId: string | null;
@@ -278,6 +234,7 @@ const DayEventCard = memo(function DayEventCard({
278
234
  width: 100,
279
235
  col: 0,
280
236
  totalCols: 1,
237
+ stackOrder: 0,
281
238
  };
282
239
  const overrides =
283
240
  overrideTop !== null && overrideHeight !== null
@@ -347,14 +304,14 @@ const DayEventCard = memo(function DayEventCard({
347
304
  }
348
305
  style={{
349
306
  ...posStyle,
350
- left: `${li.left}px`,
351
- width: `calc(min(85%, 100% - ${li.left + 2}px))`,
307
+ left: `calc(${li.left}% + ${li.col > 0 ? 2 : 0}px)`,
308
+ width: `calc(${li.width}% - ${li.col > 0 ? 4 : 2}px)`,
352
309
  zIndex:
353
310
  isBeingDragged && isDragging
354
311
  ? 100
355
312
  : focusedEventId === event.id
356
313
  ? 50
357
- : li.col + 1,
314
+ : li.stackOrder + 1,
358
315
  backgroundColor: color
359
316
  ? `color-mix(in srgb, ${color} ${isPast || isDeclined ? 8 : 18}%, hsl(var(--background)))`
360
317
  : `color-mix(in srgb, hsl(var(--primary)) ${isPast || isDeclined ? 5 : 12}%, hsl(var(--background)))`,
@@ -625,7 +582,10 @@ export const DayView = memo(function DayView({
625
582
  () => events.filter((event) => !event.allDay && !isOutOfOfficeEvent(event)),
626
583
  [events],
627
584
  );
628
- const layout = useMemo(() => computeLayout(timedEvents), [timedEvents]);
585
+ const layout = useMemo(
586
+ () => computeTimedEventLayout(timedEvents, date),
587
+ [date, timedEvents],
588
+ );
629
589
 
630
590
  // Timezone label: prefer the short generic name (e.g. "PT", "ET")
631
591
  // over the offset form ("GMT-7"), and fall back to the IANA id when
@@ -43,6 +43,10 @@ import {
43
43
  partitionAllDayEvents,
44
44
  } from "@/lib/all-day-layout";
45
45
  import { getEventDisplayColor, allOtherDeclined } from "@/lib/event-colors";
46
+ import {
47
+ computeTimedEventLayout,
48
+ type TimedEventLayout,
49
+ } from "@/lib/event-layout";
46
50
  import {
47
51
  getFirstVisibleOutOfOfficeDayIndex,
48
52
  getOutOfOfficeSegment,
@@ -168,70 +172,6 @@ function formatEventTime(start: Date, end: Date): string {
168
172
  return `${startWithAmPm}\u2013${endStr}`;
169
173
  }
170
174
 
171
- interface LayoutInfo {
172
- left: number; // percentage 0-100
173
- width: number; // percentage 0-100
174
- col: number;
175
- totalCols: number;
176
- }
177
-
178
- /**
179
- * Stacking layout — like the user's drawing and Google Cal.
180
- *
181
- * Every event is nearly full width. Overlapping events get a small left
182
- * indent per nesting depth and stack on top with opaque backgrounds.
183
- * Text at the top stays readable; the card beneath is covered.
184
- */
185
- function computeLayout(
186
- dayEvents: CalendarEvent[],
187
- day: Date,
188
- ): Map<string, LayoutInfo> {
189
- const result = new Map<string, LayoutInfo>();
190
- if (dayEvents.length === 0) return result;
191
-
192
- const dayStartMs = startOfDay(day).getTime();
193
- const dayEndMs = addDays(startOfDay(day), 1).getTime();
194
-
195
- // Cap each event's times to this day's boundaries once, reuse in sort + overlap
196
- const times = new Map(
197
- dayEvents.map((ev) => [
198
- ev.id,
199
- {
200
- start: Math.max(parseISO(ev.start).getTime(), dayStartMs),
201
- end: Math.min(parseISO(ev.end).getTime(), dayEndMs),
202
- },
203
- ]),
204
- );
205
-
206
- const sorted = [...dayEvents].sort((a, b) => {
207
- const ta = times.get(a.id)!;
208
- const tb = times.get(b.id)!;
209
- if (ta.start !== tb.start) return ta.start - tb.start;
210
- return tb.end - ta.end; // later end first when starts are equal
211
- });
212
-
213
- const INDENT_PX = 16;
214
-
215
- for (const ev of sorted) {
216
- let depth = 0;
217
- for (const other of sorted) {
218
- if (other.id === ev.id) break;
219
- const ta = times.get(other.id)!;
220
- const tb = times.get(ev.id)!;
221
- if (ta.start < tb.end && tb.start < ta.end) depth++;
222
- }
223
-
224
- result.set(ev.id, {
225
- left: depth * INDENT_PX,
226
- width: 0,
227
- col: depth,
228
- totalCols: depth + 1,
229
- });
230
- }
231
-
232
- return result;
233
- }
234
-
235
175
  function getSegmentStyle(event: CalendarEvent, day: Date) {
236
176
  const evStart = parseISO(event.start);
237
177
  const evEnd = parseISO(event.end);
@@ -251,7 +191,7 @@ interface WeekEventCardProps {
251
191
  event: CalendarEvent;
252
192
  day: Date;
253
193
  dayIndex: number;
254
- layout: Map<string, LayoutInfo>;
194
+ layout: Map<string, TimedEventLayout>;
255
195
  now: Date;
256
196
  prefs: ViewPreferences;
257
197
  focusedEventId: string | null;
@@ -333,9 +273,10 @@ const WeekEventCard = memo(function WeekEventCard({
333
273
  const t = useT();
334
274
  const li = layout.get(event.id) ?? {
335
275
  left: 0,
336
- width: 0,
276
+ width: 100,
337
277
  col: 0,
338
278
  totalCols: 1,
279
+ stackOrder: 0,
339
280
  };
340
281
  const overrides =
341
282
  overrideTop !== null && overrideHeight !== null && overrideDayIndex !== null
@@ -431,14 +372,14 @@ const WeekEventCard = memo(function WeekEventCard({
431
372
  }
432
373
  style={{
433
374
  ...style,
434
- left: `${li.left}px`,
435
- width: `calc(min(85%, 100% - ${li.left + 2}px))`,
375
+ left: `calc(${li.left}% + ${li.col > 0 ? 2 : 0}px)`,
376
+ width: `calc(${li.width}% - ${li.col > 0 ? 4 : 2}px)`,
436
377
  zIndex:
437
378
  isBeingDragged && isDragging
438
379
  ? 100
439
380
  : focusedEventId === event.id
440
381
  ? 50
441
- : li.col + 1,
382
+ : li.stackOrder + 1,
442
383
  backgroundColor: color
443
384
  ? `color-mix(in srgb, ${color} ${isPast || isDeclined ? 8 : 18}%, hsl(var(--background)))`
444
385
  : `color-mix(in srgb, hsl(var(--primary)) ${isPast || isDeclined ? 5 : 12}%, hsl(var(--background)))`,
@@ -747,7 +688,7 @@ export const WeekView = memo(function WeekView({
747
688
  const evEnd = parseISO(e.end);
748
689
  return evStart < dayEnd && evEnd > dayStart;
749
690
  });
750
- const layout = computeLayout(dayEvents, day);
691
+ const layout = computeTimedEventLayout(dayEvents, day);
751
692
  return { day, events: dayEvents, layout };
752
693
  });
753
694
  }, [days, timedEvents]);
@@ -0,0 +1,122 @@
1
+ import type { CalendarEvent } from "@shared/api";
2
+ import { addDays, parseISO, startOfDay } from "date-fns";
3
+
4
+ export interface TimedEventLayout {
5
+ left: number;
6
+ width: number;
7
+ col: number;
8
+ totalCols: number;
9
+ stackOrder: number;
10
+ }
11
+
12
+ interface EventBounds {
13
+ start: number;
14
+ end: number;
15
+ }
16
+
17
+ interface EventEntry {
18
+ event: CalendarEvent;
19
+ bounds: EventBounds;
20
+ inputOrder: number;
21
+ }
22
+
23
+ const TEXT_REGION_MINUTES = 45;
24
+ const MIN_VISIBLE_EVENT_MINUTES = 15;
25
+
26
+ function overlaps(a: EventBounds, b: EventBounds): boolean {
27
+ return a.start < b.end && b.start < a.end;
28
+ }
29
+
30
+ /**
31
+ * Pack timed events into columns while reserving horizontal space only when
32
+ * the event labels can collide. An event may span unused columns when another
33
+ * event is present there at a different time, which keeps long events readable
34
+ * without wasting the column width needed for genuinely simultaneous labels.
35
+ */
36
+ export function computeTimedEventLayout(
37
+ dayEvents: readonly CalendarEvent[],
38
+ day: Date,
39
+ ): Map<string, TimedEventLayout> {
40
+ const result = new Map<string, TimedEventLayout>();
41
+ if (dayEvents.length === 0) return result;
42
+
43
+ const dayStartMs = startOfDay(day).getTime();
44
+ const dayEndMs = addDays(startOfDay(day), 1).getTime();
45
+ const entries = dayEvents.map<EventEntry>((event, inputOrder) => {
46
+ const rawStart = parseISO(event.start).getTime();
47
+ const rawEnd = parseISO(event.end).getTime();
48
+ const start = Math.min(dayEndMs, Math.max(dayStartMs, rawStart));
49
+ // Match the card renderer's minimum height so tiny adjacent events get
50
+ // collision-aware placement even when their raw times barely overlap.
51
+ const end = Math.min(
52
+ dayEndMs,
53
+ Math.max(start, rawEnd, start + MIN_VISIBLE_EVENT_MINUTES * 60 * 1000),
54
+ );
55
+
56
+ return { event, bounds: { start, end }, inputOrder };
57
+ });
58
+
59
+ const sorted = [...entries].sort((a, b) => {
60
+ if (a.bounds.start !== b.bounds.start) {
61
+ return a.bounds.start - b.bounds.start;
62
+ }
63
+ if (a.bounds.end !== b.bounds.end) {
64
+ return b.bounds.end - a.bounds.end;
65
+ }
66
+ return a.inputOrder - b.inputOrder;
67
+ });
68
+
69
+ const textOverlaps = (a: EventBounds, b: EventBounds): boolean => {
70
+ const aTextEnd = Math.min(a.start + TEXT_REGION_MINUTES * 60 * 1000, a.end);
71
+ const bTextEnd = Math.min(b.start + TEXT_REGION_MINUTES * 60 * 1000, b.end);
72
+ return a.start < bTextEnd && b.start < aTextEnd;
73
+ };
74
+
75
+ // Place labels side by side only when their visible text regions overlap.
76
+ const columns: EventEntry[][] = [];
77
+ const eventColumns = new Map<CalendarEvent, number>();
78
+
79
+ for (const entry of sorted) {
80
+ let column = columns.findIndex((columnEntries) =>
81
+ columnEntries.every(
82
+ (placed) => !textOverlaps(placed.bounds, entry.bounds),
83
+ ),
84
+ );
85
+
86
+ if (column === -1) {
87
+ column = columns.length;
88
+ columns.push([]);
89
+ }
90
+
91
+ columns[column].push(entry);
92
+ eventColumns.set(entry.event, column);
93
+ }
94
+
95
+ const totalCols = columns.length;
96
+
97
+ for (const [stackOrder, entry] of sorted.entries()) {
98
+ const col = eventColumns.get(entry.event)!;
99
+ let span = 1;
100
+
101
+ // A column to the right is available when it has no event that overlaps
102
+ // this card in time. Label-only collisions do not need to block expansion.
103
+ for (let candidate = col + 1; candidate < totalCols; candidate++) {
104
+ if (
105
+ columns[candidate].some((other) => overlaps(other.bounds, entry.bounds))
106
+ ) {
107
+ break;
108
+ }
109
+ span++;
110
+ }
111
+
112
+ result.set(entry.event.id, {
113
+ left: (col / totalCols) * 100,
114
+ width: (span / totalCols) * 100,
115
+ col,
116
+ totalCols,
117
+ stackOrder,
118
+ });
119
+ }
120
+
121
+ return result;
122
+ }
@@ -1,5 +1,5 @@
1
1
  import { Navigate } from "react-router";
2
2
 
3
3
  export default function AvailabilityRoute() {
4
- return <Navigate to="/booking-links" replace />;
4
+ return <Navigate to="/booking-links?tab=availability" replace />;
5
5
  }
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-10
4
+ ---
5
+
6
+ Opening availability settings from the agent lands on the availability editor
@@ -14,7 +14,12 @@ export default mergeConfig(
14
14
  },
15
15
  test: {
16
16
  include: ["**/*.{test,spec}.?(c|m)[jt]s?(x)"],
17
- exclude: ["**/node_modules/**", "**/.git/**", "**/dist/**"],
17
+ exclude: [
18
+ "**/node_modules/**",
19
+ "**/.git/**",
20
+ "**/dist/**",
21
+ "**/.react-router/**",
22
+ ],
18
23
  },
19
24
  }),
20
25
  );
@@ -79,7 +79,9 @@ export function isAudioMimeType(mimeType: string | null | undefined): boolean {
79
79
  export function isNoExtractableAudioError(err: unknown): boolean {
80
80
  return (
81
81
  err instanceof AudioOnlyExtractionError &&
82
- (err.code === "NO_AUDIO_TRACK" || err.code === "NO_SPEECH_DETECTED")
82
+ (err.code === "NO_AUDIO_SAVED" ||
83
+ err.code === "NO_AUDIO_TRACK" ||
84
+ err.code === "NO_SPEECH_DETECTED")
83
85
  );
84
86
  }
85
87
 
@@ -145,6 +145,12 @@ function isPlayerUiTarget(target: EventTarget | null): boolean {
145
145
  );
146
146
  }
147
147
 
148
+ type WebkitFullscreenVideo = HTMLVideoElement & {
149
+ webkitDisplayingFullscreen?: boolean;
150
+ webkitEnterFullscreen?: () => void;
151
+ webkitExitFullscreen?: () => void;
152
+ };
153
+
148
154
  export interface VideoPlayerHandle {
149
155
  video: HTMLVideoElement | null;
150
156
  play: () => Promise<void> | void;
@@ -1354,25 +1360,59 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
1354
1360
 
1355
1361
  async function toggleFullscreenInternal() {
1356
1362
  const el = containerRef.current;
1363
+ const video = videoRef.current as WebkitFullscreenVideo | null;
1357
1364
  if (!el) return;
1358
1365
  try {
1359
1366
  if (!document.fullscreenElement) {
1360
- await el.requestFullscreen();
1361
- setIsFullscreen(true);
1367
+ if (
1368
+ video?.webkitDisplayingFullscreen ||
1369
+ (isFullscreen && video?.webkitExitFullscreen)
1370
+ ) {
1371
+ video?.webkitExitFullscreen?.();
1372
+ setIsFullscreen(false);
1373
+ } else if (typeof el.requestFullscreen === "function") {
1374
+ await el.requestFullscreen();
1375
+ setIsFullscreen(true);
1376
+ } else if (video?.webkitEnterFullscreen) {
1377
+ // iPhone Safari exposes fullscreen on the video element, not the
1378
+ // containing div used by the custom player controls.
1379
+ video.webkitEnterFullscreen();
1380
+ setIsFullscreen(true);
1381
+ } else {
1382
+ console.warn("[clips] Fullscreen unavailable");
1383
+ }
1362
1384
  } else {
1363
1385
  await document.exitFullscreen();
1364
1386
  setIsFullscreen(false);
1365
1387
  }
1366
1388
  } catch (err) {
1389
+ if (video?.webkitEnterFullscreen && !document.fullscreenElement) {
1390
+ try {
1391
+ video.webkitEnterFullscreen();
1392
+ setIsFullscreen(true);
1393
+ return;
1394
+ } catch (fallbackErr) {
1395
+ console.warn("[clips] Fullscreen fallback failed", fallbackErr);
1396
+ }
1397
+ }
1367
1398
  console.warn("[clips] Fullscreen failed", err);
1368
1399
  }
1369
1400
  }
1370
1401
 
1371
1402
  useEffect(() => {
1403
+ const video = videoRef.current as WebkitFullscreenVideo | null;
1372
1404
  const onFs = () => setIsFullscreen(!!document.fullscreenElement);
1405
+ const onNativeFsEnter = () => setIsFullscreen(true);
1406
+ const onNativeFsExit = () => setIsFullscreen(false);
1373
1407
  document.addEventListener("fullscreenchange", onFs);
1374
- return () => document.removeEventListener("fullscreenchange", onFs);
1375
- }, []);
1408
+ video?.addEventListener("webkitbeginfullscreen", onNativeFsEnter);
1409
+ video?.addEventListener("webkitendfullscreen", onNativeFsExit);
1410
+ return () => {
1411
+ document.removeEventListener("fullscreenchange", onFs);
1412
+ video?.removeEventListener("webkitbeginfullscreen", onNativeFsEnter);
1413
+ video?.removeEventListener("webkitendfullscreen", onNativeFsExit);
1414
+ };
1415
+ }, [playbackVideoEl]);
1376
1416
 
1377
1417
  const currentSegment = transcriptSegments?.find(
1378
1418
  (s) => currentMs >= s.startMs && currentMs <= s.endMs,
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-10
4
+ ---
5
+
6
+ Fixed transcription failures for recordings without captured audio
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-10
4
+ ---
5
+
6
+ Mobile clips can now enter fullscreen on iPhone Safari
@@ -49,6 +49,16 @@ import {
49
49
 
50
50
  type DocumentRow = InferSelectModel<typeof schema.documents>;
51
51
  type ContentDatabaseRow = InferSelectModel<typeof schema.contentDatabases>;
52
+ type ContentDatabaseSummaryRow = Pick<
53
+ ContentDatabaseRow,
54
+ | "id"
55
+ | "documentId"
56
+ | "title"
57
+ | "systemRole"
58
+ | "viewConfigJson"
59
+ | "createdAt"
60
+ | "updatedAt"
61
+ >;
52
62
  type ContentDatabaseItemRow = InferSelectModel<
53
63
  typeof schema.contentDatabaseItems
54
64
  >;
@@ -176,7 +186,7 @@ export async function getDatabaseById(
176
186
  }
177
187
 
178
188
  export function serializeDatabase(
179
- database: ContentDatabaseRow,
189
+ database: ContentDatabaseSummaryRow,
180
190
  description = "",
181
191
  ) {
182
192
  return {
@@ -189,6 +189,24 @@ function propertyValueTextForScreen(
189
189
  }
190
190
 
191
191
  export const DATABASE_CURRENT_VIEW_VISIBLE_ITEM_LIMIT = 50;
192
+ export const SCREEN_DOCUMENT_PREVIEW_CHARS = 1_200;
193
+
194
+ export function documentContentPreview(content: string) {
195
+ const normalized = content.trim();
196
+ if (normalized.length <= SCREEN_DOCUMENT_PREVIEW_CHARS) {
197
+ return {
198
+ contentPreview: normalized,
199
+ contentLength: content.length,
200
+ contentTruncated: false,
201
+ };
202
+ }
203
+
204
+ return {
205
+ contentPreview: `${normalized.slice(0, SCREEN_DOCUMENT_PREVIEW_CHARS).trimEnd()}... [document body truncated; call get-document for the full content]`,
206
+ contentLength: content.length,
207
+ contentTruncated: true,
208
+ };
209
+ }
192
210
 
193
211
  function propertyForDatabaseItem(
194
212
  item: ContentDatabaseItem,
@@ -535,11 +553,13 @@ export function databaseCurrentViewSnapshot(
535
553
  {};
536
554
  const calculationResults =
537
555
  arrayValue(nav.databaseCalculationResults) ??
538
- databaseCalculationSummariesForScreen(
539
- calculations,
540
- response.items,
541
- visibleProperties,
542
- );
556
+ (response.pagination?.hasMore
557
+ ? null
558
+ : databaseCalculationSummariesForScreen(
559
+ calculations,
560
+ response.items,
561
+ visibleProperties,
562
+ ));
543
563
  const groupByPropertyId =
544
564
  stringValue(nav.databaseGroupByPropertyId) ??
545
565
  activeView?.groupByPropertyId ??
@@ -625,9 +645,13 @@ export function databaseCurrentViewSnapshot(
625
645
  formQuestions:
626
646
  arrayValue(nav.databaseFormQuestions) ?? activeView?.formQuestions ?? [],
627
647
  visibleItemCount:
628
- numberValue(nav.databaseVisibleItemCount) ?? response.items.length,
648
+ numberValue(nav.databaseVisibleItemCount) ??
649
+ response.pagination?.returnedItems ??
650
+ response.items.length,
629
651
  totalItemCount:
630
- numberValue(nav.databaseTotalItemCount) ?? response.items.length,
652
+ numberValue(nav.databaseTotalItemCount) ??
653
+ response.pagination?.totalItems ??
654
+ response.items.length,
631
655
  visibleItems: arrayValue(nav.databaseVisibleItems) ?? fallbackVisibleItems,
632
656
  visibleItemLimit:
633
657
  numberValue(nav.databaseVisibleItemLimit) ??
@@ -645,7 +669,7 @@ interface NavigationState {
645
669
 
646
670
  export default defineAction({
647
671
  description:
648
- "See what the user is currently looking at on screen. Reads navigation state and fetches matching data.",
672
+ "See what the user is currently looking at on screen. Returns bounded navigation, document previews, and the current database window; use get-document for full page content.",
649
673
  schema: z.object({}),
650
674
  http: false,
651
675
  run: async () => {
@@ -679,11 +703,12 @@ export default defineAction({
679
703
  const doc = access.resource;
680
704
  const database = await getDatabaseByDocumentId(doc.id);
681
705
  const databaseMembership = await getDatabaseItemByDocumentId(doc.id);
706
+ const contentSummary = documentContentPreview(doc.content);
682
707
  screen.document = {
683
708
  id: doc.id,
684
709
  parentId: doc.parentId,
685
710
  title: doc.title,
686
- content: doc.content,
711
+ ...contentSummary,
687
712
  description: doc.description,
688
713
  icon: doc.icon,
689
714
  position: doc.position,
@@ -704,6 +729,10 @@ export default defineAction({
704
729
  if (database) {
705
730
  const databaseResponse = await getContentDatabaseResponse(
706
731
  database.id,
732
+ {
733
+ limit: DATABASE_CURRENT_VIEW_VISIBLE_ITEM_LIMIT,
734
+ includeSources: false,
735
+ },
707
736
  );
708
737
  screen.database = databaseResponse;
709
738
  screen.databaseCurrentView = databaseCurrentViewSnapshot(
@@ -727,6 +756,9 @@ export default defineAction({
727
756
  previewMembership?.database.id === database.id
728
757
  ) {
729
758
  const previewDoc = previewAccess.resource;
759
+ const previewContentSummary = documentContentPreview(
760
+ previewDoc.content,
761
+ );
730
762
  screen.databasePreview = {
731
763
  itemId: previewMembership.item.id,
732
764
  databaseId: previewMembership.database.id,
@@ -736,7 +768,7 @@ export default defineAction({
736
768
  id: previewDoc.id,
737
769
  parentId: previewDoc.parentId,
738
770
  title: previewDoc.title,
739
- content: previewDoc.content,
771
+ ...previewContentSummary,
740
772
  description: previewDoc.description,
741
773
  icon: previewDoc.icon,
742
774
  position: previewDoc.position,
@@ -760,7 +792,15 @@ export default defineAction({
760
792
  }
761
793
 
762
794
  const docs = await db
763
- .select()
795
+ .select({
796
+ id: schema.documents.id,
797
+ parentId: schema.documents.parentId,
798
+ title: schema.documents.title,
799
+ icon: schema.documents.icon,
800
+ isFavorite: schema.documents.isFavorite,
801
+ hideFromSearch: schema.documents.hideFromSearch,
802
+ visibility: schema.documents.visibility,
803
+ })
764
804
  .from(schema.documents)
765
805
  .where(
766
806
  and(
@@ -788,7 +828,17 @@ export default defineAction({
788
828
  const treeDatabases =
789
829
  treeDocs.length > 0
790
830
  ? await db
791
- .select()
831
+ .select({
832
+ id: schema.contentDatabases.id,
833
+ ownerEmail: schema.contentDatabases.ownerEmail,
834
+ orgId: schema.contentDatabases.orgId,
835
+ documentId: schema.contentDatabases.documentId,
836
+ title: schema.contentDatabases.title,
837
+ systemRole: schema.contentDatabases.systemRole,
838
+ viewConfigJson: schema.contentDatabases.viewConfigJson,
839
+ createdAt: schema.contentDatabases.createdAt,
840
+ updatedAt: schema.contentDatabases.updatedAt,
841
+ })
792
842
  .from(schema.contentDatabases)
793
843
  .where(
794
844
  inArray(