@agent-native/dispatch 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/components/app-icon.d.ts +2 -1
  2. package/dist/components/app-icon.d.ts.map +1 -1
  3. package/dist/components/app-icon.js +4 -2
  4. package/dist/components/app-icon.js.map +1 -1
  5. package/dist/components/app-list-row.d.ts +1 -1
  6. package/dist/components/app-list-row.d.ts.map +1 -1
  7. package/dist/components/app-list-row.js +1 -1
  8. package/dist/components/app-list-row.js.map +1 -1
  9. package/dist/components/app-open-actions.d.ts +2 -1
  10. package/dist/components/app-open-actions.d.ts.map +1 -1
  11. package/dist/components/app-open-actions.js +2 -2
  12. package/dist/components/app-open-actions.js.map +1 -1
  13. package/dist/components/dispatch-control-plane.d.ts.map +1 -1
  14. package/dist/components/dispatch-control-plane.js +16 -17
  15. package/dist/components/dispatch-control-plane.js.map +1 -1
  16. package/dist/components/layout/Layout.d.ts.map +1 -1
  17. package/dist/components/layout/Layout.js +91 -57
  18. package/dist/components/layout/Layout.js.map +1 -1
  19. package/dist/components/layout/workspace-apps-rail.d.ts.map +1 -1
  20. package/dist/components/layout/workspace-apps-rail.js +1 -1
  21. package/dist/components/layout/workspace-apps-rail.js.map +1 -1
  22. package/dist/components/other-apps-section.d.ts +3 -1
  23. package/dist/components/other-apps-section.d.ts.map +1 -1
  24. package/dist/components/other-apps-section.js +9 -6
  25. package/dist/components/other-apps-section.js.map +1 -1
  26. package/dist/components/workspace-app-card.d.ts.map +1 -1
  27. package/dist/components/workspace-app-card.js +7 -2
  28. package/dist/components/workspace-app-card.js.map +1 -1
  29. package/dist/components/workspace-template-card.d.ts.map +1 -1
  30. package/dist/components/workspace-template-card.js +2 -5
  31. package/dist/components/workspace-template-card.js.map +1 -1
  32. package/dist/lib/overview-chat.d.ts.map +1 -1
  33. package/dist/lib/overview-chat.js +0 -8
  34. package/dist/lib/overview-chat.js.map +1 -1
  35. package/package.json +3 -3
  36. package/src/components/app-icon.tsx +6 -2
  37. package/src/components/app-list-row.tsx +1 -1
  38. package/src/components/app-open-actions.tsx +4 -2
  39. package/src/components/dispatch-control-plane.spec.tsx +43 -7
  40. package/src/components/dispatch-control-plane.tsx +56 -75
  41. package/src/components/layout/Layout.spec.tsx +121 -1
  42. package/src/components/layout/Layout.tsx +187 -114
  43. package/src/components/layout/workspace-apps-rail.tsx +1 -0
  44. package/src/components/other-apps-section.tsx +61 -40
  45. package/src/components/workspace-app-card.spec.tsx +63 -66
  46. package/src/components/workspace-app-card.tsx +10 -3
  47. package/src/components/workspace-template-card.spec.tsx +7 -8
  48. package/src/components/workspace-template-card.tsx +21 -11
  49. package/src/lib/overview-chat.spec.ts +0 -19
  50. package/src/lib/overview-chat.ts +0 -9
  51. package/src/styles/dispatch.css +2 -2
@@ -214,6 +214,16 @@ interface ChatFirstEmbedSessionInput {
214
214
  chrome: "minimal";
215
215
  }
216
216
 
