@iloveagents/foundry-web-ui 0.14.2 → 0.17.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 (42) hide show
  1. package/README.md +23 -0
  2. package/dist/components/assistant-chat.js +1 -1
  3. package/dist/components/chat-message-parts.d.ts +12 -7
  4. package/dist/components/chat-message-parts.js +52 -17
  5. package/dist/components/data-table/data-table-column-header.d.ts +8 -0
  6. package/dist/components/data-table/data-table-column-header.js +12 -0
  7. package/dist/components/data-table/data-table-faceted-filter.d.ts +16 -0
  8. package/dist/components/data-table/data-table-faceted-filter.js +32 -0
  9. package/dist/components/data-table/data-table-pagination.d.ts +8 -0
  10. package/dist/components/data-table/data-table-pagination.js +15 -0
  11. package/dist/components/data-table/data-table-row-actions.d.ts +16 -0
  12. package/dist/components/data-table/data-table-row-actions.js +11 -0
  13. package/dist/components/data-table/data-table-toolbar.d.ts +27 -0
  14. package/dist/components/data-table/data-table-toolbar.js +18 -0
  15. package/dist/components/data-table/data-table-view-options.d.ts +7 -0
  16. package/dist/components/data-table/data-table-view-options.js +11 -0
  17. package/dist/components/data-table/data-table.d.ts +25 -0
  18. package/dist/components/data-table/data-table.js +65 -0
  19. package/dist/components/data-table/facets.d.ts +82 -0
  20. package/dist/components/data-table/facets.js +156 -0
  21. package/dist/components/data-table/selection-column.d.ts +4 -0
  22. package/dist/components/data-table/selection-column.js +19 -0
  23. package/dist/components/data-table/state.d.ts +41 -0
  24. package/dist/components/data-table/state.js +148 -0
  25. package/dist/components/data-table/use-data-table.d.ts +22 -0
  26. package/dist/components/data-table/use-data-table.js +102 -0
  27. package/dist/components/message-disclosure.d.ts +6 -18
  28. package/dist/components/message-disclosure.js +24 -9
  29. package/dist/components/reasoning-effort-picker.js +38 -2
  30. package/dist/components/reasoning-part.d.ts +8 -5
  31. package/dist/components/reasoning-part.js +15 -11
  32. package/dist/components/sidebar.js +22 -20
  33. package/dist/components/turn-activity.d.ts +33 -0
  34. package/dist/components/turn-activity.js +110 -0
  35. package/dist/index.d.ts +14 -2
  36. package/dist/index.js +14 -2
  37. package/dist/lib/nav-config.d.ts +16 -0
  38. package/dist/ui/dropdown-menu.d.ts +1 -0
  39. package/dist/ui/dropdown-menu.js +4 -1
  40. package/package.json +5 -4
  41. package/dist/components/tool-run-group.d.ts +0 -29
  42. package/dist/components/tool-run-group.js +0 -56
@@ -6,14 +6,17 @@
6
6
  * a curious reader, not the response. It stays visible after the run so a
7
7
  * reader can check *why* an answer came out the way it did.
8
8
  *
9
- * The row shape comes from `MessageDisclosure`, shared with the tool run
10
- * group — these two sit next to each other in a message and used to be built
11
- * separately, which is how they ended up with their carets on opposite sides.
9
+ * The row shape comes from `MessageDisclosure`, shared with the turn's
10
+ * activity row — these sit next to each other in a message and used to be
11
+ * built separately, which is how they ended up with their carets on opposite
12
+ * sides.
12
13
  */
