@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.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/changelog/2026-08-10-wide-tables-in-analytics-chat-now-scroll-horizontally-within.md +6 -0
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +13 -53
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +11 -70
- package/corpus/templates/calendar/app/lib/event-layout.ts +122 -0
- package/corpus/templates/calendar/app/routes/_app.availability.tsx +1 -1
- package/corpus/templates/calendar/changelog/2026-08-10-opening-availability-settings-from-the-agent-lands-on-the-av.md +6 -0
- package/corpus/templates/calendar/vitest.config.ts +6 -1
- package/corpus/templates/clips/actions/lib/audio-only-transcription.ts +3 -1
- package/corpus/templates/clips/app/components/player/video-player.tsx +44 -4
- package/corpus/templates/clips/changelog/2026-08-10-fixed-transcription-failures-for-recordings-without-captured.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-10-mobile-clips-can-now-enter-fullscreen-on-iphone-safari.md +6 -0
- package/corpus/templates/content/actions/_property-utils.ts +11 -1
- package/corpus/templates/content/actions/view-screen.ts +62 -12
- package/corpus/templates/content/server/plugins/agent-chat.ts +2 -0
- package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +22 -0
- package/corpus/templates/design/AGENTS.md +3 -0
- package/corpus/templates/design/actions/create-design-from-template.ts +19 -0
- package/corpus/templates/design/actions/get-design-snapshot.ts +18 -0
- package/corpus/templates/design/actions/get-design-template.ts +178 -0
- package/corpus/templates/design/actions/list-designs.ts +3 -1
- package/corpus/templates/design/actions/view-screen.ts +31 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +225 -214
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +3 -0
- package/corpus/templates/design/app/i18n-data.ts +78 -0
- package/corpus/templates/design/app/lib/design-authors.ts +64 -0
- package/corpus/templates/design/app/pages/Index.tsx +114 -4
- package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +1 -0
- package/corpus/templates/design/changelog/2026-08-07-alt-dragging-an-element-to-copy-it-now-keeps-the-copy-visibl.md +6 -0
- package/corpus/templates/design/changelog/2026-08-07-design-cards-now-show-who-created-each-design-with-a-filter-.md +6 -0
- package/corpus/templates/design/changelog/2026-08-07-drawing-a-shape-on-the-overview-canvas-now-keeps-the-shape-s.md +6 -0
- package/corpus/templates/design/changelog/2026-08-07-hold-option-alt-while-dragging-a-frame-on-the-overview-canva.md +6 -0
- package/corpus/templates/design/server/lib/design-template-data.ts +143 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +1 -0
- package/corpus/templates/forms/AGENTS.md +2 -2
- package/corpus/templates/forms/actions/create-form.ts +1 -1
- package/corpus/templates/forms/actions/update-form.ts +5 -5
- package/corpus/templates/forms/changelog/2026-08-10-forms-now-keep-agent-applied-field-changes-visible-after-an-.md +6 -0
- package/corpus/templates/forms/changelog/2026-08-10-published-form-links-always-use-the-working-public-form-url.md +6 -0
- package/corpus/templates/forms/server/plugins/agent-chat.ts +2 -2
- package/corpus/templates/slides/AGENTS.md +4 -3
- package/corpus/templates/slides/actions/import-google-slides-reference.ts +56 -10
- package/corpus/templates/slides/actions/patch-deck.ts +18 -2
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +46 -0
- package/corpus/templates/slides/app/components/editor/BlockBubbleMenu.tsx +194 -3
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +22 -75
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +5 -3
- package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +5 -1
- package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +15 -4
- package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +116 -55
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +27 -4
- package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +45 -0
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +143 -18
- package/corpus/templates/slides/app/i18n/en-US.ts +6 -0
- package/corpus/templates/slides/app/lib/export-pdf-client.ts +84 -25
- package/corpus/templates/slides/app/lib/generation-state.ts +7 -1
- package/corpus/templates/slides/app/lib/import-uploaded-deck.ts +11 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +44 -14
- package/corpus/templates/slides/app/pages/Index.tsx +124 -135
- package/corpus/templates/slides/changelog/2026-08-08-google-slides-export-now-tells-you-why-a-deck-fell-back-to-a.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-delete-key-now-removes-a-selected-image.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-editing-a-slide-in-an-imported-deck-no-longer-fails-to-save.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-fixed-pdf-and-custom-size-powerpoint-imports-rendering-with-.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-images-from-sites-without-cors-no-longer-export-blank.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-imported-pdf-headings-no-longer-lose-the-space-between-color.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-imported-slide-text-no-longer-renders-too-large-for-its-box.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-large-decks-no-longer-render-slowly-or-glitch-in-the-editor.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-revise-highlighted-slide-text-with-ai.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-the-deck-editor-no-longer-stays-dimmed-after-the-window-narro.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-imported-reference-decks-now-show-clear-success-and-stay-sel.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-pasted-google-slides-links-import-after-reconnecting-google-.md +6 -0
- package/corpus/templates/slides/server/handlers/google-docs-auth.ts +4 -0
- package/corpus/templates/slides/server/handlers/import/html-converter.ts +115 -22
- package/corpus/templates/slides/server/handlers/import/pdf-fidelity-parser.ts +11 -2
- package/corpus/templates/slides/server/lib/fetch-remote-image.ts +192 -0
- package/corpus/templates/slides/server/lib/google-docs-oauth.ts +13 -0
- package/corpus/templates/slides/server/lib/image-proxy-url.ts +192 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/slides/server/routes/api/image-proxy.get.ts +65 -0
- package/dist/agent/engine/registry.d.ts +8 -2
- package/dist/agent/engine/registry.js +63 -29
- package/dist/agent/production-agent.js +7 -0
- package/dist/agent/run-manager.d.ts +5 -4
- package/dist/agent/run-manager.js +6 -5
- package/dist/cli/claude-code-participant.d.ts +2 -1
- package/dist/cli/claude-code-participant.js +19 -0
- package/dist/cli/code-agent-executor.js +223 -0
- package/dist/cli/create-workspace.js +3 -3
- package/dist/cli/create.d.ts +3 -2
- package/dist/cli/create.js +20 -24
- package/dist/client/AgentPanel.js +8 -4
- package/dist/client/AssistantChat.js +34 -1
- package/dist/client/agent-chat-adapter.d.ts +1 -0
- package/dist/client/agent-chat-adapter.js +10 -2
- package/dist/client/chat/markdown-renderer.d.ts +1 -0
- package/dist/client/chat/markdown-renderer.js +4 -0
- package/dist/client/chat/message-components.d.ts +1 -0
- package/dist/client/chat/message-components.js +9 -1
- package/dist/client/chat/tool-call-display.d.ts +8 -0
- package/dist/client/chat/tool-call-display.js +30 -21
- package/dist/client/chat-first/app-pane.js +1 -1
- package/dist/client/chat-first/apps-rail.d.ts +1 -1
- package/dist/client/chat-first/apps-rail.js +11 -11
- package/dist/client/chat-first/chat-history.d.ts +1 -1
- package/dist/client/chat-first/chat-history.js +2 -0
- package/dist/client/chat-first/primary-nav.js +1 -1
- package/dist/client/progress/RunsTray.js +14 -1
- package/dist/client/resources/McpConnectionSuggestion.js +2 -2
- package/dist/client/settings/SettingsTabsPage.d.ts +3 -1
- package/dist/client/settings/SettingsTabsPage.js +2 -2
- package/dist/client/sse-event-processor.d.ts +8 -0
- package/dist/client/sse-event-processor.js +13 -6
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/deploy/build.d.ts +5 -7
- package/dist/deploy/build.js +19 -12
- package/dist/localization/default-messages.js +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +6 -6
- package/dist/provider-api/actions/provider-api.d.ts +2 -2
- package/dist/scripts/call-agent.js +3 -3
- package/dist/scripts/describe-workspace-apps.js +1 -1
- package/dist/server/agent-capabilities.js +0 -0
- package/dist/server/agent-chat/context-tools.js +1 -1
- package/dist/server/agent-chat/prompt-resources.js +1 -1
- package/dist/server/agent-chat-plugin.js +1 -1
- package/dist/server/auth.js +190 -42
- package/dist/server/better-auth-migrations.js +53 -0
- package/dist/server/http-response-telemetry.js +7 -1
- package/dist/server/onboarding-html.js +119 -33
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/password-policy.d.ts +2 -1
- package/dist/shared/password-policy.js +2 -1
- package/dist/styles/agent-conversation.css +20 -3
- package/dist/styles/agent-native.css +14 -2
- package/dist/templates/default/app/i18n/ar-SA.ts +1 -1
- package/dist/templates/default/app/i18n/de-DE.ts +1 -1
- package/dist/templates/default/app/i18n/en-US.ts +1 -1
- package/dist/templates/default/app/i18n/es-ES.ts +1 -1
- package/dist/templates/default/app/i18n/fr-FR.ts +1 -1
- package/dist/templates/default/app/i18n/hi-IN.ts +1 -1
- package/dist/templates/default/app/i18n/ja-JP.ts +1 -1
- package/dist/templates/default/app/i18n/ko-KR.ts +1 -1
- package/dist/templates/default/app/i18n/pt-BR.ts +1 -1
- package/dist/templates/default/app/i18n/zh-CN.ts +1 -1
- package/dist/templates/default/app/i18n/zh-TW.ts +1 -1
- package/dist/templates/default/pnpm-workspace.yaml +1 -0
- package/dist/templates/workspace-root/pnpm-workspace.yaml +2 -0
- package/docs/content/cloneable-saas.mdx +6 -6
- package/docs/content/deployment.mdx +10 -8
- package/docs/content/environment-variables.mdx +4 -3
- package/package.json +2 -2
- package/src/templates/default/app/i18n/ar-SA.ts +1 -1
- package/src/templates/default/app/i18n/de-DE.ts +1 -1
- package/src/templates/default/app/i18n/en-US.ts +1 -1
- package/src/templates/default/app/i18n/es-ES.ts +1 -1
- package/src/templates/default/app/i18n/fr-FR.ts +1 -1
- package/src/templates/default/app/i18n/hi-IN.ts +1 -1
- package/src/templates/default/app/i18n/ja-JP.ts +1 -1
- package/src/templates/default/app/i18n/ko-KR.ts +1 -1
- package/src/templates/default/app/i18n/pt-BR.ts +1 -1
- package/src/templates/default/app/i18n/zh-CN.ts +1 -1
- package/src/templates/default/app/i18n/zh-TW.ts +1 -1
- package/src/templates/default/pnpm-workspace.yaml +1 -0
- package/src/templates/workspace-root/pnpm-workspace.yaml +2 -0
package/corpus/README.md
CHANGED
|
@@ -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,
|
|
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:
|
|
351
|
-
width: `calc(
|
|
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.
|
|
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(
|
|
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,
|
|
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:
|
|
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:
|
|
435
|
-
width: `calc(
|
|
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.
|
|
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 =
|
|
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
|
+
}
|
|
@@ -14,7 +14,12 @@ export default mergeConfig(
|
|
|
14
14
|
},
|
|
15
15
|
test: {
|
|
16
16
|
include: ["**/*.{test,spec}.?(c|m)[jt]s?(x)"],
|
|
17
|
-
exclude: [
|
|
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 === "
|
|
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
|
-
|
|
1361
|
-
|
|
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
|
-
|
|
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,
|
|
@@ -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:
|
|
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
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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) ??
|
|
648
|
+
numberValue(nav.databaseVisibleItemCount) ??
|
|
649
|
+
response.pagination?.returnedItems ??
|
|
650
|
+
response.items.length,
|
|
629
651
|
totalItemCount:
|
|
630
|
-
numberValue(nav.databaseTotalItemCount) ??
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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(
|