217
+ interface ChatFirstGrantedAppSummary {
218
+ id: string;
219
+ name: string;
220
+ url?: string | null;
221
+ }
222
+
223
+ interface ChatFirstGrantedAppsResult {
224
+ apps: ChatFirstGrantedAppSummary[];
225
+ }
226
+
217
227
  export function buildChatFirstEmbedSessionInput(
218
228
  appId: string,
219
229
  path: string,
@@ -663,7 +673,7 @@ function DispatchChatsSection({
663
673
  return (
664
674
  <div
665
675
  className={cn(
666
- "ms-4 min-w-0 space-y-0.5",
676
+ "min-w-0 space-y-0.5",
667
677
  showNewChat && "dispatch-chat-first-chats",
668
678
  )}
669
679
  >
@@ -730,7 +740,7 @@ function DispatchChatsSection({
730
740
  <Skeleton className="h-3 w-3/4 rounded" />
731
741
  </div>
732
742
  ))}
733
- {chatFirstMode ? (
743
+ {chatFirstMode && (chatsLoading || visibleThreads.length > 0) ? (
734
744
  <ChatFirstChatHistory
735
745
  items={chatItems}
736
746
  activeId={displayedActiveThreadId}
@@ -1005,12 +1015,17 @@ export function NavContent({
1005
1015
  onChatFirstAppOpen?.(app);
1006
1016
  onNavigate?.();
1007
1017
  }}
1018
+ onOpenAllApps={() => {
1019
+ navigate(dispatchNavLinkTarget("/apps"));
1020
+ onNavigate?.();
1021
+ }}
1008
1022
  createAppTrigger={chatFirstCreateAppTrigger}
1009
1023
  renderIcon={(app) => (
1010
1024
  <AppIcon
1011
1025
  id={app.id}
1012
1026
  name={app.name}
1013
1027
  size="sm"
1028
+ monochrome
1014
1029
  className="size-5 rounded-md"
1015
1030
  />
1016
1031
  )}
@@ -1115,6 +1130,33 @@ export function NavContent({
1115
1130
  );
1116
1131
  };
1117
1132
 
1133
+ const bottomNavigation = (
1134
+ <nav className={cn("py-1", collapsed ? "px-1.5" : "px-2")}>
1135
+ <ul
1136
+ className={cn(
1137
+ collapsed ? "flex flex-col items-center gap-1" : "space-y-0.5",
1138
+ )}
1139
+ >
1140
+ {BOTTOM_NAV_ITEMS.map(renderNavItem)}
1141
+ </ul>
1142
+ </nav>
1143
+ );
1144
+ const organizationPicker = (
1145
+ <div
1146
+ className={cn("py-2", collapsed ? "flex justify-center px-1" : "px-3")}
1147
+ >
1148
+ <OrgSwitcher compact={collapsed} reserveSpace />
1149
+ </div>
1150
+ );
1151
+ const sidebarFooterActions = (
1152
+ <SidebarFooterActions
1153
+ collapsed={collapsed}
1154
+ feedback={feedbackButton}
1155
+ search={searchButton}
1156
+ collapse={collapseButton}
1157
+ />
1158
+ );
1159
+
1118
1160
  return (
1119
1161
  <>
1120
1162
  <div
@@ -1206,39 +1248,16 @@ export function NavContent({
1206
1248
  </nav>
1207
1249
 
1208
1250
  <div className="mt-auto shrink-0">
1209
- <nav className={cn("py-1", collapsed ? "px-1.5" : "px-2")}>
1210
- <ul
1211
- className={cn(
1212
- collapsed ? "flex flex-col items-center gap-1" : "space-y-0.5",
1213
- )}
1214
- >
1215
- {BOTTOM_NAV_ITEMS.map(renderNavItem)}
1216
- </ul>
1217
- </nav>
1218
- <div
1219
- className={cn(
1220
- "py-2",
1221
- collapsed ? "flex justify-center px-1" : "px-3",
1222
- )}
1223
- >
1224
- <OrgSwitcher compact={collapsed} reserveSpace />
1225
- </div>
1251
+ {bottomNavigation}
1252
+ {organizationPicker}
1226
1253
  </div>
1227
- <SidebarFooterActions
1228
- collapsed={collapsed}
1229
- feedback={feedbackButton}
1230
- search={searchButton}
1231
- collapse={collapseButton}
1232
- />
1254
+ {sidebarFooterActions}
1233
1255
  </div>
1234
- {chatFirstMode && collapsed ? (
1256
+ {chatFirstMode ? (
1235
1257
  <div className="mt-auto shrink-0">
1236
- <SidebarFooterActions
1237
- collapsed
1238
- feedback={feedbackButton}
1239
- search={searchButton}
1240
- collapse={collapseButton}
1241
- />
1258
+ {bottomNavigation}
1259
+ {organizationPicker}
1260
+ {collapsed ? sidebarFooterActions : null}
1242
1261
  </div>
1243
1262
  ) : null}
1244
1263
  </>
@@ -1270,7 +1289,11 @@ export function Layout({
1270
1289
  const isChatRoute =
1271
1290
  localPathname === "/chat" || localPathname.startsWith("/chat/");
1272
1291
  const chatFirstSurfaceScope = threadIdFromPath(localPathname) ?? "new";
1273
- const isWorkspaceAppHostRoute = localPathname.startsWith("/apps/");
1292
+ const isWorkspaceAppRoute = localPathname.startsWith("/apps/");
1293
+ const isWorkspaceAppHostRoute =
1294
+ isWorkspaceAppRoute &&
1295
+ typeof window !== "undefined" &&
1296
+ new URLSearchParams(window.location.search).get("embedded") === "1";
1274
1297
  const [chatFirstPreference, setChatFirstPreference] = useState(() =>
1275
1298
  readChatFirstMode(),
1276
1299
  );
@@ -1285,23 +1308,40 @@ export function Layout({
1285
1308
  const chatFirstAppsQuery = useActionQuery<WorkspaceAppSummary[]>(
1286
1309
  "list-workspace-apps",
1287
1310
  { includeAgentCards: false },
1288
- { enabled: chatFirstMode && isChatRoute },
1311
+ { enabled: chatFirstMode },
1312
+ );
1313
+ const chatFirstGrantedAppsQuery = useActionQuery<ChatFirstGrantedAppsResult>(
1314
+ "list_apps",
1315
+ {},
1316
+ { enabled: chatFirstMode },
1289
1317
  );
1290
1318
  const chatFirstWorkspaceApps = useMemo(
1291
1319
  () => mergeChatFirstWorkspaceApps(chatFirstAppsQuery.data),
1292
1320
  [chatFirstAppsQuery.data],
1293
1321
  );
1294
- const chatFirstAppRegistrations = useMemo<ChatFirstAppRegistration[]>(
1295
- () =>
1296
- chatFirstWorkspaceApps.map((app) => ({
1322
+ const chatFirstAppRegistrations = useMemo<ChatFirstAppRegistration[]>(() => {
1323
+ const registrations = new Map<string, ChatFirstAppRegistration>();
1324
+ for (const app of chatFirstWorkspaceApps) {
1325
+ registrations.set(app.id.toLowerCase(), {
1297
1326
  id: app.id,
1298
1327
  name: app.name,
1299
1328
  path: app.path,
1300
1329
  url: app.url,
1301
1330
  enabled: app.status !== "pending" && app.archived !== true,
1302
- })),
1303
- [chatFirstWorkspaceApps],
1304
- );
1331
+ });
1332
+ }
1333
+ for (const app of chatFirstGrantedAppsQuery.data?.apps ?? []) {
1334
+ const id = app.id.trim();
1335
+ if (!id || registrations.has(id.toLowerCase())) continue;
1336
+ registrations.set(id.toLowerCase(), {
1337
+ id,
1338
+ name: app.name,
1339
+ url: app.url,
1340
+ enabled: true,
1341
+ });
1342
+ }
1343
+ return [...registrations.values()];
1344
+ }, [chatFirstGrantedAppsQuery.data?.apps, chatFirstWorkspaceApps]);
1305
1345
  const chatFirstAppItems = useMemo<ChatFirstAppItem[]>(
1306
1346
  () =>
1307
1347
  chatFirstWorkspaceApps.map((app) => ({
@@ -1358,15 +1398,29 @@ export function Layout({
1358
1398
  ) ?? null,
1359
1399
  [chatFirstSurfaceTabs],
1360
1400
  );
1361
- const activeChatFirstApp = useMemo(
1401
+ const chatFirstAppTakesMain =
1402
+ chatFirstMode && isChatRoute && activeChatFirstSurfaceTab?.kind === "app";
1403
+ const activeChatFirstAppRegistration = useMemo(
1362
1404
  () =>
1363
1405
  activeChatFirstSurfaceTab?.kind === "app" &&
1364
1406
  activeChatFirstSurfaceTab.appId
1365
- ? (chatFirstWorkspaceApps.find(
1407
+ ? (chatFirstAppRegistrations.find(
1366
1408
  (app) => app.id === activeChatFirstSurfaceTab.appId,
1367
1409
  ) ?? null)
1368
1410
  : null,
1369
- [activeChatFirstSurfaceTab, chatFirstWorkspaceApps],
1411
+ [activeChatFirstSurfaceTab, chatFirstAppRegistrations],
1412
+ );
1413
+ const activeChatFirstApp = useMemo(
1414
+ () =>
1415
+ activeChatFirstAppRegistration
1416
+ ? {
1417
+ id: activeChatFirstAppRegistration.id,
1418
+ name:
1419
+ activeChatFirstAppRegistration.name ??
1420
+ activeChatFirstAppRegistration.id,
1421
+ }
1422
+ : null,
1423
+ [activeChatFirstAppRegistration],
1370
1424
  );
1371
1425
  const chatFirstAgentActivities = useMemo<ChatFirstAgentActivity[]>(
1372
1426
  () =>
@@ -1384,7 +1438,8 @@ export function Layout({
1384
1438
  activeChatFirstSurfaceTab?.kind === "app"
1385
1439
  ? (activeChatFirstSurfaceTab.path ??
1386
1440
  chatFirstPane?.path ??
1387
- activeChatFirstApp?.path)
1441
+ activeChatFirstAppRegistration?.path ??
1442
+ "/")
1388
1443
  : null;
1389
1444
  useEffect(() => {
1390
1445
  if (!chatFirstMode || !isChatRoute || !activeChatFirstApp) {
@@ -1446,6 +1501,12 @@ export function Layout({
1446
1501
  (pane: DispatchChatFirstPane) => {
1447
1502
  setChatFirstNotice(null);
1448
1503
  closeChatFirstSessionWatch();
1504
+ if (!isChatRoute) {
1505
+ navigateWithAgentChatViewTransition(
1506
+ navigate,
1507
+ dispatchNavLinkTarget("/chat"),
1508
+ );
1509
+ }
1449
1510
  const app = chatFirstAppRegistrations.find(
1450
1511
  (candidate) => candidate.id === pane.appId,
1451
1512
  );
@@ -1460,12 +1521,16 @@ export function Layout({
1460
1521
  ...(pane.path ? { path: pane.path } : {}),
1461
1522
  ...(pane.view ? { view: pane.view } : {}),
1462
1523
  });
1524
+ setChatFirstSurfacePanelOpen(true);
1463
1525
  persistChatFirstPane(pane);
1464
1526
  },
1465
1527
  [
1466
1528
  chatFirstAppRegistrations,
1467
1529
  chatFirstSurfaceTabsStore,
1468
1530
  persistChatFirstPane,
1531
+ isChatRoute,
1532
+ navigate,
1533
+ setChatFirstSurfacePanelOpen,
1469
1534
  ],
1470
1535
  );
1471
1536
  const openChatFirstSurface = useCallback(
@@ -1504,7 +1569,7 @@ export function Layout({
1504
1569
  );
1505
1570
  const resolveChatFirstOpenApp = useCallback(
1506
1571
  (detail: ChatFirstOpenAppDetail) => {
1507
- if (chatFirstAppsQuery.isLoading) {
1572
+ if (chatFirstAppsQuery.isLoading || chatFirstGrantedAppsQuery.isLoading) {
1508
1573
  pendingChatFirstOpenAppRef.current = detail;
1509
1574
  return;
1510
1575
  }
@@ -1535,11 +1600,10 @@ export function Layout({
1535
1600
  chatFirstAppRegistrations,
1536
1601
  chatFirstAppsQuery.isError,
1537
1602
  chatFirstAppsQuery.isLoading,
1603
+ chatFirstGrantedAppsQuery.isLoading,
1538
1604
  openChatFirstPane,
1539
1605
  ],
1540
1606
  );
1541
- const sidebarBeforeAppRef = useRef<boolean | null>(null);
1542
- const sidebarAutoCollapsedRef = useRef(false);
1543
1607
  const chatHomeHandoffActive = useAgentChatHomeHandoff({
1544
1608
  storageKey: "dispatch",
1545
1609
  activePath: localPathname,
@@ -1610,7 +1674,7 @@ export function Layout({
1610
1674
  );
1611
1675
 
1612
1676
  useEffect(() => {
1613
- if (!chatFirstMode || !isChatRoute) {
1677
+ if (!chatFirstMode) {
1614
1678
  setChatFirstPane(null);
1615
1679
  closeChatFirstSessionWatch();
1616
1680
  chatFirstSurfaceTabsStore.closeAll();
@@ -1713,7 +1777,7 @@ export function Layout({
1713
1777
  useEffect(() => {
1714
1778
  const tabCount = chatFirstSurfaceTabs.tabs.length;
1715
1779
  const previousTabCount = previousChatFirstSurfaceTabCountRef.current;
1716
- if (!chatFirstMode || !isChatRoute) {
1780
+ if (!chatFirstMode) {
1717
1781
  setChatFirstSurfacePanelOpen(false);
1718
1782
  } else if (
1719
1783
  tabCount > 0 &&
@@ -1748,12 +1812,19 @@ export function Layout({
1748
1812
 
1749
1813
  useEffect(() => {
1750
1814
  const pending = pendingChatFirstOpenAppRef.current;
1751
- if (!pending || chatFirstAppsQuery.isLoading) return;
1815
+ if (
1816
+ !pending ||
1817
+ chatFirstAppsQuery.isLoading ||
1818
+ chatFirstGrantedAppsQuery.isLoading
1819
+ )
1820
+ return;
1752
1821
  resolveChatFirstOpenApp(pending);
1753
1822
  }, [
1754
1823
  chatFirstAppsQuery.data,
1755
1824
  chatFirstAppsQuery.isError,
1756
1825
  chatFirstAppsQuery.isLoading,
1826
+ chatFirstGrantedAppsQuery.data,
1827
+ chatFirstGrantedAppsQuery.isLoading,
1757
1828
  resolveChatFirstOpenApp,
1758
1829
  ]);
1759
1830
 
@@ -1774,28 +1845,6 @@ export function Layout({
1774
1845
  isChatRoute,
1775
1846
  ]);
1776
1847
 
1777
- useEffect(() => {
1778
- if (isWorkspaceAppHostRoute) {
1779
- if (!sidebarAutoCollapsedRef.current) {
1780
- sidebarAutoCollapsedRef.current = true;
1781
- sidebarBeforeAppRef.current = sidebarCollapsed;
1782
- }
1783
- if (!sidebarCollapsed) setSidebarCollapsed(true);
1784
- return;
1785
- }
1786
-
1787
- if (!sidebarAutoCollapsedRef.current) return;
1788
- sidebarAutoCollapsedRef.current = false;
1789
- const previousSidebarState = sidebarBeforeAppRef.current;
1790
- sidebarBeforeAppRef.current = null;
1791
- if (
1792
- previousSidebarState !== null &&
1793
- previousSidebarState !== sidebarCollapsed
1794
- ) {
1795
- setSidebarCollapsed(previousSidebarState);
1796
- }
1797
- }, [isWorkspaceAppHostRoute, sidebarCollapsed]);
1798
-
1799
1848
  useEffect(() => {
1800
1849
  if (typeof window === "undefined" || isWorkspaceAppHostRoute) return;
1801
1850
  try {
@@ -1892,11 +1941,17 @@ export function Layout({
1892
1941
  (tab: ChatFirstSurfaceTab) => {
1893
1942
  if (tab.kind === "app") {
1894
1943
  if (tab.id !== chatFirstSurfaceTabs.activeTabId) return null;
1895
- const app = tab.appId
1896
- ? (chatFirstWorkspaceApps.find(
1944
+ const registration = tab.appId
1945
+ ? (chatFirstAppRegistrations.find(
1897
1946
  (candidate) => candidate.id === tab.appId,
1898
1947
  ) ?? null)
1899
1948
  : null;
1949
+ const app = registration
1950
+ ? {
1951
+ id: registration.id,
1952
+ name: registration.name ?? registration.id,
1953
+ }
1954
+ : null;
1900
1955
  return (
1901
1956
  <ChatFirstAppPane
1902
1957
  app={app}
@@ -1999,7 +2054,7 @@ export function Layout({
1999
2054
  chatFirstEmbedUrl,
2000
2055
  chatFirstSessionWatch.target,
2001
2056
  chatFirstSurfaceTabs.activeTabId,
2002
- chatFirstWorkspaceApps,
2057
+ chatFirstAppRegistrations,
2003
2058
  closeChatFirstSurfaceTab,
2004
2059
  renderChatFirstWatchChat,
2005
2060
  ],
@@ -2067,7 +2122,7 @@ export function Layout({
2067
2122
  <main
2068
2123
  className={cn(
2069
2124
  "flex-1",
2070
- isChatRoute || isWorkspaceAppHostRoute
2125
+ isChatRoute || isWorkspaceAppRoute
2071
2126
  ? "min-h-0 overflow-hidden"
2072
2127
  : "overflow-y-auto",
2073
2128
  )}
@@ -2082,6 +2137,40 @@ export function Layout({
2082
2137
  </main>
2083
2138
  </div>
2084
2139
  );
2140
+ const chatFirstSurfaceTabsBar =
2141
+ activeChatFirstSurfaceTab?.kind === "app" ? null : (
2142
+ <ChatFirstSurfaceTabs
2143
+ tabs={chatFirstSurfaceTabs.tabs}
2144
+ activeTabId={chatFirstSurfaceTabs.activeTabId}
2145
+ onActivate={activateChatFirstSurfaceTab}
2146
+ onClose={closeChatFirstSurfaceTab}
2147
+ onCloseOthers={(tab) => {
2148
+ activateChatFirstSurfaceTab(tab);
2149
+ chatFirstSurfaceTabsStore.closeOthers(tab.id);
2150
+ }}
2151
+ onCloseToRight={(tab) => {
2152
+ const targetIndex = chatFirstSurfaceTabs.tabs.findIndex(
2153
+ (candidate) => candidate.id === tab.id,
2154
+ );
2155
+ const activeIndex = chatFirstSurfaceTabs.tabs.findIndex(
2156
+ (candidate) => candidate.id === chatFirstSurfaceTabs.activeTabId,
2157
+ );
2158
+ if (activeIndex > targetIndex) activateChatFirstSurfaceTab(tab);
2159
+ chatFirstSurfaceTabsStore.closeToRight(tab.id);
2160
+ }}
2161
+ onCloseAll={closeAllChatFirstSurfaceTabs}
2162
+ onOpenSurface={openChatFirstSurface}
2163
+ copy={chatFirstCopy}
2164
+ />
2165
+ );
2166
+ const chatFirstSurfaceContent =
2167
+ chatFirstSurfaceTabs.tabs.length > 0 ? (
2168
+ <ChatFirstSurfaceContent
2169
+ tabs={chatFirstSurfaceTabs.tabs}
2170
+ activeTabId={chatFirstSurfaceTabs.activeTabId}
2171
+ renderTab={renderChatFirstSurfaceTab}
2172
+ />
2173
+ ) : null;
2085
2174
  const content = isChatRoute ? (
2086
2175
  <div
2087
2176
  className={cn(
@@ -2089,47 +2178,31 @@ export function Layout({
2089
2178
  chatFirstMode && "dispatch-chat-first-surface",
2090
2179
  )}
2091
2180
  >
2092
- {appContent}
2093
- {chatFirstMode && chatFirstSurfacePanel.open ? (
2094
- <ChatFirstSurfacePanel
2095
- width={chatFirstSurfaceResize.width}
2096
- onResizePointerDown={chatFirstSurfaceResize.onPointerDown}
2097
- copy={chatFirstCopy}
2181
+ {chatFirstAppTakesMain ? (
2182
+ <div
2183
+ className="flex min-w-0 flex-1 flex-col overflow-hidden"
2184
+ data-dispatch-chat-first-main-app
2098
2185
  >
2099
- <ChatFirstSurfaceTabs
2100
- tabs={chatFirstSurfaceTabs.tabs}
2101
- activeTabId={chatFirstSurfaceTabs.activeTabId}
2102
- onActivate={activateChatFirstSurfaceTab}
2103
- onClose={closeChatFirstSurfaceTab}
2104
- onCloseOthers={(tab) => {
2105
- activateChatFirstSurfaceTab(tab);
2106
- chatFirstSurfaceTabsStore.closeOthers(tab.id);
2107
- }}
2108
- onCloseToRight={(tab) => {
2109
- const targetIndex = chatFirstSurfaceTabs.tabs.findIndex(
2110
- (candidate) => candidate.id === tab.id,
2111
- );
2112
- const activeIndex = chatFirstSurfaceTabs.tabs.findIndex(
2113
- (candidate) =>
2114
- candidate.id === chatFirstSurfaceTabs.activeTabId,
2115
- );
2116
- if (activeIndex > targetIndex) activateChatFirstSurfaceTab(tab);
2117
- chatFirstSurfaceTabsStore.closeToRight(tab.id);
2118
- }}
2119
- onCloseAll={closeAllChatFirstSurfaceTabs}
2120
- onOpenSurface={openChatFirstSurface}
2121
- copy={chatFirstCopy}
2122
- />
2123
- {chatFirstSurfaceTabs.tabs.length > 0 ? (
2124
- <ChatFirstSurfaceContent
2125
- tabs={chatFirstSurfaceTabs.tabs}
2126
- activeTabId={chatFirstSurfaceTabs.activeTabId}
2127
- renderTab={renderChatFirstSurfaceTab}
2128
- />
2186
+ {chatFirstSurfaceContent}
2187
+ </div>
2188
+ ) : (
2189
+ <>
2190
+ {appContent}
2191
+ {chatFirstMode && chatFirstSurfacePanel.open ? (
2192
+ <ChatFirstSurfacePanel
2193
+ width={chatFirstSurfaceResize.width}
2194
+ onResizePointerDown={chatFirstSurfaceResize.onPointerDown}
2195
+ copy={chatFirstCopy}
2196
+ >
2197
+ {chatFirstSurfaceTabsBar}
2198
+ {chatFirstSurfaceContent}
2199
+ </ChatFirstSurfacePanel>
2129
2200
  ) : null}
2130
- </ChatFirstSurfacePanel>
2131
- ) : null}
2201
+ </>
2202
+ )}
2132
2203
  </div>
2204
+ ) : isWorkspaceAppRoute ? (
2205
+ appContent
2133
2206
  ) : (
2134
2207
  <AgentSidebar
2135
2208
  position="right"
@@ -97,6 +97,7 @@ export function WorkspaceAppsRail({
97
97
  id={app.id}
98
98
  name={label}
99
99
  size="sm"
100
+ monochrome
100
101
  className={cn("size-5 rounded-md", active && "ring-1 ring-ring/30")}
101
102
  />
102
103
  {!collapsed ? <span className="truncate">{label}</span> : null}
@@ -1,5 +1,4 @@
1
1
  import { useT } from "@agent-native/core/client/i18n";
2
- import { IconStack2 } from "@tabler/icons-react";
3
2
 
4
3
  import { filterOtherApps, type ConnectedAppSummary } from "../lib/other-apps";
5
4
  import type { WorkspaceAppId } from "../lib/other-apps";
@@ -80,6 +79,8 @@ export function OtherAppsSection({
80
79
  onRetryConnectedApps,
81
80
  templateLabels,
82
81
  onRemixSuccess,
82
+ heading = "Other apps",
83
+ embeddedInList = false,
83
84
  className,
84
85
  }: {
85
86
  templates?: CuratedWorkspaceTemplatesResult;
@@ -96,6 +97,8 @@ export function OtherAppsSection({
96
97
  result: unknown,
97
98
  template: CuratedWorkspaceTemplate,
98
99
  ) => void;
100
+ heading?: string | null;
101
+ embeddedInList?: boolean;
99
102
  className?: string;
100
103
  }) {
101
104
  const t = useT();
@@ -109,26 +112,15 @@ export function OtherAppsSection({
109
112
 
110
113
  if (!isLoading && !hasError && entries.length === 0) return null;
111
114
 
112
- return (
113
- <section className={cn("space-y-3 border-t pt-6", className)}>
114
- <div className="flex min-w-0 items-start gap-2">
115
- <IconStack2
116
- size={16}
117
- className="mt-0.5 shrink-0 text-muted-foreground"
118
- />
119
- <div className="min-w-0">
115
+ const content = (
116
+ <>
117
+ {heading ? (
118
+ <div className="flex min-w-0 items-center">
120
119
  <h2 className="truncate text-sm font-semibold text-foreground">
121
- {t("dispatch.pages.otherApps", { defaultValue: "Other apps" })}
120
+ {t("dispatch.pages.otherApps", { defaultValue: heading })}
122
121
  </h2>
123
- {entries.length > 0 ? (
124
- <p className="mt-0.5 text-xs text-muted-foreground">
125
- {t("dispatch.pages.availableCount", {
126
- count: entries.length,
127
- })}
128
- </p>
129
- ) : null}
130
122
  </div>
131
- </div>
123
+ ) : null}
132
124
 
133
125
  {templatesError ? (
134
126
  <ActionQueryError
@@ -144,31 +136,37 @@ export function OtherAppsSection({
144
136
  ) : null}
145
137
 
146
138
  {isLoading && entries.length === 0 ? (
147
- <OtherAppsSkeletonList />
139
+ embeddedInList ? (
140
+ <OtherAppsSkeletonRows />
141
+ ) : (
142
+ <OtherAppsSkeletonList />
143
+ )
148
144
  ) : entries.length > 0 ? (
149
- <AppList className={APP_LIST_GRID_CLASS}>
150
- {entries.map((entry) =>
151
- entry.kind === "template" ? (
152
- <WorkspaceTemplateCard
153
- key={`template:${templateKey(entry.template)}`}
154
- template={entry.template}
155
- labels={templateLabels}
156
- catalog
157
- className={APP_LIST_GRID_ROW_CLASS}
158
- onRemixSuccess={onRemixSuccess}
159
- />
160
- ) : (
161
- <ConnectedAppCard
162
- key={`connected:${entry.app.id}`}
163
- app={entry.app}
164
- className={APP_LIST_GRID_ROW_CLASS}
165
- />
166
- ),
167
- )}
168
- </AppList>
145
+ entries.map((entry) =>
146
+ entry.kind === "template" ? (
147
+ <WorkspaceTemplateCard
148
+ key={`template:${templateKey(entry.template)}`}
149
+ template={entry.template}
150
+ labels={templateLabels}
151
+ catalog
152
+ className={APP_LIST_GRID_ROW_CLASS}
153
+ onRemixSuccess={onRemixSuccess}
154
+ />
155
+ ) : (
156
+ <ConnectedAppCard
157
+ key={`connected:${entry.app.id}`}
158
+ app={entry.app}
159
+ className={APP_LIST_GRID_ROW_CLASS}
160
+ />
161
+ ),
162
+ )
169
163
  ) : null}
170
- </section>
164
+ </>
171
165
  );
166
+
167
+ if (embeddedInList) return content;
168
+
169
+ return <section className={cn("space-y-3", className)}>{content}</section>;
172
170
  }
173
171
 
174
172
  function OtherAppsSkeletonList() {
@@ -193,3 +191,26 @@ function OtherAppsSkeletonList() {
193
191
  </AppList>
194
192
  );
195
193
  }
194
+
195
+ function OtherAppsSkeletonRows() {
196
+ return (
197
+ <>
198
+ {Array.from({ length: 4 }).map((_, index) => (
199
+ <div
200
+ key={index}
201
+ className={cn(
202
+ "flex min-w-0 items-center gap-3 border-b px-4 py-3.5 last:border-b-0",
203
+ APP_LIST_GRID_ROW_CLASS,
204
+ )}
205
+ >
206
+ <Skeleton className="size-8 shrink-0 rounded-xl" />
207
+ <div className="min-w-0 flex-1 space-y-2">
208
+ <Skeleton className="h-4 w-32" />
209
+ <Skeleton className="h-3 w-2/3" />
210
+ </div>
211
+ <Skeleton className="h-9 w-24 shrink-0 rounded-md" />
212
+ </div>
213
+ ))}
214
+ </>
215
+ );
216
+ }