13
- export declare function ReasoningPart({ text }: {
14
+ export declare function ReasoningPart({ text, nested }: {
14
15
  text: string;
16
+ nested?: boolean;
15
17
  }): import("react/jsx-runtime").JSX.Element | null;
16
18
  /** Adapter for assistant-ui's `Reasoning` part slot. */
17
- export declare const ReasoningMessagePartComponent: ({ text }: {
19
+ export declare const ReasoningMessagePartComponent: ({ text, nested, }: {
18
20
  text: string;
21
+ nested?: boolean;
19
22
  }) => import("react/jsx-runtime").JSX.Element;
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from "react";
3
3
  import { Brain } from "lucide-react";
4
4
  import { MessageDisclosure } from "./message-disclosure.js";
@@ -10,19 +10,23 @@ import { MessageDisclosure } from "./message-disclosure.js";
10
10
  * a curious reader, not the response. It stays visible after the run so a
11
11
  * reader can check *why* an answer came out the way it did.
12
12
  *
13
- * The row shape comes from `MessageDisclosure`, shared with the tool run
14
- * group — these two sit next to each other in a message and used to be built
15
- * separately, which is how they ended up with their carets on opposite sides.
13
+ * The row shape comes from `MessageDisclosure`, shared with the turn's
14
+ * activity row — these sit next to each other in a message and used to be
15
+ * built separately, which is how they ended up with their carets on opposite
16
+ * sides.
16
17
  */
17
- export function ReasoningPart({ text }) {
18
+ export function ReasoningPart({ text, nested = false }) {
18
19
  const [isOpen, setIsOpen] = useState(false);
19
20
  if (!text.trim())
20
21
  return null;
21
- return (_jsx(MessageDisclosure, { icon: _jsx(Brain, { className: "size-3.5" }), label: "Reasoning", open: isOpen, onOpenChange: setIsOpen,
22
- // Under the work, above the conclusion: the model usually emits its
23
- // thinking first, but a reader wants "what it did", then "why", and
24
- // the answer last — the message should end on its point.
25
- placement: "after-tools", ariaLabel: isOpen ? "Hide reasoning" : "Show reasoning", children: _jsx("div", { className: "whitespace-pre-wrap text-xs text-muted-foreground", children: text }) }));
22
+ // Inside the turn's activity fold there is no second fold: the reader
23
+ // already opened one to get here, and a disclosure containing disclosures
24
+ // is a maze. Just the thinking, captioned, in the slot the model produced
25
+ // it in.
26
+ if (nested) {
27
+ return (_jsxs("div", { className: "flex flex-col gap-1", "data-reasoning-part": true, children: [_jsxs("span", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [_jsx(Brain, { className: "size-3.5" }), "Reasoning"] }), _jsx("div", { className: "whitespace-pre-wrap text-xs text-muted-foreground", children: text })] }));
28
+ }
29
+ return (_jsx(MessageDisclosure, { icon: _jsx(Brain, { className: "size-3.5" }), label: "Reasoning", open: isOpen, onOpenChange: setIsOpen, ariaLabel: isOpen ? "Hide reasoning" : "Show reasoning", children: _jsx("div", { className: "whitespace-pre-wrap text-xs text-muted-foreground", children: text }) }));
26
30
  }
27
31
  /** Adapter for assistant-ui's `Reasoning` part slot. */
28
- export const ReasoningMessagePartComponent = ({ text }) => (_jsx(ReasoningPart, { text: text }));
32
+ export const ReasoningMessagePartComponent = ({ text, nested = false, }) => _jsx(ReasoningPart, { text: text, nested: nested });
@@ -1,17 +1,17 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
3
3
  import { NavLink, useLocation, useNavigate } from "react-router";
4
- import { Ellipsis, PanelLeft, PanelLeftOpen, SquarePen, ChevronDown, ChevronRight, GripVertical, Plus, Upload } from "lucide-react";
4
+ import { Ellipsis, PanelLeft, PanelLeftOpen, SquarePen, ChevronDown, ChevronRight, GripVertical, Plus, Upload, } from "lucide-react";
5
5
  import { cn } from "@iloveagents/foundry-web-primitives";
6
6
  import { useSidebarStore, RAIL_WIDTH, SIDEBAR_MIN_WIDTH, SIDEBAR_MAX_WIDTH, } from "../lib/sidebar-store.js";
7
7
  import { useToolPanelStore } from "../lib/tool-panel-store.js";
8
8
  import { useNewConversation } from "../lib/use-new-conversation.js";
9
9
  import { useNavStore } from "../lib/nav-store.js";
10
10
  import { useAppStore } from "../lib/app-store.js";
11
- import { getContainerDropZoneLabel, hasAnyDropTarget, hasExternalFilesData } from "../lib/nav-dnd.js";
11
+ import { getContainerDropZoneLabel, hasAnyDropTarget, hasExternalFilesData, } from "../lib/nav-dnd.js";
12
12
  import { TooltipIconButton } from "./tooltip-icon-button.js";
13
13
  import { Button } from "@iloveagents/foundry-web-primitives";
14
- import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "../ui/dropdown-menu.js";
14
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "../ui/dropdown-menu.js";
15
15
  import { AppBrand } from "./app-brand.js";
16
16
  const ACTIVE_NAV_ROW_CLASS = "border border-transparent bg-sidebar-selected shadow-none";
17
17
  const ACTIVE_NAV_TEXT_CLASS = "font-semibold text-sidebar-foreground";
@@ -127,10 +127,7 @@ function useActiveNavRowScroll(isActive) {
127
127
  return;
128
128
  const rect = element.getBoundingClientRect();
129
129
  const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
130
- const isOnScreen = rect.width > 0 &&
131
- rect.height > 0 &&
132
- rect.right > 0 &&
133
- rect.left < viewportWidth;
130
+ const isOnScreen = rect.width > 0 && rect.height > 0 && rect.right > 0 && rect.left < viewportWidth;
134
131
  if (!isOnScreen)
135
132
  return;
136
133
  const nav = element.closest("nav");
@@ -427,10 +424,10 @@ function ContainerDropZone({ dnd }) {
427
424
  * row underneath; the branch chevron keeps its own, since positioned
428
425
  * elements paint above the static button and win the hit test.
429
426
  */
430
- function NavTwisty({ hasChildren, isExpanded, onToggle, label, isActive, depth, inTree = true, }) {
427
+ function NavTwisty({ hasChildren, childrenUnloaded = false, isExpanded, onToggle, label, isActive, depth, inTree = true, }) {
431
428
  const cell = cn(NAV_TWISTY_CLASS, getNavDepthOffsets(depth).column);
432
429
  if (!hasChildren) {
433
- return (_jsx("span", { "data-nav-twisty-cell": true, "aria-hidden": "true", className: cn(cell, "pointer-events-none"), children: inTree && (_jsx("span", { className: cn("size-1 rounded-full", isActive ? "bg-primary/70" : "bg-sidebar-foreground/30") })) }));
430
+ return (_jsx("span", { "data-nav-twisty-cell": true, "aria-hidden": "true", className: cn(cell, "pointer-events-none"), children: inTree && !childrenUnloaded && (_jsx("span", { "data-nav-leaf-dot": true, className: cn("size-1 rounded-full", isActive ? "bg-primary/70" : "bg-sidebar-foreground/30") })) }));
434
431
  }
435
432
  return (_jsx("span", { "data-nav-twisty-cell": true, className: cell, children: _jsx("button", { type: "button", "data-nav-twisty": true, "aria-expanded": isExpanded, "aria-label": isExpanded ? `Collapse ${label}` : `Expand ${label}`, onClick: (e) => {
436
433
  e.preventDefault();
@@ -480,11 +477,13 @@ function NavLeafItem({ item, isActive, onClick, depth = 0, }) {
480
477
  !isDisabled &&
481
478
  "ring-2 ring-inset ring-sidebar-ring/50 bg-sidebar-selected", isFileDragOver &&
482
479
  !isDisabled &&
483
- "outline-dashed outline-2 -outline-offset-2 outline-primary bg-primary/10", item.dimmed && !isDisabled && "opacity-50"), children: [_jsx(NavTwisty, { hasChildren: false, label: item.label, isActive: isActive && !isDisabled, depth: depth }), _jsxs("button", { type: "button", disabled: isDisabled, onClick: isDisabled ? undefined : onClick, className: cn("flex items-center gap-2 flex-1 min-w-0 py-1.5 text-sm text-left", bodyPl, "pr-3 transition-[padding] duration-150", NAV_ROW_ACTIONS_CLEARANCE, isDisabled
480
+ "outline-dashed outline-2 -outline-offset-2 outline-primary bg-primary/10", item.dimmed && !isDisabled && "opacity-50"), children: [_jsx(NavTwisty, { hasChildren: false, childrenUnloaded: item.childrenUnloaded, label: item.label, isActive: isActive && !isDisabled, depth: depth }), _jsxs("button", { type: "button", disabled: isDisabled, onClick: isDisabled ? undefined : onClick, className: cn("flex items-center gap-2 flex-1 min-w-0 py-1.5 text-sm text-left", bodyPl, "pr-3 transition-[padding] duration-150", NAV_ROW_ACTIONS_CLEARANCE, isDisabled
484
481
  ? "text-sidebar-foreground/45 cursor-default"
485
482
  : isActive
486
483
  ? ACTIVE_NAV_TEXT_CLASS
487
- : "text-sidebar-foreground/80"), children: [_jsx(item.icon, { className: cn("size-3.5 shrink-0", isActive && !isDisabled ? ACTIVE_NAV_ICON_CLASS : "text-sidebar-foreground/60") }), _jsx("span", { className: "truncate", children: item.label }), item.badge && (_jsx("span", { className: cn("shrink-0 rounded px-1 py-0.5 text-[10px] font-medium", isActive ? ACTIVE_NAV_BADGE_CLASS : "bg-sidebar-accent text-sidebar-accent-foreground"), children: item.badge })), _jsx(NavStatusIcon, { statusIcon: item.statusIcon }), _jsx(NavStatusDot, { statusDot: item.statusDot })] }), isFileDragOver && !isDisabled && (_jsx(Upload, { "aria-hidden": "true", className: "size-3.5 mr-2 shrink-0 text-primary pointer-events-none" })), _jsx(NavRowControls, { item: item })] }));
484
+ : "text-sidebar-foreground/80"), children: [_jsx(item.icon, { className: cn("size-3.5 shrink-0", isActive && !isDisabled ? ACTIVE_NAV_ICON_CLASS : "text-sidebar-foreground/60") }), _jsx("span", { className: "truncate", children: item.label }), item.badge && (_jsx("span", { className: cn("shrink-0 rounded px-1 py-0.5 text-[10px] font-medium", isActive
485
+ ? ACTIVE_NAV_BADGE_CLASS
486
+ : "bg-sidebar-accent text-sidebar-accent-foreground"), children: item.badge })), _jsx(NavStatusIcon, { statusIcon: item.statusIcon }), _jsx(NavStatusDot, { statusDot: item.statusDot })] }), isFileDragOver && !isDisabled && (_jsx(Upload, { "aria-hidden": "true", className: "size-3.5 mr-2 shrink-0 text-primary pointer-events-none" })), _jsx(NavRowControls, { item: item })] }));
488
487
  }
489
488
  return (_jsxs("button", { ref: activeButtonRef, "data-active-nav-row": isActive ? "true" : undefined, "data-nav-depth": depth, type: "button", disabled: isDisabled, onClick: isDisabled ? undefined : onClick, ...(isDisabled ? {} : draggableProps), ...(isDisabled ? {} : dropTargetProps), className: cn("relative flex items-center gap-2 w-full", "rounded-lg border border-transparent pr-3 py-1.5 text-sm text-left", bodyPl, isDisabled
490
489
  ? "opacity-40 cursor-default"
@@ -493,7 +492,7 @@ function NavLeafItem({ item, isActive, onClick, depth = 0, }) {
493
492
  !isDisabled &&
494
493
  "ring-2 ring-inset ring-sidebar-ring/50 bg-sidebar-selected", isFileDragOver &&
495
494
  !isDisabled &&
496
- "outline-dashed outline-2 -outline-offset-2 outline-primary bg-primary/10", item.dimmed && !isDisabled && "opacity-50"), children: [_jsx(NavTwisty, { hasChildren: false, label: item.label, isActive: isActive && !isDisabled, depth: depth }), _jsx(item.icon, { className: cn("size-3.5 shrink-0", isActive && !isDisabled ? ACTIVE_NAV_ICON_CLASS : "text-sidebar-foreground/60") }), _jsx("span", { className: cn("truncate", isActive && !isDisabled && ACTIVE_NAV_TEXT_CLASS), children: item.label }), item.badge && (_jsx("span", { className: cn("shrink-0 rounded px-1 py-0.5 text-[10px] font-medium", isActive ? ACTIVE_NAV_BADGE_CLASS : "bg-sidebar-accent text-sidebar-accent-foreground"), children: item.badge })), _jsx(NavStatusIcon, { statusIcon: item.statusIcon }), _jsx(NavStatusDot, { statusDot: item.statusDot }), isFileDragOver && !isDisabled && (_jsx(Upload, { "aria-hidden": "true", className: "ml-auto size-3.5 shrink-0 text-primary pointer-events-none" }))] }));
495
+ "outline-dashed outline-2 -outline-offset-2 outline-primary bg-primary/10", item.dimmed && !isDisabled && "opacity-50"), children: [_jsx(NavTwisty, { hasChildren: false, childrenUnloaded: item.childrenUnloaded, label: item.label, isActive: isActive && !isDisabled, depth: depth }), _jsx(item.icon, { className: cn("size-3.5 shrink-0", isActive && !isDisabled ? ACTIVE_NAV_ICON_CLASS : "text-sidebar-foreground/60") }), _jsx("span", { className: cn("truncate", isActive && !isDisabled && ACTIVE_NAV_TEXT_CLASS), children: item.label }), item.badge && (_jsx("span", { className: cn("shrink-0 rounded px-1 py-0.5 text-[10px] font-medium", isActive ? ACTIVE_NAV_BADGE_CLASS : "bg-sidebar-accent text-sidebar-accent-foreground"), children: item.badge })), _jsx(NavStatusIcon, { statusIcon: item.statusIcon }), _jsx(NavStatusDot, { statusDot: item.statusDot }), isFileDragOver && !isDisabled && (_jsx(Upload, { "aria-hidden": "true", className: "ml-auto size-3.5 shrink-0 text-primary pointer-events-none" }))] }));
497
496
  }
498
497
  function SubNavItems({ items, parentPath, depth = 1, }) {
499
498
  const navigate = useNavigate();
@@ -587,7 +586,9 @@ function NestedFolderItem({ item, depth }) {
587
586
  ? ACTIVE_NAV_ICON_CLASS
588
587
  : isAncestorActive
589
588
  ? "text-sidebar-foreground/80"
590
- : "text-sidebar-foreground/60") }), _jsx("span", { className: "truncate", children: item.label }), item.badge && (_jsx("span", { className: cn("shrink-0 rounded px-1 py-0.5 text-[10px] font-medium", isActive ? ACTIVE_NAV_BADGE_CLASS : "bg-sidebar-accent text-sidebar-accent-foreground"), children: item.badge })), _jsx(NavStatusIcon, { statusIcon: item.statusIcon }), _jsx(NavStatusDot, { statusDot: item.statusDot })] }), isFileDragOver && !isDisabled && (_jsx(Upload, { "aria-hidden": "true", className: "size-3.5 mr-2 shrink-0 text-primary pointer-events-none" })), _jsx(NavRowControls, { item: item })] }), isExpanded && item.children && (_jsxs("div", { children: [item.children.map((child) => {
589
+ : "text-sidebar-foreground/60") }), _jsx("span", { className: "truncate", children: item.label }), item.badge && (_jsx("span", { className: cn("shrink-0 rounded px-1 py-0.5 text-[10px] font-medium", isActive
590
+ ? ACTIVE_NAV_BADGE_CLASS
591
+ : "bg-sidebar-accent text-sidebar-accent-foreground"), children: item.badge })), _jsx(NavStatusIcon, { statusIcon: item.statusIcon }), _jsx(NavStatusDot, { statusDot: item.statusDot })] }), isFileDragOver && !isDisabled && (_jsx(Upload, { "aria-hidden": "true", className: "size-3.5 mr-2 shrink-0 text-primary pointer-events-none" })), _jsx(NavRowControls, { item: item })] }), isExpanded && item.children && (_jsxs("div", { children: [item.children.map((child) => {
591
592
  const hasNestedChildren = child.children && child.children.length > 0;
592
593
  if (hasNestedChildren) {
593
594
  return (_jsx(NestedFolderItem, { item: child, depth: depth + 1 }, child.to + child.label));
@@ -634,7 +635,7 @@ function CollapsibleNavItem({ item, inTree = true }) {
634
635
  !isDisabled &&
635
636
  "ring-2 ring-inset ring-sidebar-ring/50 bg-sidebar-selected", isFileDragOver &&
636
637
  !isDisabled &&
637
- "outline-dashed outline-2 -outline-offset-2 outline-primary bg-primary/10", item.dimmed && !isDisabled && "opacity-50"), children: [_jsx(NavTwisty, { hasChildren: hasChildren, inTree: inTree, isExpanded: isExpanded, onToggle: hasChildren ? () => setManualOverride(!isExpanded) : undefined, label: item.label, isActive: (selected || ancestor) && !isDisabled, depth: 0 }), _jsxs("button", { type: "button", onClick: () => {
638
+ "outline-dashed outline-2 -outline-offset-2 outline-primary bg-primary/10", item.dimmed && !isDisabled && "opacity-50"), children: [_jsx(NavTwisty, { hasChildren: hasChildren, childrenUnloaded: item.childrenUnloaded, inTree: inTree, isExpanded: isExpanded, onToggle: hasChildren ? () => setManualOverride(!isExpanded) : undefined, label: item.label, isActive: (selected || ancestor) && !isDisabled, depth: 0 }), _jsxs("button", { type: "button", onClick: () => {
638
639
  if (isDisabled)
639
640
  return;
640
641
  setNavContext({
@@ -659,13 +660,15 @@ function CollapsibleNavItem({ item, inTree = true }) {
659
660
  ? ACTIVE_NAV_ICON_CLASS
660
661
  : ancestor
661
662
  ? "text-sidebar-foreground/80"
662
- : "text-sidebar-foreground/60") }), _jsx("span", { className: "truncate", children: item.label }), item.badge && (_jsx("span", { className: cn("shrink-0 rounded px-1 py-0.5 text-[10px] font-medium", selected ? ACTIVE_NAV_BADGE_CLASS : "bg-sidebar-accent text-sidebar-accent-foreground"), children: item.badge })), _jsx(NavStatusIcon, { statusIcon: item.statusIcon }), _jsx(NavStatusDot, { statusDot: item.statusDot })] }), isFileDragOver && !isDisabled && (_jsx(Upload, { "aria-hidden": "true", className: "size-3.5 mr-2 shrink-0 text-primary pointer-events-none" })), _jsx(NavRowControls, { item: item })] }));
663
+ : "text-sidebar-foreground/60") }), _jsx("span", { className: "truncate", children: item.label }), item.badge && (_jsx("span", { className: cn("shrink-0 rounded px-1 py-0.5 text-[10px] font-medium", selected
664
+ ? ACTIVE_NAV_BADGE_CLASS
665
+ : "bg-sidebar-accent text-sidebar-accent-foreground"), children: item.badge })), _jsx(NavStatusIcon, { statusIcon: item.statusIcon }), _jsx(NavStatusDot, { statusDot: item.statusDot })] }), isFileDragOver && !isDisabled && (_jsx(Upload, { "aria-hidden": "true", className: "size-3.5 mr-2 shrink-0 text-primary pointer-events-none" })), _jsx(NavRowControls, { item: item })] }));
663
666
  return (_jsxs("div", { ...(isDisabled ? {} : dropTargetProps), ...(isDisabled ? {} : draggableProps), children: [inner(isActive, isAncestorActive), hasChildren && isExpanded && (_jsxs(_Fragment, { children: [_jsx(SubNavItems, { items: item.children, parentPath: item.to }), item.dnd?.canDrop() && _jsx(ContainerDropZone, { dnd: item.dnd })] }))] }));
