@agent-native/core 0.161.17 → 0.161.22
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/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/analytics/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/analytics/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/analytics/AGENTS.md +1 -0
- package/corpus/templates/analytics/server/plugins/auth.ts +4 -0
- package/corpus/templates/assets/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/assets/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/assets/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/assets/AGENTS.md +1 -0
- package/corpus/templates/brain/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/brain/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/brain/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/brain/AGENTS.md +1 -0
- package/corpus/templates/calendar/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/calendar/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/calendar/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/calendar/AGENTS.md +1 -0
- package/corpus/templates/chat/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/chat/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/chat/AGENTS.md +4 -0
- package/corpus/templates/clips/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/clips/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/clips/AGENTS.md +1 -0
- package/corpus/templates/clips/actions/finalize-meeting.ts +57 -21
- package/corpus/templates/clips/actions/get-meeting.ts +11 -3
- package/corpus/templates/clips/actions/request-transcript.ts +183 -8
- package/corpus/templates/clips/actions/update-meeting.ts +66 -14
- package/corpus/templates/clips/app/components/meetings/canvas-editor.tsx +80 -135
- package/corpus/templates/clips/app/components/player/video-player.tsx +4 -0
- package/corpus/templates/clips/app/hooks/use-playback-position.ts +10 -2
- package/corpus/templates/clips/app/i18n/en-US.ts +4 -0
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +470 -35
- package/corpus/templates/clips/app/routes/embed.$shareId.tsx +1 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +26 -25
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +1 -0
- package/corpus/templates/clips/changelog/2026-08-18-clips-no-longer-opens-welcome-window-on-first-launch.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-18-meeting-notes-support-rich-editing-for-personal-notes-summar.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-18-pending-recording-files-now-use-a-clips-prefixed-name.md +6 -0
- package/corpus/templates/clips/desktop/src/main.tsx +0 -3
- package/corpus/templates/clips/desktop/src/shared/config.ts +0 -1
- package/corpus/templates/clips/desktop/src/styles.css +0 -118
- package/corpus/templates/clips/desktop/src-tauri/capabilities/default.json +0 -1
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +0 -45
- package/corpus/templates/clips/desktop/src-tauri/src/config.rs +1 -4
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +0 -10
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +57 -5
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +13 -3
- package/corpus/templates/content/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/content/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/content/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/content/AGENTS.md +1 -0
- package/corpus/templates/crm/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/crm/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/crm/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/crm/AGENTS.md +1 -0
- package/corpus/templates/design/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/design/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/design/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/design/AGENTS.md +1 -0
- package/corpus/templates/design/app/i18n-data.ts +11 -0
- package/corpus/templates/design/app/lib/agent-chat.ts +9 -8
- package/corpus/templates/dispatch/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/dispatch/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/dispatch/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/dispatch/AGENTS.md +5 -5
- package/corpus/templates/factory/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/factory/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/factory/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/factory/AGENTS.md +1 -0
- package/corpus/templates/forms/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/forms/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/forms/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/forms/AGENTS.md +1 -0
- package/corpus/templates/macros/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/macros/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/macros/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/macros/AGENTS.md +1 -0
- package/corpus/templates/mail/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/mail/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/mail/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/mail/AGENTS.md +1 -0
- package/corpus/templates/mail/app/components/email/EmailList.tsx +74 -22
- package/corpus/templates/mail/app/components/email/infinite-pagination.ts +82 -0
- package/corpus/templates/mail/app/hooks/use-emails.ts +1 -0
- package/corpus/templates/mail/app/pages/InboxPage.tsx +15 -10
- package/corpus/templates/mail/app/pages/inbox-zero.ts +35 -0
- package/corpus/templates/mail/changelog/2026-08-18-pinned-labels-load-matches.md +6 -0
- package/corpus/templates/plan/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/plan/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/plan/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/plan/AGENTS.md +1 -0
- package/corpus/templates/slides/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/slides/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/slides/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/slides/AGENTS.md +2 -1
- package/corpus/templates/slides/actions/get-deck.ts +5 -1
- package/corpus/templates/slides/actions/list-decks.ts +29 -8
- package/corpus/templates/slides/actions/view-screen.ts +7 -3
- package/corpus/templates/slides/changelog/2026-08-18-decks-appear-under-mine.md +6 -0
- package/corpus/templates/slides/shared/ownership.ts +6 -0
- package/corpus/templates/tasks/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/tasks/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/tasks/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/tasks/AGENTS.md +1 -0
- package/dist/action.js +43 -1
- package/dist/app-config/schema.d.ts +2 -0
- package/dist/app-config/workspace.d.ts +2 -0
- package/dist/app-config/workspace.js +18 -0
- package/dist/catalog.json +23 -23
- package/dist/client/analytics.d.ts +1 -0
- package/dist/client/api-path.js +7 -3
- package/dist/client/chat/run-recovery.js +1 -1
- package/dist/client/frame.js +20 -6
- package/dist/client/org/workspace-app-links.js +10 -1
- package/dist/client/route-state.js +17 -13
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.js +26 -2
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/provider-api/actions/custom-provider-registration.d.ts +14 -14
- package/dist/provider-api/actions/provider-api.d.ts +15 -15
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/app-origin-config.d.ts +1 -0
- package/dist/server/app-origin-config.js +3 -0
- package/dist/server/auth.d.ts +6 -0
- package/dist/server/auth.js +25 -7
- package/dist/server/cookie-namespace.js +17 -2
- package/dist/server/credential-provider.js +5 -2
- package/dist/server/derived-secret.js +3 -2
- package/dist/server/onboarding-html.js +34 -2
- package/dist/server/workspace-oauth.js +11 -6
- package/dist/shared/runtime-config.d.ts +2 -0
- package/dist/shared/runtime-config.js +12 -3
- package/dist/templates/chat/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/dist/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/dist/templates/chat/.agents/skills/secrets/SKILL.md +29 -2
- package/dist/templates/chat/AGENTS.md +4 -0
- package/dist/templates/default/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/dist/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/dist/templates/default/.agents/skills/secrets/SKILL.md +29 -2
- package/dist/templates/default/AGENTS.md +4 -0
- package/dist/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/dist/templates/headless/.agents/skills/secrets/SKILL.md +29 -2
- package/dist/templates/headless/AGENTS.md +4 -0
- package/dist/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +29 -2
- package/dist/templates/workspace-core/AGENTS.md +4 -0
- package/dist/templates/workspace-root/AGENTS.md +9 -0
- package/dist/vite/client.js +5 -2
- package/docs/content/deployment.mdx +70 -0
- package/docs/content/faq.mdx +1 -1
- package/package.json +2 -2
- package/src/templates/chat/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/src/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/src/templates/chat/.agents/skills/secrets/SKILL.md +29 -2
- package/src/templates/chat/AGENTS.md +4 -0
- package/src/templates/default/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/src/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/src/templates/default/.agents/skills/secrets/SKILL.md +29 -2
- package/src/templates/default/AGENTS.md +4 -0
- package/src/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/src/templates/headless/.agents/skills/secrets/SKILL.md +29 -2
- package/src/templates/headless/AGENTS.md +4 -0
- package/src/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +29 -2
- package/src/templates/workspace-core/AGENTS.md +4 -0
- package/src/templates/workspace-root/AGENTS.md +9 -0
- package/corpus/templates/clips/desktop/src/overlays/onboarding.tsx +0 -314
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
IconExternalLink,
|
|
18
18
|
IconLoader2,
|
|
19
19
|
IconNotes,
|
|
20
|
+
IconPlus,
|
|
20
21
|
IconPlayerStop,
|
|
21
22
|
IconRefresh,
|
|
22
23
|
IconTrash,
|
|
@@ -61,6 +62,7 @@ import {
|
|
|
61
62
|
DropdownMenuItem,
|
|
62
63
|
DropdownMenuTrigger,
|
|
63
64
|
} from "@/components/ui/dropdown-menu";
|
|
65
|
+
import { Input } from "@/components/ui/input";
|
|
64
66
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
65
67
|
import {
|
|
66
68
|
Tooltip,
|
|
@@ -95,6 +97,7 @@ interface Meeting {
|
|
|
95
97
|
ownerEmail?: string | null;
|
|
96
98
|
scheduledStart: string;
|
|
97
99
|
scheduledEnd?: string | null;
|
|
100
|
+
updatedAt?: string | null;
|
|
98
101
|
actualStart?: string | null;
|
|
99
102
|
actualEnd?: string | null;
|
|
100
103
|
platform?: string;
|
|
@@ -217,31 +220,41 @@ function TitleEditor({
|
|
|
217
220
|
function ActionItemsByPerson({
|
|
218
221
|
items,
|
|
219
222
|
onToggle,
|
|
223
|
+
onChange,
|
|
224
|
+
onRemove,
|
|
225
|
+
onAdd,
|
|
226
|
+
emptyLabel,
|
|
220
227
|
readOnly = false,
|
|
221
228
|
}: {
|
|
222
229
|
items: ActionItem[];
|
|
223
230
|
onToggle: (index: number, completed: boolean) => void;
|
|
231
|
+
onChange: (index: number, text: string) => void;
|
|
232
|
+
onRemove: (index: number) => void;
|
|
233
|
+
onAdd: (text: string) => void;
|
|
234
|
+
emptyLabel: string;
|
|
224
235
|
readOnly?: boolean;
|
|
225
236
|
}) {
|
|
237
|
+
const t = useT();
|
|
238
|
+
const [adding, setAdding] = useState(false);
|
|
239
|
+
|
|
226
240
|
// Preserve original index for toggle callback while grouping.
|
|
227
241
|
const grouped = useMemo(() => {
|
|
228
242
|
const map = new Map<string, Array<{ item: ActionItem; index: number }>>();
|
|
229
243
|
items.forEach((it, index) => {
|
|
230
|
-
const key = it.assigneeEmail || "
|
|
244
|
+
const key = it.assigneeEmail || "";
|
|
231
245
|
const arr = map.get(key) ?? [];
|
|
232
246
|
arr.push({ item: it, index });
|
|
233
247
|
map.set(key, arr);
|
|
234
248
|
});
|
|
235
249
|
const entries = Array.from(map.entries());
|
|
236
250
|
entries.sort(([a], [b]) => {
|
|
237
|
-
if (a
|
|
238
|
-
if (b
|
|
251
|
+
if (!a) return 1;
|
|
252
|
+
if (!b) return -1;
|
|
239
253
|
return a.localeCompare(b);
|
|
240
254
|
});
|
|
241
255
|
return entries;
|
|
242
256
|
}, [items]);
|
|
243
257
|
|
|
244
|
-
if (items.length === 0) return null;
|
|
245
258
|
return (
|
|
246
259
|
<div className="space-y-3">
|
|
247
260
|
{grouped.map(([who, list]) => (
|
|
@@ -250,10 +263,12 @@ function ActionItemsByPerson({
|
|
|
250
263
|
<Avatar className="h-5 w-5">
|
|
251
264
|
<AvatarImage alt={who} />
|
|
252
265
|
<AvatarFallback className="text-[9px]">
|
|
253
|
-
{attendeeInitials(who)}
|
|
266
|
+
{attendeeInitials(who || t("meetingDetail.unassigned"))}
|
|
254
267
|
</AvatarFallback>
|
|
255
268
|
</Avatar>
|
|
256
|
-
<span className="text-xs font-medium">
|
|
269
|
+
<span className="text-xs font-medium">
|
|
270
|
+
{who || t("meetingDetail.unassigned")}
|
|
271
|
+
</span>
|
|
257
272
|
<span className="text-[10px] text-muted-foreground">
|
|
258
273
|
{list.filter((x) => x.item.completedAt).length}/{list.length}
|
|
259
274
|
</span>
|
|
@@ -290,24 +305,153 @@ function ActionItemsByPerson({
|
|
|
290
305
|
<IconCheck className="h-2.5 w-2.5 text-background" />
|
|
291
306
|
)}
|
|
292
307
|
</button>
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
308
|
+
{readOnly ? (
|
|
309
|
+
<span
|
|
310
|
+
className={cn(
|
|
311
|
+
"flex-1",
|
|
312
|
+
done && "line-through text-muted-foreground",
|
|
313
|
+
)}
|
|
314
|
+
>
|
|
315
|
+
{it.text}
|
|
316
|
+
</span>
|
|
317
|
+
) : (
|
|
318
|
+
<ActionItemTextEditor
|
|
319
|
+
value={it.text}
|
|
320
|
+
done={done}
|
|
321
|
+
placeholder={t("meetingDetail.actionItemPlaceholder")}
|
|
322
|
+
onCommit={(text) => onChange(index, text)}
|
|
323
|
+
onCancel={() => {}}
|
|
324
|
+
/>
|
|
325
|
+
)}
|
|
326
|
+
{!readOnly && (
|
|
327
|
+
<Button
|
|
328
|
+
type="button"
|
|
329
|
+
size="icon"
|
|
330
|
+
variant="ghost"
|
|
331
|
+
className="h-6 w-6 shrink-0 text-muted-foreground hover:text-destructive"
|
|
332
|
+
aria-label={t("meetingDetail.removeActionItem")}
|
|
333
|
+
onClick={() => onRemove(index)}
|
|
334
|
+
>
|
|
335
|
+
<IconTrash className="h-3.5 w-3.5" />
|
|
336
|
+
</Button>
|
|
337
|
+
)}
|
|
301
338
|
</li>
|
|
302
339
|
);
|
|
303
340
|
})}
|
|
304
341
|
</ul>
|
|
305
342
|
</div>
|
|
306
343
|
))}
|
|
344
|
+
|
|
345
|
+
{grouped.length === 0 && (
|
|
346
|
+
<p className="text-sm leading-relaxed text-muted-foreground/50 italic">
|
|
347
|
+
{emptyLabel}
|
|
348
|
+
</p>
|
|
349
|
+
)}
|
|
350
|
+
|
|
351
|
+
{adding && (
|
|
352
|
+
<div className="flex items-start gap-2 pl-7 text-xs leading-relaxed">
|
|
353
|
+
<span className="mt-0.5 h-3.5 w-3.5 shrink-0 rounded border border-border" />
|
|
354
|
+
<ActionItemTextEditor
|
|
355
|
+
value=""
|
|
356
|
+
isNew
|
|
357
|
+
autoFocus
|
|
358
|
+
placeholder={t("meetingDetail.actionItemPlaceholder")}
|
|
359
|
+
onCommit={(text) => {
|
|
360
|
+
onAdd(text);
|
|
361
|
+
setAdding(false);
|
|
362
|
+
}}
|
|
363
|
+
onCancel={() => setAdding(false)}
|
|
364
|
+
/>
|
|
365
|
+
</div>
|
|
366
|
+
)}
|
|
367
|
+
|
|
368
|
+
{!readOnly && (
|
|
369
|
+
<Button
|
|
370
|
+
type="button"
|
|
371
|
+
variant="ghost"
|
|
372
|
+
size="sm"
|
|
373
|
+
className="ml-7 h-7 px-1.5 text-xs text-muted-foreground hover:text-foreground"
|
|
374
|
+
disabled={adding}
|
|
375
|
+
onClick={() => setAdding(true)}
|
|
376
|
+
>
|
|
377
|
+
<IconPlus className="mr-1.5 h-3.5 w-3.5" />
|
|
378
|
+
{t("meetingDetail.addActionItem")}
|
|
379
|
+
</Button>
|
|
380
|
+
)}
|
|
307
381
|
</div>
|
|
308
382
|
);
|
|
309
383
|
}
|
|
310
384
|
|
|
385
|
+
function ActionItemTextEditor({
|
|
386
|
+
value,
|
|
387
|
+
done = false,
|
|
388
|
+
isNew = false,
|
|
389
|
+
autoFocus = false,
|
|
390
|
+
placeholder,
|
|
391
|
+
onCommit,
|
|
392
|
+
onCancel,
|
|
393
|
+
}: {
|
|
394
|
+
value: string;
|
|
395
|
+
done?: boolean;
|
|
396
|
+
isNew?: boolean;
|
|
397
|
+
autoFocus?: boolean;
|
|
398
|
+
placeholder: string;
|
|
399
|
+
onCommit: (text: string) => void;
|
|
400
|
+
onCancel: () => void;
|
|
401
|
+
}) {
|
|
402
|
+
const [draft, setDraft] = useState(value);
|
|
403
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
404
|
+
const committedRef = useRef(false);
|
|
405
|
+
|
|
406
|
+
useEffect(() => {
|
|
407
|
+
setDraft(value);
|
|
408
|
+
committedRef.current = false;
|
|
409
|
+
}, [value]);
|
|
410
|
+
|
|
411
|
+
useEffect(() => {
|
|
412
|
+
if (autoFocus) inputRef.current?.focus();
|
|
413
|
+
}, [autoFocus]);
|
|
414
|
+
|
|
415
|
+
const commit = () => {
|
|
416
|
+
if (committedRef.current) return;
|
|
417
|
+
const next = draft.trim();
|
|
418
|
+
if (!next) {
|
|
419
|
+
setDraft(value);
|
|
420
|
+
onCancel();
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (isNew || next !== value) {
|
|
424
|
+
committedRef.current = true;
|
|
425
|
+
onCommit(next);
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
return (
|
|
430
|
+
<Input
|
|
431
|
+
ref={inputRef}
|
|
432
|
+
value={draft}
|
|
433
|
+
placeholder={placeholder}
|
|
434
|
+
onChange={(event) => setDraft(event.target.value)}
|
|
435
|
+
onBlur={commit}
|
|
436
|
+
onKeyDown={(event) => {
|
|
437
|
+
if (event.key === "Enter") {
|
|
438
|
+
event.preventDefault();
|
|
439
|
+
commit();
|
|
440
|
+
}
|
|
441
|
+
if (event.key === "Escape") {
|
|
442
|
+
event.preventDefault();
|
|
443
|
+
setDraft(value);
|
|
444
|
+
onCancel();
|
|
445
|
+
}
|
|
446
|
+
}}
|
|
447
|
+
className={cn(
|
|
448
|
+
"h-auto min-h-0 flex-1 border-0 bg-transparent px-0 py-0 text-xs shadow-none focus-visible:ring-0",
|
|
449
|
+
done && "line-through text-muted-foreground",
|
|
450
|
+
)}
|
|
451
|
+
/>
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
|
|
311
455
|
export default function MeetingDetailRoute() {
|
|
312
456
|
const t = useT();
|
|
313
457
|
const { meetingId } = useParams<{ meetingId: string }>();
|
|
@@ -326,7 +470,12 @@ export default function MeetingDetailRoute() {
|
|
|
326
470
|
role?: "owner" | "admin" | "editor" | "commenter" | "viewer";
|
|
327
471
|
};
|
|
328
472
|
|
|
329
|
-
const {
|
|
473
|
+
const {
|
|
474
|
+
data,
|
|
475
|
+
isLoading,
|
|
476
|
+
isError,
|
|
477
|
+
refetch: refetchMeeting,
|
|
478
|
+
} = useActionQuery<GetMeetingResp>(
|
|
330
479
|
"get-meeting",
|
|
331
480
|
{ id: meetingId },
|
|
332
481
|
{
|
|
@@ -357,6 +506,31 @@ export default function MeetingDetailRoute() {
|
|
|
357
506
|
const [transcriptCopied, setTranscriptCopied] = useState(false);
|
|
358
507
|
const previousHasNotesRef = useRef(false);
|
|
359
508
|
const autoFinalizedRef = useRef(false);
|
|
509
|
+
const actionItemsDraftRef = useRef<{
|
|
510
|
+
meetingId: string;
|
|
511
|
+
items: ActionItem[];
|
|
512
|
+
} | null>(null);
|
|
513
|
+
const actionItemsAuthoritativeRef = useRef<{
|
|
514
|
+
meetingId: string;
|
|
515
|
+
items: ActionItem[];
|
|
516
|
+
} | null>(null);
|
|
517
|
+
const actionItemsSaveRevisionRef = useRef(0);
|
|
518
|
+
const pendingActionItemsSavesRef = useRef(0);
|
|
519
|
+
const meetingContentSaveQueueRef = useRef<Promise<unknown>>(
|
|
520
|
+
Promise.resolve(),
|
|
521
|
+
);
|
|
522
|
+
const meetingTitleSaveRevisionRef = useRef(0);
|
|
523
|
+
const meetingTitleDraftRef = useRef<{
|
|
524
|
+
meetingId: string;
|
|
525
|
+
value: string;
|
|
526
|
+
} | null>(null);
|
|
527
|
+
const richNotePendingRef = useRef<{
|
|
528
|
+
meetingId: string;
|
|
529
|
+
patch: { summaryMd?: string; userNotesMd?: string };
|
|
530
|
+
label: string;
|
|
531
|
+
} | null>(null);
|
|
532
|
+
const richNoteSaveActiveRef = useRef(false);
|
|
533
|
+
const meetingUpdatedAtRef = useRef<string | null>(null);
|
|
360
534
|
|
|
361
535
|
// Imperative scroll-to handle wired by TranscriptBubbles
|
|
362
536
|
const transcriptScrollToRef = useRef<((index: number) => void) | null>(null);
|
|
@@ -405,6 +579,32 @@ export default function MeetingDetailRoute() {
|
|
|
405
579
|
(meeting?.bulletsJson?.length ?? 0) > 0 ||
|
|
406
580
|
(meeting?.actionItemsJson?.length ?? 0) > 0;
|
|
407
581
|
|
|
582
|
+
useEffect(() => {
|
|
583
|
+
if (!meeting) {
|
|
584
|
+
actionItemsDraftRef.current = null;
|
|
585
|
+
actionItemsAuthoritativeRef.current = null;
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
if (
|
|
589
|
+
actionItemsDraftRef.current?.meetingId !== meeting.id ||
|
|
590
|
+
pendingActionItemsSavesRef.current === 0
|
|
591
|
+
) {
|
|
592
|
+
const serverItems = meeting.actionItemsJson ?? [];
|
|
593
|
+
actionItemsAuthoritativeRef.current = {
|
|
594
|
+
meetingId: meeting.id,
|
|
595
|
+
items: serverItems,
|
|
596
|
+
};
|
|
597
|
+
actionItemsDraftRef.current = {
|
|
598
|
+
meetingId: meeting.id,
|
|
599
|
+
items: serverItems,
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
}, [meeting?.id, meeting?.actionItemsJson]);
|
|
603
|
+
|
|
604
|
+
useEffect(() => {
|
|
605
|
+
meetingUpdatedAtRef.current = meeting?.updatedAt ?? null;
|
|
606
|
+
}, [meeting?.id, meeting?.updatedAt]);
|
|
607
|
+
|
|
408
608
|
// Recording is a native Clips desktop-app gesture (Granola-style), not an
|
|
409
609
|
// in-browser capture. For an un-recorded, not-yet-past meeting we surface a
|
|
410
610
|
// handoff to the desktop app. While the desktop records, this web view polls
|
|
@@ -469,31 +669,243 @@ export default function MeetingDetailRoute() {
|
|
|
469
669
|
);
|
|
470
670
|
};
|
|
471
671
|
|
|
672
|
+
const recordMeetingSaveResult = (result: unknown) => {
|
|
673
|
+
const updatedAt = (
|
|
674
|
+
result as { meeting?: { updatedAt?: unknown } } | null | undefined
|
|
675
|
+
)?.meeting?.updatedAt;
|
|
676
|
+
if (typeof updatedAt !== "string") return;
|
|
677
|
+
meetingUpdatedAtRef.current = updatedAt;
|
|
678
|
+
patchCachedMeeting({ updatedAt });
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
const refetchMeetingAfterSaveFailure = async () => {
|
|
682
|
+
try {
|
|
683
|
+
const refreshed = await refetchMeeting();
|
|
684
|
+
const updatedAt = refreshed.data?.meeting?.updatedAt;
|
|
685
|
+
if (typeof updatedAt === "string") {
|
|
686
|
+
meetingUpdatedAtRef.current = updatedAt;
|
|
687
|
+
}
|
|
688
|
+
return refreshed.data;
|
|
689
|
+
} catch (error) {
|
|
690
|
+
console.error("[clips] meeting refetch after save failed", error);
|
|
691
|
+
return undefined;
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
|
|
472
695
|
const handleTitleChange = (next: string) => {
|
|
473
696
|
if (!meeting) return;
|
|
697
|
+
const revision = ++meetingTitleSaveRevisionRef.current;
|
|
698
|
+
meetingTitleDraftRef.current = { meetingId: meeting.id, value: next };
|
|
474
699
|
patchCachedMeeting({ title: next });
|
|
475
|
-
|
|
700
|
+
const save = meetingContentSaveQueueRef.current
|
|
701
|
+
.catch(() => undefined)
|
|
702
|
+
.then(async () => {
|
|
703
|
+
const expectedUpdatedAt = meetingUpdatedAtRef.current;
|
|
704
|
+
const result = await updateMeeting.mutateAsync({
|
|
705
|
+
id: meeting.id,
|
|
706
|
+
title: next,
|
|
707
|
+
...(expectedUpdatedAt ? { expectedUpdatedAt } : {}),
|
|
708
|
+
});
|
|
709
|
+
recordMeetingSaveResult(result);
|
|
710
|
+
return result;
|
|
711
|
+
});
|
|
712
|
+
meetingContentSaveQueueRef.current = save.catch((error) => {
|
|
713
|
+
return (async () => {
|
|
714
|
+
console.error("[clips] meeting title save failed", error);
|
|
715
|
+
const hasNewerDraft = meetingTitleSaveRevisionRef.current !== revision;
|
|
716
|
+
await refetchMeetingAfterSaveFailure();
|
|
717
|
+
if (
|
|
718
|
+
hasNewerDraft &&
|
|
719
|
+
meetingTitleDraftRef.current?.meetingId === meeting.id
|
|
720
|
+
) {
|
|
721
|
+
patchCachedMeeting({ title: meetingTitleDraftRef.current.value });
|
|
722
|
+
}
|
|
723
|
+
toast.error(t("transcriptPanel.saveFailed", { status: "title" }));
|
|
724
|
+
})();
|
|
725
|
+
});
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
const enqueueRichNoteSave = (
|
|
729
|
+
meetingId: string,
|
|
730
|
+
patch: { summaryMd?: string; userNotesMd?: string },
|
|
731
|
+
label: string,
|
|
732
|
+
) => {
|
|
733
|
+
const pending = richNotePendingRef.current;
|
|
734
|
+
richNotePendingRef.current = {
|
|
735
|
+
meetingId,
|
|
736
|
+
patch: {
|
|
737
|
+
...(pending?.meetingId === meetingId ? pending.patch : {}),
|
|
738
|
+
...patch,
|
|
739
|
+
},
|
|
740
|
+
label,
|
|
741
|
+
};
|
|
742
|
+
if (richNoteSaveActiveRef.current) return;
|
|
743
|
+
|
|
744
|
+
richNoteSaveActiveRef.current = true;
|
|
745
|
+
const drain = meetingContentSaveQueueRef.current
|
|
746
|
+
.catch(() => undefined)
|
|
747
|
+
.then(async () => {
|
|
748
|
+
while (richNotePendingRef.current) {
|
|
749
|
+
const current = richNotePendingRef.current;
|
|
750
|
+
richNotePendingRef.current = null;
|
|
751
|
+
try {
|
|
752
|
+
const expectedUpdatedAt = meetingUpdatedAtRef.current;
|
|
753
|
+
const result = await updateMeeting.mutateAsync({
|
|
754
|
+
id: current.meetingId,
|
|
755
|
+
...current.patch,
|
|
756
|
+
...(expectedUpdatedAt ? { expectedUpdatedAt } : {}),
|
|
757
|
+
});
|
|
758
|
+
recordMeetingSaveResult(result);
|
|
759
|
+
} catch (error) {
|
|
760
|
+
console.error("[clips] rich note save failed", error);
|
|
761
|
+
await refetchMeetingAfterSaveFailure();
|
|
762
|
+
// A newer blur may have arrived while the failed request was in
|
|
763
|
+
// flight. Refetch to refresh the CAS token, then put that latest
|
|
764
|
+
// draft back into the optimistic cache before retrying it.
|
|
765
|
+
const nextPending = richNotePendingRef.current as {
|
|
766
|
+
meetingId: string;
|
|
767
|
+
patch: { summaryMd?: string; userNotesMd?: string };
|
|
768
|
+
label: string;
|
|
769
|
+
} | null;
|
|
770
|
+
if (nextPending) {
|
|
771
|
+
patchCachedMeeting(nextPending.patch);
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
toast.error(
|
|
775
|
+
t("transcriptPanel.saveFailed", { status: current.label }),
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
})
|
|
780
|
+
.finally(() => {
|
|
781
|
+
richNoteSaveActiveRef.current = false;
|
|
782
|
+
});
|
|
783
|
+
meetingContentSaveQueueRef.current = drain.catch((error) => {
|
|
784
|
+
console.error("[clips] rich note save drain failed", error);
|
|
785
|
+
});
|
|
476
786
|
};
|
|
477
787
|
|
|
478
788
|
const handleSummaryChange = (next: string) => {
|
|
479
789
|
if (!meeting) return;
|
|
480
790
|
patchCachedMeeting({ summaryMd: next });
|
|
481
|
-
|
|
791
|
+
enqueueRichNoteSave(
|
|
792
|
+
meeting.id,
|
|
793
|
+
{ summaryMd: next },
|
|
794
|
+
t("meetingDetail.summary"),
|
|
795
|
+
);
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
const handleUserNotesChange = (next: string) => {
|
|
799
|
+
if (!meeting) return;
|
|
800
|
+
patchCachedMeeting({ userNotesMd: next });
|
|
801
|
+
enqueueRichNoteSave(
|
|
802
|
+
meeting.id,
|
|
803
|
+
{ userNotesMd: next },
|
|
804
|
+
t("meetingDetail.myNotes"),
|
|
805
|
+
);
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
const persistActionItems = (next: ActionItem[]) => {
|
|
809
|
+
if (!meeting) return;
|
|
810
|
+
const meetingId = meeting.id;
|
|
811
|
+
const revision = ++actionItemsSaveRevisionRef.current;
|
|
812
|
+
actionItemsDraftRef.current = { meetingId, items: next };
|
|
813
|
+
patchCachedMeeting({ actionItemsJson: next });
|
|
814
|
+
pendingActionItemsSavesRef.current += 1;
|
|
815
|
+
const save = meetingContentSaveQueueRef.current
|
|
816
|
+
.catch(() => undefined)
|
|
817
|
+
.then(async () => {
|
|
818
|
+
const expectedUpdatedAt = meetingUpdatedAtRef.current;
|
|
819
|
+
const result = await updateMeeting.mutateAsync({
|
|
820
|
+
id: meetingId,
|
|
821
|
+
actionItems: next,
|
|
822
|
+
...(expectedUpdatedAt ? { expectedUpdatedAt } : {}),
|
|
823
|
+
});
|
|
824
|
+
recordMeetingSaveResult(result);
|
|
825
|
+
actionItemsAuthoritativeRef.current = { meetingId, items: next };
|
|
826
|
+
return result;
|
|
827
|
+
});
|
|
828
|
+
meetingContentSaveQueueRef.current = save
|
|
829
|
+
.catch(async (error) => {
|
|
830
|
+
console.error("[clips] action-item save failed", error);
|
|
831
|
+
toast.error(
|
|
832
|
+
t("transcriptPanel.saveFailed", {
|
|
833
|
+
status: t("meetingDetail.actionItems"),
|
|
834
|
+
}),
|
|
835
|
+
);
|
|
836
|
+
const hasNewerDraft = actionItemsSaveRevisionRef.current !== revision;
|
|
837
|
+
const refreshed = await refetchMeetingAfterSaveFailure();
|
|
838
|
+
if (hasNewerDraft) {
|
|
839
|
+
const latestDraft = actionItemsDraftRef.current;
|
|
840
|
+
if (latestDraft?.meetingId === meetingId) {
|
|
841
|
+
patchCachedMeeting({ actionItemsJson: latestDraft.items });
|
|
842
|
+
}
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
const rollbackItems = Array.isArray(refreshed?.actionItems)
|
|
847
|
+
? refreshed.actionItems
|
|
848
|
+
: actionItemsAuthoritativeRef.current?.meetingId === meetingId
|
|
849
|
+
? actionItemsAuthoritativeRef.current.items
|
|
850
|
+
: [];
|
|
851
|
+
actionItemsAuthoritativeRef.current = {
|
|
852
|
+
meetingId,
|
|
853
|
+
items: rollbackItems,
|
|
854
|
+
};
|
|
855
|
+
actionItemsDraftRef.current = { meetingId, items: rollbackItems };
|
|
856
|
+
patchCachedMeeting({ actionItemsJson: rollbackItems });
|
|
857
|
+
})
|
|
858
|
+
.finally(() => {
|
|
859
|
+
pendingActionItemsSavesRef.current -= 1;
|
|
860
|
+
});
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
const currentActionItems = () => {
|
|
864
|
+
const draft = actionItemsDraftRef.current;
|
|
865
|
+
if (meeting && draft && draft.meetingId === meeting.id) {
|
|
866
|
+
return draft.items;
|
|
867
|
+
}
|
|
868
|
+
return meeting?.actionItemsJson ?? [];
|
|
482
869
|
};
|
|
483
870
|
|
|
484
871
|
const handleToggleActionItem = (index: number, completed: boolean) => {
|
|
485
872
|
if (!meeting) return;
|
|
486
|
-
const items =
|
|
873
|
+
const items = currentActionItems();
|
|
487
874
|
const next = items.map((it, i) =>
|
|
488
875
|
i === index
|
|
489
876
|
? { ...it, completedAt: completed ? new Date().toISOString() : null }
|
|
490
877
|
: it,
|
|
491
878
|
);
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
879
|
+
persistActionItems(next);
|
|
880
|
+
};
|
|
881
|
+
|
|
882
|
+
const handleActionItemChange = (index: number, text: string) => {
|
|
883
|
+
if (!meeting) return;
|
|
884
|
+
const items = currentActionItems();
|
|
885
|
+
persistActionItems(
|
|
886
|
+
items.map((item, itemIndex) =>
|
|
887
|
+
itemIndex === index ? { ...item, text } : item,
|
|
888
|
+
),
|
|
889
|
+
);
|
|
890
|
+
};
|
|
891
|
+
|
|
892
|
+
const handleActionItemRemove = (index: number) => {
|
|
893
|
+
if (!meeting) return;
|
|
894
|
+
const items = currentActionItems();
|
|
895
|
+
persistActionItems(items.filter((_, itemIndex) => itemIndex !== index));
|
|
896
|
+
};
|
|
897
|
+
|
|
898
|
+
const handleActionItemAdd = (text: string) => {
|
|
899
|
+
if (!meeting) return;
|
|
900
|
+
persistActionItems([
|
|
901
|
+
...currentActionItems(),
|
|
902
|
+
{
|
|
903
|
+
text,
|
|
904
|
+
assigneeEmail: null,
|
|
905
|
+
dueDate: null,
|
|
906
|
+
completedAt: null,
|
|
907
|
+
},
|
|
908
|
+
]);
|
|
497
909
|
};
|
|
498
910
|
|
|
499
911
|
const handleJumpToSegment = (segmentIndex: number) => {
|
|
@@ -642,7 +1054,13 @@ export default function MeetingDetailRoute() {
|
|
|
642
1054
|
if (!segments.length) return;
|
|
643
1055
|
const text = segments
|
|
644
1056
|
.map((s) => {
|
|
645
|
-
const label =
|
|
1057
|
+
const label =
|
|
1058
|
+
s.speaker?.trim() ||
|
|
1059
|
+
(s.source === "mic"
|
|
1060
|
+
? t("transcriptBubbles.me")
|
|
1061
|
+
: s.source === "system"
|
|
1062
|
+
? t("transcriptBubbles.them")
|
|
1063
|
+
: t("transcriptBubbles.unknownSpeaker"));
|
|
646
1064
|
return `${label}: ${s.text}`;
|
|
647
1065
|
})
|
|
648
1066
|
.join("\n");
|
|
@@ -909,12 +1327,31 @@ export default function MeetingDetailRoute() {
|
|
|
909
1327
|
</div>
|
|
910
1328
|
|
|
911
1329
|
<div className="min-h-0 flex-1 overflow-y-auto">
|
|
1330
|
+
<div className="max-w-2xl px-6 pt-5">
|
|
1331
|
+
<div className="mb-2 text-xs font-medium">
|
|
1332
|
+
{t("meetingDetail.myNotes")}
|
|
1333
|
+
</div>
|
|
1334
|
+
<CanvasEditor
|
|
1335
|
+
view="user"
|
|
1336
|
+
userNotesMd={meeting.userNotesMd ?? ""}
|
|
1337
|
+
onUserNotesChange={handleUserNotesChange}
|
|
1338
|
+
readOnly={!canEdit}
|
|
1339
|
+
className="max-w-none px-0 py-0"
|
|
1340
|
+
/>
|
|
1341
|
+
</div>
|
|
1342
|
+
|
|
1343
|
+
<div className="max-w-2xl px-6 pt-5">
|
|
1344
|
+
<div className="mb-2 text-xs font-medium">
|
|
1345
|
+
{t("meetingDetail.aiNotes")}
|
|
1346
|
+
</div>
|
|
1347
|
+
</div>
|
|
912
1348
|
<CanvasEditor
|
|
913
1349
|
view="ai"
|
|
914
1350
|
summaryMd={meeting.summaryMd ?? ""}
|
|
915
1351
|
bullets={bullets.map((b) => b.text)}
|
|
916
1352
|
onSummaryChange={handleSummaryChange}
|
|
917
1353
|
readOnly={!canEdit}
|
|
1354
|
+
className="max-w-2xl px-6 pt-0 pb-0"
|
|
918
1355
|
renderBullet={(b) => (
|
|
919
1356
|
<BulletLink
|
|
920
1357
|
bullet={b}
|
|
@@ -933,17 +1370,15 @@ export default function MeetingDetailRoute() {
|
|
|
933
1370
|
<div className="mb-3 text-xs font-medium">
|
|
934
1371
|
{t("meetingDetail.actionItems")}
|
|
935
1372
|
</div>
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
</p>
|
|
946
|
-
)}
|
|
1373
|
+
<ActionItemsByPerson
|
|
1374
|
+
items={actionItems}
|
|
1375
|
+
onToggle={handleToggleActionItem}
|
|
1376
|
+
onChange={handleActionItemChange}
|
|
1377
|
+
onRemove={handleActionItemRemove}
|
|
1378
|
+
onAdd={handleActionItemAdd}
|
|
1379
|
+
emptyLabel={t("meetingDetail.noActionItems")}
|
|
1380
|
+
readOnly={!canEdit}
|
|
1381
|
+
/>
|
|
947
1382
|
</div>
|
|
948
1383
|
</div>
|
|
949
1384
|
</div>
|
|
@@ -236,6 +236,7 @@ export default function EmbedRoute() {
|
|
|
236
236
|
videoFormat={recording.videoFormat}
|
|
237
237
|
embedProvider={isLoomEmbedBacked ? "loom" : null}
|
|
238
238
|
durationMs={recording.durationMs}
|
|
239
|
+
persistPlaybackPosition={false}
|
|
239
240
|
editsJson={recording.editsJson}
|
|
240
241
|
thumbnailUrl={recording.thumbnailUrl}
|
|
241
242
|
defaultSpeed={parsePlaybackSpeed(recording.defaultSpeed) ?? 1.2}
|