@agent-native/core 0.178.0 → 0.178.1-nightly-20260909221943

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 (88) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/clips/.agents/skills/dictate/SKILL.md +1 -0
  3. package/corpus/templates/clips/DESIGN.md +10 -7
  4. package/corpus/templates/clips/actions/list-organization-state.ts +56 -3
  5. package/corpus/templates/clips/actions/list-recordings.ts +2 -0
  6. package/corpus/templates/clips/actions/search-dictations.ts +81 -0
  7. package/corpus/templates/clips/app/components/agent-destination-logos.tsx +50 -0
  8. package/corpus/templates/clips/app/components/bug-report/bug-report-dialog.tsx +60 -0
  9. package/corpus/templates/clips/app/components/bug-report/bug-report-form.tsx +206 -0
  10. package/corpus/templates/clips/app/components/clips-command-menu.tsx +653 -0
  11. package/corpus/templates/clips/app/components/library/empty-state.tsx +52 -17
  12. package/corpus/templates/clips/app/components/library/folder-card.tsx +34 -0
  13. package/corpus/templates/clips/app/components/library/folder-tree.tsx +109 -64
  14. package/corpus/templates/clips/app/components/library/library-action-hrefs.ts +27 -0
  15. package/corpus/templates/clips/app/components/library/library-grid.tsx +295 -103
  16. package/corpus/templates/clips/app/components/library/library-layout.tsx +381 -203
  17. package/corpus/templates/clips/app/components/library/library-primary-actions.tsx +48 -0
  18. package/corpus/templates/clips/app/components/library/page-header.tsx +48 -8
  19. package/corpus/templates/clips/app/components/library/recording-card.tsx +58 -55
  20. package/corpus/templates/clips/app/components/library/search-bar.tsx +5 -19
  21. package/corpus/templates/clips/app/components/library/sidebar-feedback-button.tsx +48 -0
  22. package/corpus/templates/clips/app/components/player/comments-panel.tsx +2 -2
  23. package/corpus/templates/clips/app/components/player/share-dialog.tsx +344 -260
  24. package/corpus/templates/clips/app/components/player/viewer-controls.tsx +2 -2
  25. package/corpus/templates/clips/app/components/recorder/camera-visualizer.tsx +0 -33
  26. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +5 -1
  27. package/corpus/templates/clips/app/components/ui/tabs.tsx +1 -1
  28. package/corpus/templates/clips/app/hooks/use-command-search.ts +53 -0
  29. package/corpus/templates/clips/app/hooks/use-navigation-state.ts +32 -13
  30. package/corpus/templates/clips/app/hooks/use-upload-video-picker.tsx +1 -0
  31. package/corpus/templates/clips/app/i18n/en-US.ts +7 -0
  32. package/corpus/templates/clips/app/lib/agent-share.ts +16 -0
  33. package/corpus/templates/clips/app/lib/command-events.ts +7 -0
  34. package/corpus/templates/clips/app/root.tsx +6 -82
  35. package/corpus/templates/clips/app/routes/_app.dictate.tsx +25 -5
  36. package/corpus/templates/clips/app/routes/_app.library._index.tsx +2 -30
  37. package/corpus/templates/clips/app/routes/_app.library.folder.$folderId.tsx +13 -2
  38. package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +4 -17
  39. package/corpus/templates/clips/app/routes/_app.r.$recordingId.tsx +111 -114
  40. package/corpus/templates/clips/app/routes/_app.spaces.$spaceId.folder.$folderId.tsx +25 -2
  41. package/corpus/templates/clips/app/routes/_app.spaces.$spaceId.tsx +14 -61
  42. package/corpus/templates/clips/app/routes/_app.spaces._index.tsx +34 -24
  43. package/corpus/templates/clips/app/routes/_app.trash.tsx +1 -1
  44. package/corpus/templates/clips/app/routes/_app.tsx +2 -4
  45. package/corpus/templates/clips/app/routes/bug-report.tsx +11 -189
  46. package/corpus/templates/clips/app/routes/share.$shareId.tsx +148 -123
  47. package/corpus/templates/clips/changelog/2026-09-08-comments-and-desktop-window-controls.md +6 -0
  48. package/corpus/templates/clips/changelog/2026-09-08-global-command-search.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-09-09-agent-sidebar-fits-below-toolbar.md +6 -0
  50. package/corpus/templates/clips/changelog/2026-09-09-desktop-switches-stay-clear-in-dark-mode.md +6 -0
  51. package/corpus/templates/clips/changelog/2026-09-09-recording-agent-uses-global-panel.md +6 -0
  52. package/corpus/templates/clips/desktop/src/app.tsx +2 -2
  53. package/corpus/templates/clips/desktop/src/components/MediaDeviceRow.tsx +5 -3
  54. package/corpus/templates/clips/desktop/src/components/SourceRow.tsx +1 -0
  55. package/corpus/templates/clips/desktop/src/components/Switch.tsx +2 -5
  56. package/corpus/templates/clips/desktop/src/components/ui/switch.tsx +21 -3
  57. package/corpus/templates/clips/desktop/src/hooks/useMediaDevices.ts +72 -52
  58. package/corpus/templates/clips/desktop/src/styles.css +18 -19
  59. package/corpus/templates/clips/desktop/src/tailwind.css +3 -0
  60. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +18 -10
  61. package/corpus/templates/clips/desktop/src-tauri/src/util.rs +3 -5
  62. package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +14 -3
  63. package/corpus/templates/design/AGENTS.md +2 -1
  64. package/corpus/templates/design/actions/export-png.ts +119 -0
  65. package/corpus/templates/design/actions/take-design-screenshot.ts +47 -13
  66. package/corpus/templates/design/server/plugins/agent-chat.ts +3 -1
  67. package/corpus/templates/factory/actions/dispatch-factory-item.ts +51 -22
  68. package/corpus/templates/factory/actions/list-triage-items.ts +21 -11
  69. package/corpus/templates/factory/actions/poll-slack-channel.ts +7 -2
  70. package/corpus/templates/factory/changelog/2026-09-09-slack-feedback-keeps-claimed-markers-after-poll.md +6 -0
  71. package/corpus/templates/factory/changelog/2026-09-09-slack-feedback-preserves-started-claimed-threads.md +6 -0
  72. package/corpus/templates/factory/changelog/2026-09-09-slack-feedback-skips-claimed-parents.md +6 -0
  73. package/corpus/templates/factory/server/lib/factory-automation-config.ts +1 -1
  74. package/corpus/templates/factory/server/lib/slack-feedback-prompt.ts +47 -0
  75. package/corpus/templates/factory/server/plugins/agent-chat.ts +5 -2
  76. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +5 -4
  77. package/corpus/templates/factory/server/triage/review-skill-alignment.ts +12 -7
  78. package/corpus/templates/factory/server/triage/slack-review-window.ts +39 -0
  79. package/dist/client/CommandMenu.js +13 -1
  80. package/dist/client/analytics.js +10 -0
  81. package/dist/collab/routes.d.ts +1 -1
  82. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  83. package/dist/notifications/routes.d.ts +2 -2
  84. package/dist/observability/routes.d.ts +5 -5
  85. package/dist/vite/client.js +25 -7
  86. package/dist/vite/sentry-source-maps.d.ts +12 -0
  87. package/dist/vite/sentry-source-maps.js +105 -0
  88. package/package.json +2 -1