664
667
  }
665
668
  if (isDisabled) {
666
669
  return (_jsxs("div", { "data-nav-depth": 0, className: cn("relative flex items-center gap-2 w-full opacity-40", inTree ? getNavDepthOffsets(0).body : NAV_FLAT_BODY_CLASS,
667
670
  // Transparent border for box-model parity — see the NavLink variant.
668
- "rounded-lg border border-transparent pr-3 py-2 text-left text-sm text-sidebar-foreground/45"), children: [_jsx(NavTwisty, { hasChildren: false, inTree: inTree, label: item.label, depth: 0 }), _jsx(item.icon, { className: "size-4 shrink-0 text-sidebar-foreground/45" }), _jsx("span", { className: "truncate", children: item.label })] }));
671
+ "rounded-lg border border-transparent pr-3 py-2 text-left text-sm text-sidebar-foreground/45"), children: [_jsx(NavTwisty, { hasChildren: false, childrenUnloaded: item.childrenUnloaded, inTree: inTree, label: item.label, depth: 0 }), _jsx(item.icon, { className: "size-4 shrink-0 text-sidebar-foreground/45" }), _jsx("span", { className: "truncate", children: item.label })] }));
669
672
  }
670
673
  return (_jsx(NavLink, { ref: activeLinkRef, "data-active-nav-row": currentPath === item.to ? "true" : undefined, "data-nav-depth": 0, to: item.to, ...draggableProps, ...dropTargetProps, className: ({ isActive }) => cn("relative flex items-center gap-2 w-full", inTree ? getNavDepthOffsets(0).body : NAV_FLAT_BODY_CLASS,
671
674
  // The transparent border is NOT decoration: it is what makes this
@@ -675,7 +678,8 @@ function CollapsibleNavItem({ item, inTree = true }) {
675
678
  // siblings' and the column goes ragged.
676
679
  "rounded-xl border border-transparent pr-3 py-2 text-sm text-left", "text-sidebar-foreground/85 transition-colors hover:bg-sidebar-accent/70", isActive && cn(ACTIVE_NAV_ROW_CLASS, ACTIVE_NAV_TEXT_CLASS), isDragOver &&
677
680
  !isFileDragOver &&
678
- "bg-sidebar-selected ring-2 ring-inset ring-sidebar-ring/50", isFileDragOver && "outline-dashed outline-2 -outline-offset-2 outline-primary bg-primary/10", item.dimmed && "opacity-50"), children: ({ isActive }) => (_jsxs(_Fragment, { children: [_jsx(NavTwisty, { hasChildren: false, inTree: inTree, label: item.label, isActive: isActive, depth: 0 }), _jsx(item.icon, { className: cn("size-4 shrink-0", isActive ? ACTIVE_NAV_ICON_CLASS : "text-sidebar-foreground/60") }), _jsx("span", { className: "truncate", children: item.label }), item.badge && (_jsx("span", { className: cn("shrink-0 rounded px-1 py-0.5 text-[10px] font-medium", isActive
681
+ "bg-sidebar-selected ring-2 ring-inset ring-sidebar-ring/50", isFileDragOver &&
682
+ "outline-dashed outline-2 -outline-offset-2 outline-primary bg-primary/10", item.dimmed && "opacity-50"), children: ({ isActive }) => (_jsxs(_Fragment, { children: [_jsx(NavTwisty, { hasChildren: false, childrenUnloaded: item.childrenUnloaded, inTree: inTree, label: item.label, isActive: isActive, depth: 0 }), _jsx(item.icon, { className: cn("size-4 shrink-0", isActive ? ACTIVE_NAV_ICON_CLASS : "text-sidebar-foreground/60") }), _jsx("span", { className: "truncate", children: item.label }), item.badge && (_jsx("span", { className: cn("shrink-0 rounded px-1 py-0.5 text-[10px] font-medium", isActive
679
683
  ? ACTIVE_NAV_BADGE_CLASS
680
684
  : "bg-sidebar-accent text-sidebar-accent-foreground"), children: item.badge })), _jsx(NavStatusIcon, { statusIcon: item.statusIcon }), _jsx(NavStatusDot, { statusDot: item.statusDot }), isFileDragOver && (_jsx(Upload, { "aria-hidden": "true", className: "ml-auto size-3.5 shrink-0 text-primary pointer-events-none" }))] })) }));
681
685
  }
@@ -802,9 +806,7 @@ function useGroupCollapsed(label, defaultCollapsed) {
802
806
  // that happens to collide with a prototype key (e.g. ``toString``,
803
807
  // ``__proto__``, ``constructor``) is not treated as a persisted
804
808
  // entry. ``map`` is parsed JSON so its prototype is ``Object``'s.
805
- return Object.prototype.hasOwnProperty.call(map, label)
806
- ? map[label]
807
- : Boolean(defaultCollapsed);
809
+ return Object.prototype.hasOwnProperty.call(map, label) ? map[label] : Boolean(defaultCollapsed);
808
810
  }
809
811
  function toggleGroupCollapsed(label, current) {
810
812
  writeCollapseMap({ ...readCollapseMap(), [label]: !current });
@@ -0,0 +1,33 @@
1
+ import type { ReactNode } from "react";
2
+ /**
3
+ * ONE row for everything a turn did before it answered.
4
+ *
5
+ * A turn that searched a corpus eleven times used to render eleven summary
6
+ * rows — one per adjacent run of tool calls — plus a reasoning row of its
7
+ * own, and the answer somewhere underneath. Each row was individually
8
+ * reasonable; stacked, they were a ladder the reader had to climb past to
9
+ * reach the point.
10
+ *
11
+ * So the machinery folds: "Worked · 20 tools · reasoning". The model's PROSE
12
+ * never goes inside — it is what the reader came for and it stays in the
13
+ * open, which also means each stretch of it ends one of these rows and
14
+ * starts the next. A turn that narrates its steps gets one row per step
15
+ * rather than one per tool run; a turn that just works and answers gets
16
+ * exactly one.
17
+ *
18
+ * Order is CHRONOLOGICAL, inside the fold and outside it. Reasoning explains
19
+ * why the next call happened ("I need the termination clause from both
20
+ * agreements" → three searches); moving it away from the calls it explains
21
+ * turns a narrative into a pile. Outside, a multi-step turn reads as work,
22
+ * what it learned, work, what it learned, answer — the model's prose ends
23
+ * one of these rows and starts the next.
24
+ *
25
+ * Collapsed by default, for the reason the per-run fold this replaces had
26
+ * already found: in a real transcript the machinery dominates the answer it
27
+ * was meant to support, and the reader scrolls past it to reach the point.
28
+ */
29
+ export declare function TurnActivity({ indices, isRunning, children, }: {
30
+ indices: readonly number[];
31
+ isRunning: boolean;
32
+ children: ReactNode;
33
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,110 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { useAuiState } from "@assistant-ui/react";
4
+ import { Loader2, CheckCircle2, XCircle, AlertCircle } from "lucide-react";
5
+ import { MessageDisclosure } from "./message-disclosure.js";
6
+ import { _detectErrorResult } from "./tool-fallback.js";
7
+ /**
8
+ * ONE row for everything a turn did before it answered.
9
+ *
10
+ * A turn that searched a corpus eleven times used to render eleven summary
11
+ * rows — one per adjacent run of tool calls — plus a reasoning row of its
12
+ * own, and the answer somewhere underneath. Each row was individually
13
+ * reasonable; stacked, they were a ladder the reader had to climb past to
14
+ * reach the point.
15
+ *
16
+ * So the machinery folds: "Worked · 20 tools · reasoning". The model's PROSE
17
+ * never goes inside — it is what the reader came for and it stays in the
18
+ * open, which also means each stretch of it ends one of these rows and
19
+ * starts the next. A turn that narrates its steps gets one row per step
20
+ * rather than one per tool run; a turn that just works and answers gets
21
+ * exactly one.
22
+ *
23
+ * Order is CHRONOLOGICAL, inside the fold and outside it. Reasoning explains
24
+ * why the next call happened ("I need the termination clause from both
25
+ * agreements" → three searches); moving it away from the calls it explains
26
+ * turns a narrative into a pile. Outside, a multi-step turn reads as work,
27
+ * what it learned, work, what it learned, answer — the model's prose ends
28
+ * one of these rows and starts the next.
29
+ *
30
+ * Collapsed by default, for the reason the per-run fold this replaces had
31
+ * already found: in a real transcript the machinery dominates the answer it
32
+ * was meant to support, and the reader scrolls past it to reach the point.
33
+ */
34
+ export function TurnActivity({ indices, isRunning, children, }) {
35
+ const { tools, failed, hasReasoning } = useTurnOutcome(indices);
36
+ const [isOpen, setIsOpen] = useState(false);
37
+ const summary = summarise({ tools, failed, hasReasoning, isRunning });
38
+ // Red only when EVERY call failed AND the turn has SETTLED. One failure
39
+ // among twenty is a turn that recovered, and painting it red would report a
40
+ // broken answer where there is a working one — but so would painting it red
41
+ // mid-run, when the only calls so far happen to have failed and the model is
42
+ // still reasoning its way to one that works. A partial failure still says
43
+ // so, in the count, without the alarm.
44
+ const allFailed = !isRunning && tools > 0 && failed === tools;
45
+ return (_jsx(MessageDisclosure, { open: isOpen, onOpenChange: setIsOpen, tone: allFailed ? "destructive" : "muted", icon: isRunning ? (_jsx(Loader2, { className: "size-3.5 animate-spin text-primary" })) : allFailed ? (_jsx(XCircle, { className: "size-3.5 text-destructive" })) : failed > 0 ? (_jsx(AlertCircle, { className: "size-3.5 text-muted-foreground" })) : (_jsx(CheckCircle2, { className: "size-3.5 text-primary" })), label: summary,
46
+ // The summary goes in the accessible name, not just the visible one.
47
+ // An `aria-label` REPLACES the name a button would otherwise derive
48
+ // from its contents, so a bare "Show what this turn did" would have
49
+ // announced the row while hiding the very thing it summarises — the
50
+ // call count, whether anything failed, whether there is thinking to
51
+ // read. A screen-reader user got strictly less than a sighted one.
52
+ ariaLabel: `${summary} — ${isOpen ? "hide" : "show"} details`, "data-turn-activity": true, children: _jsx("div", { className: "flex flex-col gap-2", children: children }) }));
53
+ }
54
+ /**
55
+ * The row's one line of text.
56
+ *
57
+ * Says what happened, in the order a reader asks it: is it still going, how
58
+ * much work was there, did any of it fail, and was there thinking to read.
59
+ * "Worked" alone when a turn only thought — claiming tools it never called
60
+ * would be a lie in the summary line, which is the one part nobody expands
61
+ * to check.
62
+ *
63
+ * This row describes its CONTENTS; it does not narrate the run. The phase a
64
+ * run is in belongs to the status indicator above the composer, which knows
65
+ * things this row cannot — which tool is in flight, whether the stream has
66
+ * stalled. A turn with reasoning and no calls yet therefore says "Reasoning"
67
+ * rather than "Thinking…", which is what the indicator is already saying two
68
+ * lines below; the same word twice on one screen reads as a stutter, not as
69
+ * two facts.
70
+ *
71
+ * No ellipses either: the spinner in the row's own icon slot carries "still
72
+ * going", and it does it without competing for the label.
73
+ */
74
+ function summarise({ tools, failed, hasReasoning, isRunning, }) {
75
+ const toolText = tools === 1 ? "1 tool" : `${tools} tools`;
76
+ // 0 tools while running means the group holds nothing but reasoning —
77
+ // `useTurnOutcome` counts every non-reasoning part as a call — so naming it
78
+ // is accurate, not a guess.
79
+ if (isRunning)
80
+ return tools > 0 ? `Working · ${toolText}` : "Reasoning";
81
+ const parts = ["Worked"];
82
+ if (tools > 0)
83
+ parts.push(failed > 0 ? `${toolText} · ${failed} failed` : toolText);
84
+ if (hasReasoning)
85
+ parts.push("reasoning");
86
+ return parts.join(" · ");
87
+ }
88
+ /** What the turn actually contains, read from the message's own parts. */
89
+ function useTurnOutcome(indices) {
90
+ const parts = useAuiState((s) => s.message.parts);
91
+ let tools = 0;
92
+ let failed = 0;
93
+ let hasReasoning = false;
94
+ for (const index of indices) {
95
+ const part = parts?.[index];
96
+ if (!part)
97
+ continue;
98
+ if (part.type === "reasoning") {
99
+ hasReasoning = true;
100
+ continue;
101
+ }
102
+ // Anything else under this node is a call the turn made. Counted by
103
+ // exclusion rather than by an allow-list of type names, so a new tool
104
+ // part type shows up in the count instead of vanishing from it.
105
+ tools++;
106
+ if (part.status?.type === "incomplete" || _detectErrorResult(part.result))
107
+ failed++;
108
+ }
109
+ return { tools, failed, hasReasoning };
110
+ }
package/dist/index.d.ts CHANGED
@@ -33,6 +33,18 @@ export { CollectionEmptyState } from "./components/collection-empty-state.js";
33
33
  export { CollectionSkeleton } from "./components/collection-skeleton.js";
34
34
  export { InfiniteScrollSentinel } from "./components/infinite-scroll-sentinel.js";
35
35
  export { CollectionSurface } from "./components/collection-surface.js";
36
+ export { DataTable, type DataTableProps } from "./components/data-table/data-table.js";
37
+ export { DataTableToolbar, type DataTableFacet, type DataTableToolbarProps, } from "./components/data-table/data-table-toolbar.js";
38
+ export { DataTableFacetedFilter, type DataTableFacetedFilterProps, } from "./components/data-table/data-table-faceted-filter.js";
39
+ export { DataTableViewOptions, type DataTableViewOptionsProps, } from "./components/data-table/data-table-view-options.js";
40
+ export { DataTableColumnHeader, type DataTableColumnHeaderProps, } from "./components/data-table/data-table-column-header.js";
41
+ export { DataTablePagination, type DataTablePaginationProps, } from "./components/data-table/data-table-pagination.js";
42
+ export { DataTableRowActions, type DataTableRowAction, type DataTableRowActionsProps, } from "./components/data-table/data-table-row-actions.js";
43
+ export { selectionColumn, SELECTION_COLUMN_ID } from "./components/data-table/selection-column.js";
44
+ export { useDataTable, type UseDataTableOptions } from "./components/data-table/use-data-table.js";
45
+ export { DEFAULT_PAGE_SIZE, EMPTY_DATA_TABLE_STATE, isDataTableUrlKey, mergeDataTableUrlState, parseDataTableState, serializeDataTableState, type DataTableState, type DataTableUrlOptions, } from "./components/data-table/state.js";
46
+ export { FACET_EMPTY, cellText, columnLabel, facetCounts, facetFilterFn, facetKeys, facetOptions, normalizeFacetValue, rowSearchText, tokenSearchFilterFn, type DataTableFacetCount, type DataTableFacetOption, } from "./components/data-table/facets.js";
47
+ export { createColumnHelper, flexRender, type Column, type ColumnDef, type ColumnFiltersState, type Row, type RowSelectionState, type SortingState, type Table, type VisibilityState, } from "@tanstack/react-table";
36
48
  export { ThemeRuntimeProvider, ThemeScope, ThemeDocumentMetadata, useThemeRuntime, } from "./components/theme-runtime-provider.js";
37
49
  export { JsonViewer } from "./components/json-viewer.js";
38
50
  export { UserMenu } from "./components/user-menu.js";
@@ -42,7 +54,7 @@ export { NameDialog } from "./components/name-dialog.js";
42
54
  export { SurfaceCard, surfaceCardVariants } from "./components/surface-card.js";
43
55
  export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "./ui/tooltip.js";
44
56
  export { Collapsible, CollapsibleTrigger, CollapsibleContent } from "./ui/collapsible.js";
45
- export { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuLabel, } from "./ui/dropdown-menu.js";
57
+ export { DropdownMenu, DropdownMenuTrigger, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuLabel, } from "./ui/dropdown-menu.js";
46
58
  export { Dialog, DialogPortal, DialogOverlay, DialogTrigger, DialogClose, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription, } from "./ui/dialog.js";
47
59
  export { Popover, PopoverTrigger, PopoverAnchor, PopoverContent } from "./ui/popover.js";
48
60
  export { Select, selectClassName } from "./ui/select.js";
@@ -66,7 +78,7 @@ export { usePageTools } from "./lib/use-page-tools.js";
66
78
  export { useNewConversation } from "./lib/use-new-conversation.js";
67
79
  export { AGUIAdapterSDK } from "./lib/ag-ui-adapter.js";
68
80
  export { FileAttachmentAdapter, resolveMimeType } from "./lib/attachment-adapter.js";
69
- export { registerAttachmentAdapter, useAttachmentAdapterStore, } from "./lib/attachment-registry.js";
81
+ export { registerAttachmentAdapter, useAttachmentAdapterStore } from "./lib/attachment-registry.js";
70
82
  export { ReasoningPart, ReasoningMessagePartComponent } from "./components/reasoning-part.js";
71
83
  export { ReasoningEffortPicker } from "./components/reasoning-effort-picker.js";
72
84
  export { AuthProvider } from "./lib/auth-provider.js";
package/dist/index.js CHANGED
@@ -31,6 +31,18 @@ export { CollectionEmptyState } from "./components/collection-empty-state.js";
31
31
  export { CollectionSkeleton } from "./components/collection-skeleton.js";
32
32
  export { InfiniteScrollSentinel } from "./components/infinite-scroll-sentinel.js";
33
33
  export { CollectionSurface } from "./components/collection-surface.js";
34
+ export { DataTable } from "./components/data-table/data-table.js";
35
+ export { DataTableToolbar, } from "./components/data-table/data-table-toolbar.js";
36
+ export { DataTableFacetedFilter, } from "./components/data-table/data-table-faceted-filter.js";
37
+ export { DataTableViewOptions, } from "./components/data-table/data-table-view-options.js";
38
+ export { DataTableColumnHeader, } from "./components/data-table/data-table-column-header.js";
39
+ export { DataTablePagination, } from "./components/data-table/data-table-pagination.js";
40
+ export { DataTableRowActions, } from "./components/data-table/data-table-row-actions.js";
41
+ export { selectionColumn, SELECTION_COLUMN_ID } from "./components/data-table/selection-column.js";
42
+ export { useDataTable } from "./components/data-table/use-data-table.js";
43
+ export { DEFAULT_PAGE_SIZE, EMPTY_DATA_TABLE_STATE, isDataTableUrlKey, mergeDataTableUrlState, parseDataTableState, serializeDataTableState, } from "./components/data-table/state.js";
44
+ export { FACET_EMPTY, cellText, columnLabel, facetCounts, facetFilterFn, facetKeys, facetOptions, normalizeFacetValue, rowSearchText, tokenSearchFilterFn, } from "./components/data-table/facets.js";
45
+ export { createColumnHelper, flexRender, } from "@tanstack/react-table";
34
46
  export { ThemeRuntimeProvider, ThemeScope, ThemeDocumentMetadata, useThemeRuntime, } from "./components/theme-runtime-provider.js";
35
47
  export { JsonViewer } from "./components/json-viewer.js";
36
48
  export { UserMenu } from "./components/user-menu.js";
@@ -46,7 +58,7 @@ export { SurfaceCard, surfaceCardVariants } from "./components/surface-card.js";
46
58
  // package. Tracked for a follow-up under #95/#96.
47
59
  export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "./ui/tooltip.js";
48
60
  export { Collapsible, CollapsibleTrigger, CollapsibleContent } from "./ui/collapsible.js";
49
- export { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuLabel, } from "./ui/dropdown-menu.js";
61
+ export { DropdownMenu, DropdownMenuTrigger, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuLabel, } from "./ui/dropdown-menu.js";
50
62
  export { Dialog, DialogPortal, DialogOverlay, DialogTrigger, DialogClose, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription, } from "./ui/dialog.js";
51
63
  export { Popover, PopoverTrigger, PopoverAnchor, PopoverContent } from "./ui/popover.js";
52
64
  export { Select, selectClassName } from "./ui/select.js";
@@ -74,7 +86,7 @@ export { useNewConversation } from "./lib/use-new-conversation.js";
74
86
  // --- Adapters ---
75
87
  export { AGUIAdapterSDK } from "./lib/ag-ui-adapter.js";
76
88
  export { FileAttachmentAdapter, resolveMimeType } from "./lib/attachment-adapter.js";
77
- export { registerAttachmentAdapter, useAttachmentAdapterStore, } from "./lib/attachment-registry.js";
89
+ export { registerAttachmentAdapter, useAttachmentAdapterStore } from "./lib/attachment-registry.js";
78
90
  export { ReasoningPart, ReasoningMessagePartComponent } from "./components/reasoning-part.js";
79
91
  export { ReasoningEffortPicker } from "./components/reasoning-effort-picker.js";
80
92
  // --- Auth ---
@@ -67,6 +67,22 @@ export interface NavItem {
67
67
  contextInstructions?: string;
68
68
  /** Sub-navigation items (rendered indented under parent) */
69
69
  children?: NavItem[];
70
+ /**
71
+ * The row's children have not been fetched yet, so `children` being empty
72
+ * says nothing about whether it has any.
73
+ *
74
+ * A tree row normally shows one of two markers: a chevron if it branches, a
75
+ * dot if it is a leaf. That is two states for a question with THREE answers
76
+ * — has children, has none, and nobody has looked. A caller that loads
77
+ * subtrees lazily (only the active one, say) leaves every other row with an
78
+ * empty `children`, and the row then claims to be a leaf on the strength of
79
+ * a fetch that never happened.
80
+ *
81
+ * Set this while the answer is unknown and the row shows neither marker: no
82
+ * chevron to click into nothing, no dot asserting an emptiness no one
83
+ * checked. Clear it once the children are known, empty or not.
84
+ */
85
+ childrenUnloaded?: boolean;
70
86
  /** Inline action buttons rendered next to the nav item (e.g., + document, + folder) */
71
87
  actions?: NavItemAction[];
72
88
  /** Optional drag-and-drop behavior (provided by feature modules) */
@@ -5,3 +5,4 @@ export declare const DropdownMenuContent: import("react").ForwardRefExoticCompon
5
5
  export declare const DropdownMenuItem: import("react").ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuItemProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
6
6
  export declare const DropdownMenuSeparator: import("react").ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuSeparatorProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
7
7
  export declare const DropdownMenuLabel: import("react").ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuLabelProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
8
+ export declare const DropdownMenuCheckboxItem: import("react").ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuCheckboxItemProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
@@ -1,6 +1,7 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
3
3
  import { forwardRef } from "react";
4
+ import { Check } from "lucide-react";
4
5
  import { cn } from "@iloveagents/foundry-web-primitives";
5
6
  export const DropdownMenu = DropdownMenuPrimitive.Root;
6
7
  export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
@@ -17,3 +18,5 @@ export const DropdownMenuSeparator = forwardRef(({ className, ...props }, ref) =
17
18
  DropdownMenuSeparator.displayName = "DropdownMenuSeparator";
18
19
  export const DropdownMenuLabel = forwardRef(({ className, ...props }, ref) => (_jsx(DropdownMenuPrimitive.Label, { ref: ref, className: cn("px-2 py-1.5 text-sm font-semibold", className), ...props })));
19
20
  DropdownMenuLabel.displayName = "DropdownMenuLabel";
21
+ export const DropdownMenuCheckboxItem = forwardRef(({ className, children, checked, ...props }, ref) => (_jsxs(DropdownMenuPrimitive.CheckboxItem, { ref: ref, checked: checked, className: cn("relative flex cursor-default select-none items-center gap-2 rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none", "transition-colors focus:bg-accent focus:text-accent-foreground", "data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className), ...props, children: [_jsx("span", { className: "absolute left-2 flex size-4 items-center justify-center", children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx(Check, { className: "size-4" }) }) }), children] })));
22
+ DropdownMenuCheckboxItem.displayName = "DropdownMenuCheckboxItem";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iloveagents/foundry-web-ui",
3
- "version": "0.14.2",
3
+ "version": "0.17.0",
4
4
  "license": "MIT",
5
5
  "description": "React agent UI core for Foundry UI — chat, composer, AG-UI adapter for assistant-ui, tool cards, panels, sidebar, theme runtime, and UI stores.",
6
6
  "keywords": [
@@ -65,13 +65,14 @@
65
65
  "@radix-ui/react-popover": "^1.1.15",
66
66
  "@radix-ui/react-slot": "^1.2.0",
67
67
  "@radix-ui/react-tooltip": "^1.2.0",
68
+ "@tanstack/react-table": "^8.21.3",
68
69
  "class-variance-authority": "^0.7.0",
69
70
  "clsx": "^2.1.0",
70
- "tailwind-merge": "^3.5.0",
71
71
  "react-markdown": "^10.0.0",
72
72
  "remark-gfm": "^4.0.0",
73
- "@iloveagents/foundry-agent": "^0.14.2",
74
- "@iloveagents/foundry-web-primitives": "^0.14.2"
73
+ "tailwind-merge": "^3.5.0",
74
+ "@iloveagents/foundry-agent": "^0.17.0",
75
+ "@iloveagents/foundry-web-primitives": "^0.17.0"
75
76
  },
76
77
  "devDependencies": {
77
78
  "@ag-ui/client": "^0.0.52",
@@ -1,29 +0,0 @@
1
- import type { ReactNode } from "react";
2
- /**
3
- * One row standing in for a run of adjacent tool calls, with the cards under
4
- * it behind a disclosure.
5
- *
6
- * A turn that calls eight tools used to render eight bordered cards with no
7
- * way to fold them away. The run is now one summary line — "8 tools · done" —
8
- * over a group the reader can collapse when the machinery is in the way.
9
- *
10
- * COLLAPSED by default. This was open at first, on the reasoning that a
11
- * developer watching an agent work wants the detail — but in a real
12
- * transcript the cards dominate the answer they were supposed to support,
13
- * and the reader scrolls past machinery to reach the point. The summary line
14
- * already carries what matters at a glance (how many calls, and whether they
15
- * worked); opening it is the deliberate act.
16
- *
17
- * The row shape is shared with the reasoning part via `MessageDisclosure`, so
18
- * the two foldaways in a message read as one family instead of two widgets.
19
- *
20
- * RED only when the whole run failed. One failure among eight successful
21
- * calls is not a red turn — the agent recovered, and painting the summary red
22
- * would report a broken run where there was a working one. A partial failure
23
- * still says so, in the count, without the alarm.
24
- */
25
- export declare function ToolRunGroup({ indices, isRunning, children, }: {
26
- indices: readonly number[];
27
- isRunning: boolean;
28
- children: ReactNode;
29
- }): import("react/jsx-runtime").JSX.Element;
@@ -1,56 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useState } from "react";
3
- import { useAuiState } from "@assistant-ui/react";
4
- import { Loader2, CheckCircle2, XCircle, AlertCircle } from "lucide-react";
5
- import { MessageDisclosure } from "./message-disclosure.js";
6
- import { _detectErrorResult } from "./tool-fallback.js";
7
- /**
8
- * One row standing in for a run of adjacent tool calls, with the cards under
9
- * it behind a disclosure.
10
- *
11
- * A turn that calls eight tools used to render eight bordered cards with no
12
- * way to fold them away. The run is now one summary line — "8 tools · done" —
13
- * over a group the reader can collapse when the machinery is in the way.
14
- *
15
- * COLLAPSED by default. This was open at first, on the reasoning that a
16
- * developer watching an agent work wants the detail — but in a real
17
- * transcript the cards dominate the answer they were supposed to support,
18
- * and the reader scrolls past machinery to reach the point. The summary line
19
- * already carries what matters at a glance (how many calls, and whether they
20
- * worked); opening it is the deliberate act.
21
- *
22
- * The row shape is shared with the reasoning part via `MessageDisclosure`, so
23
- * the two foldaways in a message read as one family instead of two widgets.
24
- *
25
- * RED only when the whole run failed. One failure among eight successful
26
- * calls is not a red turn — the agent recovered, and painting the summary red
27
- * would report a broken run where there was a working one. A partial failure
28
- * still says so, in the count, without the alarm.
29
- */
30
- export function ToolRunGroup({ indices, isRunning, children, }) {
31
- const { total, failed } = useToolRunOutcome(indices);
32
- // Every call in the run failed — the only state that earns the alarm colour.
33
- const allFailed = total > 0 && failed === total;
34
- const [isOpen, setIsOpen] = useState(false);
35
- const label = isRunning
36
- ? `Using ${total === 1 ? "1 tool" : `${total} tools`}…`
37
- : failed > 0
38
- ? `${total === 1 ? "1 tool" : `${total} tools`} · ${failed} failed`
39
- : `${total === 1 ? "1 tool" : `${total} tools`} · done`;
40
- return (_jsx(MessageDisclosure, { open: isOpen, onOpenChange: setIsOpen, tone: allFailed ? "destructive" : "muted", icon: isRunning ? (_jsx(Loader2, { className: "size-3.5 animate-spin text-primary" })) : allFailed ? (_jsx(XCircle, { className: "size-3.5 text-destructive" })) : failed > 0 ? (
41
- // Partial: the run produced a result, some of it the hard way.
42
- _jsx(AlertCircle, { className: "size-3.5 text-muted-foreground" })) : (_jsx(CheckCircle2, { className: "size-3.5 text-primary" })), label: label, "data-tool-run-group": true, children: _jsx("div", { className: "flex flex-col gap-2", children: children }) }));
43
- }
44
- function useToolRunOutcome(indices) {
45
- const parts = useAuiState((s) => s.message.parts);
46
- let failed = 0;
47
- for (const index of indices) {
48
- const part = parts?.[index];
49
- if (!part)
50
- continue;
51
- const status = part.status;
52
- if (status?.type === "incomplete" || _detectErrorResult(part.result))
53
- failed++;
54
- }
55
- return { total: indices.length, failed };
56
- }