@@ -1,4 +1,8 @@
1
- import { AgentPanel } from "@agent-native/core/client/agent-chat";
1
+ import {
2
+ requestAgentSidebarOpen,
3
+ SIDEBAR_STATE_CHANGE_EVENT,
4
+ type AgentSidebarStateChangeDetail,
5
+ } from "@agent-native/core/client/agent-chat";
2
6
  import {
3
7
  agentNativePath,
4
8
  appBasePath,
@@ -46,7 +50,6 @@ import {
46
50
  IconBolt,
47
51
  IconMessage,
48
52
  IconExternalLink,
49
- IconFolder,
50
53
  IconMoodSmile,
51
54
  } from "@tabler/icons-react";
52
55
  import { useQuery, useQueryClient } from "@tanstack/react-query";
@@ -64,7 +67,11 @@ import { toast } from "sonner";
64
67
  import { ClipsAvatar } from "@/components/clips-avatar";
65
68
  import { EditableRecordingTitle } from "@/components/editable-recording-title";
66
69
  import { EditorLayout } from "@/components/editor/editor-layout";
67
- import { PageHeader } from "@/components/library/page-header";
70
+ import {
71
+ PageBreadcrumb,
72
+ PageHeader,
73
+ type PageBreadcrumbItem,
74
+ } from "@/components/library/page-header";
68
75
  import {
69
76
  BrowserDiagnosticsPanel,
70
77
  isFullBrowserDiagnostics,
@@ -101,14 +108,6 @@ import {
101
108
  ViewerTabsTrigger,
102
109
  } from "@/components/player/viewer-controls";
103
110
  import { StorageSetupCard } from "@/components/recorder/storage-setup-card";
104
- import {
105
- Breadcrumb,
106
- BreadcrumbItem,
107
- BreadcrumbLink,
108
- BreadcrumbList,
109
- BreadcrumbPage,
110
- BreadcrumbSeparator,
111
- } from "@/components/ui/breadcrumb";
112
111
  import { Button } from "@/components/ui/button";
113
112
  import {
114
113
  DropdownMenuItem,
@@ -419,9 +418,35 @@ export function meta() {
419
418
  return [{ title: enMessages.recordingRoute.pageTitle }];
420
419
  }
421
420
 
422
- type SidePanel = "transcript" | "comments" | "agent" | "debug" | "settings";
421
+ type SidePanel = "transcript" | "comments" | "debug" | "settings";
423
422
  type ToolbarPanel = Exclude<SidePanel, "comments">;
424
423
 
424
+ function useGlobalAgentSidebarOpen() {
425
+ const [open, setOpen] = useState(false);
426
+
427
+ useEffect(() => {
428
+ const handleStateChange = (event: Event) => {
429
+ const detail = (event as CustomEvent<AgentSidebarStateChangeDetail>)
430
+ .detail;
431
+ if (detail && typeof detail.open === "boolean") {
432
+ setOpen(detail.open);
433
+ }
434
+ };
435
+
436
+ window.addEventListener(SIDEBAR_STATE_CHANGE_EVENT, handleStateChange);
437
+ const mountedPanel = document.querySelector<HTMLElement>(
438
+ ".agent-sidebar-panel[data-agent-sidebar-state='open']",
439
+ );
440
+ setOpen(Boolean(mountedPanel));
441
+
442
+ return () => {
443
+ window.removeEventListener(SIDEBAR_STATE_CHANGE_EVENT, handleStateChange);
444
+ };
445
+ }, []);
446
+
447
+ return open;
448
+ }
449
+
425
450
  const WORKFLOW_MENU_ITEMS: Array<{
426
451
  kind: WorkflowKind;
427
452
  labelKey: string;
@@ -536,6 +561,7 @@ export default function RecordingPage() {
536
561
  const commentsSectionRef = useRef<HTMLElement | null>(null);
537
562
 
538
563
  const [panel, setPanel] = useState<SidePanel | null>("transcript");
564
+ const globalAgentSidebarOpen = useGlobalAgentSidebarOpen();
539
565
  const [theaterMode, setTheaterMode] = useState(false);
540
566
  const [editing, setEditing] = useState(false);
541
567
  const [currentMs, setCurrentMs] = useState(startMs);
@@ -573,16 +599,18 @@ export default function RecordingPage() {
573
599
  const nextParams = new URLSearchParams(searchParams);
574
600
  nextParams.set("panel", "comments");
575
601
  setSearchParams(nextParams, { replace: true });
576
- requestAnimationFrame(() => {
577
- commentsSectionRef.current?.scrollIntoView({
578
- block: "start",
579
- behavior: "smooth",
602
+ if (isCompactLayout) {
603
+ requestAnimationFrame(() => {
604
+ commentsSectionRef.current?.scrollIntoView({
605
+ block: "start",
606
+ behavior: "smooth",
607
+ });
580
608
  });
581
- });
582
- }, [searchParams, setSearchParams]);
609
+ }
610
+ }, [isCompactLayout, searchParams, setSearchParams]);
583
611
  const openAgentPanel = useCallback(() => {
584
- openSidePanel("agent");
585
- }, [openSidePanel]);
612
+ requestAgentSidebarOpen();
613
+ }, []);
586
614
  const transcriptKickedRef = useRef<string | null>(null);
587
615
  // When the recording lands in the processing state but never flips to
588
616
  // 'ready', stop spinning forever and surface an error banner so the user
@@ -929,17 +957,23 @@ export default function RecordingPage() {
929
957
  }, [browserDiagnostics, canEdit, panel]);
930
958
 
931
959
  useEffect(() => {
960
+ if (panelParam === "agent") {
961
+ setPanel("transcript");
962
+ requestAgentSidebarOpen();
963
+ return;
964
+ }
932
965
  if (panelParam === "comments") {
933
- setPanel("comments");
934
- requestAnimationFrame(() => {
935
- commentsSectionRef.current?.scrollIntoView({ block: "start" });
936
- });
966
+ setPanel(recording?.enableComments ? "comments" : "transcript");
967
+ if (isCompactLayout) {
968
+ requestAnimationFrame(() => {
969
+ commentsSectionRef.current?.scrollIntoView({ block: "start" });
970
+ });
971
+ }
937
972
  return;
938
973
  }
939
974
  if (
940
975
  (panelParam === "transcript" ||
941
976
  panelParam === "insights" ||
942
- panelParam === "agent" ||
943
977
  panelParam === "debug" ||
944
978
  panelParam === "settings") &&
945
979
  (panelParam !== "settings" || canEdit) &&
@@ -947,7 +981,13 @@ export default function RecordingPage() {
947
981
  ) {
948
982
  setPanel(panelParam === "insights" ? "transcript" : panelParam);
949
983
  }
950
- }, [browserDiagnostics, canEdit, panelParam]);
984
+ }, [
985
+ browserDiagnostics,
986
+ canEdit,
987
+ isCompactLayout,
988
+ panelParam,
989
+ recording?.enableComments,
990
+ ]);
951
991
 
952
992
  const builderCredits =
953
993
  (playerDataQ.data?.builderCredits as BuilderCreditsStatus | null) ?? null;
@@ -965,55 +1005,30 @@ export default function RecordingPage() {
965
1005
  const visibleTitle = recording
966
1006
  ? displayRecordingTitle(recording.title)
967
1007
  : "Untitled Clip";
1008
+ const recordingBreadcrumbItems: PageBreadcrumbItem[] = [
1009
+ ...(recordingSpace
1010
+ ? [
1011
+ { label: t("navigation.spaces"), to: "/spaces" },
1012
+ {
1013
+ label: recordingSpace.name,
1014
+ to: `/spaces/${recordingSpace.id}`,
1015
+ },
1016
+ ]
1017
+ : [{ label: t("navigation.library"), to: "/library" }]),
1018
+ ...(recordingFolder
1019
+ ? [
1020
+ {
1021
+ label: recordingFolder.name,
1022
+ to: recordingFolder.spaceId
1023
+ ? `/spaces/${recordingFolder.spaceId}/folder/${recordingFolder.id}`
1024
+ : `/library/folder/${recordingFolder.id}`,
1025
+ },
1026
+ ]
1027
+ : []),
1028
+ { label: visibleTitle },
1029
+ ];
968
1030
  const recordingBreadcrumb = (
969
- <Breadcrumb aria-label={t("navigation.library")} className="min-w-0">
970
- <BreadcrumbList className="flex-nowrap overflow-hidden">
971
- <BreadcrumbItem className="shrink-0">
972
- <BreadcrumbLink asChild>
973
- <NavLink to="/library">{t("navigation.library")}</NavLink>
974
- </BreadcrumbLink>
975
- </BreadcrumbItem>
976
- {recordingSpace ? (
977
- <>
978
- <BreadcrumbSeparator className="shrink-0" />
979
- <BreadcrumbItem className="min-w-0">
980
- <BreadcrumbLink asChild>
981
- <NavLink
982
- to={`/spaces/${recordingSpace.id}`}
983
- className="truncate"
984
- >
985
- {recordingSpace.name}
986
- </NavLink>
987
- </BreadcrumbLink>
988
- </BreadcrumbItem>
989
- </>
990
- ) : null}
991
- {recordingFolder ? (
992
- <>
993
- <BreadcrumbSeparator className="shrink-0" />
994
- <BreadcrumbItem className="min-w-0">
995
- <BreadcrumbLink asChild>
996
- <NavLink
997
- to={
998
- recordingFolder.spaceId
999
- ? `/spaces/${recordingFolder.spaceId}/folder/${recordingFolder.id}`
1000
- : `/library/folder/${recordingFolder.id}`
1001
- }
1002
- className="flex min-w-0 items-center gap-1.5"
1003
- >
1004
- <IconFolder className="size-4 shrink-0" />
1005
- <span className="truncate">{recordingFolder.name}</span>
1006
- </NavLink>
1007
- </BreadcrumbLink>
1008
- </BreadcrumbItem>
1009
- </>
1010
- ) : null}
1011
- <BreadcrumbSeparator className="shrink-0" />
1012
- <BreadcrumbItem className="min-w-0">
1013
- <BreadcrumbPage className="truncate">{visibleTitle}</BreadcrumbPage>
1014
- </BreadcrumbItem>
1015
- </BreadcrumbList>
1016
- </Breadcrumb>
1031
+ <PageBreadcrumb items={recordingBreadcrumbItems} />
1017
1032
  );
1018
1033
  // Attribution `via` must never point at someone who isn't the owner, so it
1019
1034
  // is only tagged when the viewer is the owner (same rule as the share dialog).
@@ -1925,7 +1940,7 @@ export default function RecordingPage() {
1925
1940
 
1926
1941
  const renderPanelTabs = () => (
1927
1942
  <ViewerTabsList className="min-w-0 shrink-0 bg-sidebar">
1928
- {isCompactLayout ? (
1943
+ {recording.enableComments ? (
1929
1944
  <ViewerTabsTrigger value="comments">
1930
1945
  {t("playerSettings.comments")}
1931
1946
  </ViewerTabsTrigger>
@@ -1933,9 +1948,6 @@ export default function RecordingPage() {
1933
1948
  <ViewerTabsTrigger value="transcript">
1934
1949
  {t("recordingPage.transcript")}
1935
1950
  </ViewerTabsTrigger>
1936
- <ViewerTabsTrigger value="agent">
1937
- {t("recordingPage.agent")}
1938
- </ViewerTabsTrigger>
1939
1951
  {browserDiagnostics ? (
1940
1952
  <ViewerTabsTrigger value="debug">
1941
1953
  <span className="flex items-center justify-center gap-1.5">
@@ -1964,7 +1976,7 @@ export default function RecordingPage() {
1964
1976
  "scroll-mt-14",
1965
1977
  compact
1966
1978
  ? "flex min-h-0 flex-1 flex-col px-4 pb-5 pt-4"
1967
- : "flex min-h-0 flex-1 flex-col px-1 pb-5 pt-4",
1979
+ : "flex min-h-0 flex-1 flex-col overflow-hidden px-3 pb-3 pt-3",
1968
1980
  )}
1969
1981
  >
1970
1982
  {!compact ? (
@@ -1992,9 +2004,18 @@ export default function RecordingPage() {
1992
2004
  </section>
1993
2005
  );
1994
2006
 
1995
- const renderSidePanel = () => {
2007
+ const renderSidePanel = (compact = false) => {
1996
2008
  return (
1997
2009
  <>
2010
+ {recording.enableComments ? (
2011
+ <TabsContent
2012
+ forceMount
2013
+ value="comments"
2014
+ className="mt-0 flex min-h-0 flex-1 flex-col overflow-hidden data-[state=inactive]:hidden"
2015
+ >
2016
+ {renderCommentsSection(compact)}
2017
+ </TabsContent>
2018
+ ) : null}
1998
2019
  <TabsContent
1999
2020
  value="transcript"
2000
2021
  className="mt-0 flex-1 min-h-0 data-[state=inactive]:hidden"
@@ -2035,26 +2056,6 @@ export default function RecordingPage() {
2035
2056
  isRegenerating={requestTranscript.isPending}
2036
2057
  />
2037
2058
  </TabsContent>
2038
- <TabsContent
2039
- value="agent"
2040
- className="mt-0 flex min-h-0 flex-1 flex-col overflow-y-auto data-[state=inactive]:hidden"
2041
- >
2042
- <AgentPanel
2043
- emptyStateText={t("recordingPage.askAboutClip")}
2044
- dynamicSuggestions={false}
2045
- scope={{ type: "recording", id: recording.id }}
2046
- missingApiKeySetupLayout="sidebar"
2047
- suggestions={[
2048
- t("recordingPage.summarizeClip"),
2049
- t("recordingPage.findKeyMoments"),
2050
- t("recordingPage.listFollowUpActions"),
2051
- t("recordingPage.draftQuestions"),
2052
- ]}
2053
- browserTabId={browserTabId}
2054
- showHeader={false}
2055
- showTabBar={false}
2056
- />
2057
- </TabsContent>
2058
2059
  {browserDiagnostics ? (
2059
2060
  <TabsContent
2060
2061
  value="debug"
@@ -2392,7 +2393,7 @@ export default function RecordingPage() {
2392
2393
  }
2393
2394
  openSidePanel(value as ToolbarPanel);
2394
2395
  }}
2395
- className="clips-recording-view grid h-full min-h-0 w-full max-w-full grid-cols-1 overflow-x-hidden bg-background lg:grid-cols-[minmax(0,1fr)_auto] lg:grid-rows-[minmax(0,1fr)] lg:overflow-hidden [&_.agent-composer-root]:!border-0 [&_.agent-composer-root]:!bg-background"
2396
+ className="clips-recording-view grid h-full min-h-0 w-full max-w-full grid-cols-1 overflow-x-hidden bg-background lg:grid-cols-[minmax(0,1fr)_auto] lg:grid-rows-[minmax(0,1fr)] lg:overflow-hidden"
2396
2397
  >
2397
2398
  {/* Main video column */}
2398
2399
  <div className="contents">
@@ -2407,11 +2408,11 @@ export default function RecordingPage() {
2407
2408
  {editing && canUseNativeEditor ? (
2408
2409
  <EditorLayout recordingId={recording.id} className="flex-1" />
2409
2410
  ) : (
2410
- <div className="mx-auto flex min-h-0 w-full flex-1 flex-col gap-0 sm:gap-4 lg:max-w-[calc(177.778dvh-35.556rem)]">
2411
+ <div className="mx-auto flex min-h-0 w-full flex-1 flex-col gap-0 sm:gap-4 lg:max-w-[min(100%,1600px,calc(177.778dvh-35.556rem))]">
2411
2412
  <div className="flex w-full shrink-0 justify-center">
2412
- {/* A 16:9 width derived from 100dvh - 20rem keeps the
2413
- recording context and start of the discussion in view on
2414
- displays that are both very wide and very tall. */}
2413
+ {/* Let the viewer grow on wide displays without pushing the
2414
+ discussion below the first scrollable viewport. The comments
2415
+ list owns the desktop scroll so the player stays in context. */}
2415
2416
  <div className="relative aspect-video w-full overflow-hidden bg-card shadow-sm ring-1 ring-border sm:rounded-2xl">
2416
2417
  <VideoPlayer
2417
2418
  ref={playerRef}
@@ -2479,7 +2480,7 @@ export default function RecordingPage() {
2479
2480
  onDraftChange={setCommentDraft}
2480
2481
  onClose={() => setCommentOpen(false)}
2481
2482
  onAdded={() => {
2482
- setPanel("comments");
2483
+ if (isCompactLayout) setPanel("comments");
2483
2484
  void playerDataQ.refetch();
2484
2485
  }}
2485
2486
  />
@@ -2588,26 +2589,22 @@ export default function RecordingPage() {
2588
2589
  </div>
2589
2590
  </div>
2590
2591
 
2591
- {isCompactLayout ? (
2592
+ {isCompactLayout && !globalAgentSidebarOpen ? (
2592
2593
  <RecordingSidePanel
2593
2594
  id="clip-activity-panel"
2594
2595
  className="mt-2 lg:hidden"
2595
2596
  tabs={renderPanelTabs()}
2596
2597
  >
2597
- {panel === "comments"
2598
- ? renderCommentsSection(true)
2599
- : renderSidePanel()}
2598
+ {renderSidePanel(true)}
2600
2599
  </RecordingSidePanel>
2601
- ) : (
2602
- renderCommentsSection()
2603
- )}
2600
+ ) : null}
2604
2601
  </div>
2605
2602
  )}
2606
2603
  </div>
2607
2604
  </div>
2608
2605
 
2609
2606
  {/* Side panel */}
2610
- {!editing && !isCompactLayout && panel && panel !== "comments" ? (
2607
+ {!editing && !isCompactLayout && !globalAgentSidebarOpen && panel ? (
2611
2608
  <RecordingSidePanel
2612
2609
  className="hidden lg:col-start-2 lg:row-start-1 lg:flex lg:w-[360px] xl:w-[420px] 2xl:w-[440px]"
2613
2610
  tabs={renderPanelTabs()}
@@ -3,7 +3,8 @@ import { useMemo } from "react";
3
3
  import { useParams } from "react-router";
4
4
 
5
5
  import { LibraryGrid } from "@/components/library/library-grid";
6
- import { useFolders } from "@/hooks/use-library";
6
+ import { LibraryPrimaryActions } from "@/components/library/library-primary-actions";
7
+ import { useFolders, useOrganizations, useSpaces } from "@/hooks/use-library";
7
8
  import enMessages from "@/i18n/en-US";
8
9
 
9
10
  export function meta() {
@@ -17,7 +18,18 @@ export default function SpaceFolderRoute() {
17
18
  folderId: string;
18
19
  }>();
19
20
 
20
- const { data: folders } = useFolders({ spaceId });
21
+ const { data: organizations } = useOrganizations();
22
+ const currentOrganizationId =
23
+ organizations?.currentId ?? organizations?.organizations?.[0]?.id;
24
+ const { data: spacesData } = useSpaces(currentOrganizationId);
25
+ const space = (spacesData?.spaces ?? []).find(
26
+ (candidate: any) => candidate.id === spaceId,
27
+ );
28
+
29
+ const { data: folders } = useFolders({
30
+ organizationId: currentOrganizationId,
31
+ spaceId,
32
+ });
21
33
  const folder = useMemo(
22
34
  () =>
23
35
  (folders?.folders ?? []).find((f: any) => f.id === folderId) as
@@ -33,6 +45,17 @@ export default function SpaceFolderRoute() {
33
45
  folderId={folderId}
34
46
  emptyKind="folder"
35
47
  title={folder?.name ?? t("navigation.folder")}
48
+ breadcrumbItems={[
49
+ { label: t("navigation.spaces"), to: "/spaces" },
50
+ {
51
+ label: space?.name ?? t("navigation.space"),
52
+ to: `/spaces/${spaceId}`,
53
+ },
54
+ { label: folder?.name ?? t("navigation.folder") },
55
+ ]}
56
+ extraActions={
57
+ <LibraryPrimaryActions folderId={folderId} spaceId={spaceId} />
58
+ }
36
59
  />
37
60
  );
38
61
  }
@@ -1,10 +1,9 @@
1
1
  import { useT } from "@agent-native/core/client/i18n";
2
- import { useMemo } from "react";
3
2
  import { useParams } from "react-router";
4
3
 
5
- import { FolderTree, type FolderNode } from "@/components/library/folder-tree";
6
4
  import { LibraryGrid } from "@/components/library/library-grid";
7
- import { useFolders, useSpaces, useOrganizations } from "@/hooks/use-library";
5
+ import { LibraryPrimaryActions } from "@/components/library/library-primary-actions";
6
+ import { useSpaces, useOrganizations } from "@/hooks/use-library";
8
7
  import enMessages from "@/i18n/en-US";
9
8
 
10
9
  export function meta() {
@@ -20,64 +19,18 @@ export default function SpaceRoute() {
20
19
  const { data: spacesData } = useSpaces(currentOrganizationId);
21
20
  const space = (spacesData?.spaces ?? []).find((s: any) => s.id === spaceId);
22
21
 
23
- const { data: folders } = useFolders({
24
- organizationId: currentOrganizationId,
25
- spaceId,
26
- });
27
- const folderList: FolderNode[] = useMemo(
28
- () =>
29
- (folders?.folders ?? [])
30
- .filter((f: any) => f.spaceId === spaceId)
31
- .map((f: any) => ({
32
- id: f.id,
33
- parentId: f.parentId ?? null,
34
- spaceId: f.spaceId ?? null,
35
- name: f.name,
36
- })),
37
- [folders, spaceId],
38
- );
39
-
40
22
  return (
41
- <div className="flex flex-1 min-h-0">
42
- {/* Space-scoped folder rail */}
43
- <aside className="hidden w-[220px] shrink-0 flex-col border-r border-border bg-sidebar p-2 lg:flex">
44
- <div className="px-2 py-1.5">
45
- <div className="flex items-center gap-2">
46
- <div
47
- className="h-5 w-5 rounded flex items-center justify-center text-[10px] text-white"
48
- style={{ background: (space as any)?.color ?? "#18181B" }}
49
- >
50
- {(space as any)?.iconEmoji ??
51
- (space as any)?.name?.slice(0, 1).toUpperCase() ??
52
- t("navigation.space").slice(0, 1)}
53
- </div>
54
- <span className="text-xs font-semibold text-foreground truncate">
55
- {(space as any)?.name ?? t("navigation.space")}
56
- </span>
57
- </div>
58
- </div>
59
- <div className="mt-2">
60
- <div className="px-2 pb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">
61
- {t("navigation.folders")}
62
- </div>
63
- <FolderTree
64
- folders={folderList}
65
- organizationId={currentOrganizationId}
66
- spaceId={spaceId ?? null}
67
- buildPath={(id) => `/spaces/${spaceId}/folder/${id}`}
68
- />
69
- </div>
70
- </aside>
71
-
72
- <div className="flex min-w-0 flex-1 flex-col">
73
- <LibraryGrid
74
- view="space"
75
- spaceId={spaceId}
76
- folderId={null}
77
- emptyKind="space"
78
- title={(space as any)?.name ?? t("navigation.space")}
79
- />
80
- </div>
81
- </div>
23
+ <LibraryGrid
24
+ view="space"
25
+ spaceId={spaceId}
26
+ folderId={null}
27
+ emptyKind="space"
28
+ title={(space as any)?.name ?? t("navigation.space")}
29
+ breadcrumbItems={[
30
+ { label: t("navigation.spaces"), to: "/spaces" },
31
+ { label: (space as any)?.name ?? t("navigation.space") },
32
+ ]}
33
+ extraActions={<LibraryPrimaryActions spaceId={spaceId} />}
34
+ />
82
35
  );
83
36
  }
@@ -1,24 +1,20 @@
1
1
  import { useT } from "@agent-native/core/client/i18n";
2
2
  import { useOrgRole } from "@agent-native/core/client/org";
3
3
  import { IconPlus, IconUsersGroup } from "@tabler/icons-react";
4
- import { useState } from "react";
4
+ import { useEffect, useState } from "react";
5
5
 
6
6
  import { CreateSpaceDialog } from "@/components/library/create-space-dialog";
7
+ import { AppEmptyState } from "@/components/library/empty-state";
7
8
  import {
8
9
  PageBreadcrumb,
9
10
  PageHeader,
10
11
  PageHeaderPrimaryAction,
11
12
  } from "@/components/library/page-header";
12
13
  import { SpaceCard, type SpaceCardData } from "@/components/library/space-card";
13
- import {
14
- Empty,
15
- EmptyDescription,
16
- EmptyHeader,
17
- EmptyMedia,
18
- EmptyTitle,
19
- } from "@/components/ui/empty";
14
+ import { Button } from "@/components/ui/button";
20
15
  import { useSpaces, useOrganizations } from "@/hooks/use-library";
21
16
  import enMessages from "@/i18n/en-US";
17
+ import { OPEN_CREATE_SPACE_EVENT } from "@/lib/command-events";
22
18
 
23
19
  export function meta() {
24
20
  return [{ title: enMessages.clipsFinalRaw.spacesPageTitle }];
@@ -44,6 +40,18 @@ export default function SpacesIndexRoute() {
44
40
  const currentOrganizationId =
45
41
  organizations?.currentId ?? organizations?.organizations?.[0]?.id;
46
42
  const { data, isLoading, refetch } = useSpaces(currentOrganizationId);
43
+ const createSpaceLabel = t("createSpaceDialog.newSpace");
44
+
45
+ useEffect(() => {
46
+ if (!canManageOrg) return;
47
+ const handleOpenCreateSpace = () => setCreateOpen(true);
48
+ window.addEventListener(OPEN_CREATE_SPACE_EVENT, handleOpenCreateSpace);
49
+ return () =>
50
+ window.removeEventListener(
51
+ OPEN_CREATE_SPACE_EVENT,
52
+ handleOpenCreateSpace,
53
+ );
54
+ }, [canManageOrg]);
47
55
 
48
56
  const spaces: SpaceCardData[] = (data?.spaces ?? []).map((s: any) => ({
49
57
  id: s.id,
@@ -58,12 +66,12 @@ export default function SpacesIndexRoute() {
58
66
  return (
59
67
  <div className="flex flex-1 flex-col min-h-0">
60
68
  <PageHeader>
61
- <PageBreadcrumb label={t("navigation.spaces")} />
62
- {canManageOrg && (
69
+ <PageBreadcrumb items={[{ label: t("navigation.spaces") }]} />
70
+ {canManageOrg && spaces.length > 0 && (
63
71
  <div className="ml-auto">
64
72
  <PageHeaderPrimaryAction onClick={() => setCreateOpen(true)}>
65
73
  <IconPlus />
66
- {t("createSpaceDialog.newSpace")}
74
+ {createSpaceLabel}
67
75
  </PageHeaderPrimaryAction>
68
76
  </div>
69
77
  )}
@@ -77,19 +85,21 @@ export default function SpacesIndexRoute() {
77
85
  ))}
78
86
  </div>
79
87
  ) : spaces.length === 0 ? (
80
- <Empty className="min-h-full rounded-none">
81
- <EmptyHeader>
82
- <EmptyMedia variant="icon">
83
- <IconUsersGroup />
84
- </EmptyMedia>
85
- <EmptyTitle>{t("navigation.noSpaces")}</EmptyTitle>
86
- {canManageOrg ? (
87
- <EmptyDescription>
88
- {t("createSpaceDialog.description")}
89
- </EmptyDescription>
90
- ) : null}
91
- </EmptyHeader>
92
- </Empty>
88
+ <AppEmptyState
89
+ icon={IconUsersGroup}
90
+ title={t("navigation.noSpaces")}
91
+ description={
92
+ canManageOrg ? t("createSpaceDialog.description") : undefined
93
+ }
94
+ content={
95
+ canManageOrg ? (
96
+ <Button onClick={() => setCreateOpen(true)} size="sm">
97
+ <IconPlus />
98
+ {createSpaceLabel}
99
+ </Button>
100
+ ) : null
101
+ }
102
+ />
93
103
  ) : (
94
104
  <div className="grid gap-4 [grid-template-columns:repeat(auto-fill,minmax(240px,1fr))]">
95
105
  {spaces.map((s) => (
@@ -190,7 +190,7 @@ export default function TrashRoute() {
190
190
  return (
191
191
  <div className="flex flex-1 flex-col min-h-0">
192
192
  <PageHeader>
193
- <PageBreadcrumb label={t("trashRoute.title")} />
193
+ <PageBreadcrumb items={[{ label: t("trashRoute.title") }]} />
194
194
  <div className="ms-auto flex items-center gap-2">
195
195
  {selectedIds.length > 0 && (
196
196
  <>
@@ -1,7 +1,7 @@
1
1
  import { useExperiment } from "@agent-native/core/client/experiments";
2
2
  import { CLIPS_MEETINGS, CLIPS_WISPRFLOW } from "@shared/experiments";
3
3
  import { useEffect, useRef } from "react";
4
- import { Outlet, useLocation, useNavigate } from "react-router";
4
+ import { Outlet, useNavigate } from "react-router";
5
5
 
6
6
  import { LibraryLayout } from "@/components/library/library-layout";
7
7
  import { useAutoTitleBridge } from "@/hooks/use-auto-title";
@@ -71,8 +71,6 @@ function useGlobalSequenceShortcuts() {
71
71
  // Pathless layout route — keeps the left sidebar + agent chat mounted across
72
72
  // every library/space/archive/trash navigation. See client-side-routing skill.
73
73
  export default function AppLayoutRoute() {
74
- const location = useLocation();
75
- const recordingWorkspace = /^\/r\/[^/]+\/?$/.test(location.pathname);
76
74
  // Watch for server-queued title delegations and dispatch them to the agent
77
75
  // chat. `sendToAgentChat` is browser-only so the server can't call it
78
76
  // directly; this bridge is how `request-transcript`'s "auto-title when the
@@ -83,7 +81,7 @@ export default function AppLayoutRoute() {
83
81
  useGlobalSequenceShortcuts();
84
82
 
85
83
  return (
86
- <LibraryLayout showAgentSidebar={!recordingWorkspace}>
84
+ <LibraryLayout>
87
85
  <Outlet />
88
86
  </LibraryLayout>
89
87
  );