@iloveagents/foundry-web-ui 0.3.1 → 0.5.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 (227) hide show
  1. package/README.md +13 -4
  2. package/dist/components/ag-ui-runtime-provider.d.ts +64 -0
  3. package/dist/components/ag-ui-runtime-provider.js +71 -0
  4. package/dist/components/app-brand.d.ts +6 -0
  5. package/dist/components/app-brand.js +13 -0
  6. package/dist/components/assistant-chat.d.ts +26 -0
  7. package/dist/components/assistant-chat.js +94 -0
  8. package/dist/components/chat-attachments.d.ts +25 -0
  9. package/dist/components/chat-attachments.js +20 -0
  10. package/dist/components/chat-bubble.d.ts +1 -0
  11. package/dist/components/chat-bubble.js +105 -0
  12. package/dist/components/chat-header.d.ts +2 -0
  13. package/dist/components/chat-header.js +16 -0
  14. package/dist/components/client-tool-executor.d.ts +12 -0
  15. package/dist/components/client-tool-executor.js +108 -0
  16. package/dist/components/collection-empty-state.d.ts +9 -0
  17. package/dist/components/collection-empty-state.js +6 -0
  18. package/dist/components/collection-filter-bar.d.ts +19 -0
  19. package/dist/components/collection-filter-bar.js +28 -0
  20. package/dist/components/collection-search-input.d.ts +6 -0
  21. package/dist/components/collection-search-input.js +8 -0
  22. package/dist/components/collection-skeleton.d.ts +6 -0
  23. package/dist/components/collection-skeleton.js +10 -0
  24. package/dist/components/collection-sort-menu.d.ts +10 -0
  25. package/dist/components/collection-sort-menu.js +9 -0
  26. package/dist/components/collection-surface.d.ts +8 -0
  27. package/dist/components/collection-surface.js +5 -0
  28. package/dist/components/collection-toolbar.d.ts +9 -0
  29. package/dist/components/collection-toolbar.js +5 -0
  30. package/dist/components/collection-view-toggle.d.ts +7 -0
  31. package/dist/components/collection-view-toggle.js +17 -0
  32. package/dist/components/composer-submit-bridge.d.ts +1 -0
  33. package/dist/components/composer-submit-bridge.js +15 -0
  34. package/dist/components/confirm-dialog.d.ts +11 -0
  35. package/dist/components/confirm-dialog.js +6 -0
  36. package/dist/components/confirmation-card.d.ts +14 -0
  37. package/dist/components/confirmation-card.js +12 -0
  38. package/dist/components/context-badges.d.ts +16 -0
  39. package/dist/components/context-badges.js +56 -0
  40. package/dist/components/context-bar.d.ts +41 -0
  41. package/dist/components/context-bar.js +123 -0
  42. package/dist/components/form-dialog.d.ts +12 -0
  43. package/dist/components/form-dialog.js +13 -0
  44. package/dist/components/global-selection-popover.d.ts +7 -0
  45. package/dist/components/global-selection-popover.js +167 -0
  46. package/dist/components/infinite-scroll-sentinel.d.ts +6 -0
  47. package/dist/components/infinite-scroll-sentinel.js +25 -0
  48. package/dist/components/json-viewer.d.ts +10 -0
  49. package/dist/components/json-viewer.js +80 -0
  50. package/dist/components/loading-indicator.d.ts +13 -0
  51. package/dist/components/loading-indicator.js +35 -0
  52. package/dist/components/markdown-text.d.ts +13 -0
  53. package/dist/components/markdown-text.js +221 -0
  54. package/dist/components/message-error.d.ts +10 -0
  55. package/dist/components/message-error.js +10 -0
  56. package/dist/components/message-timestamp.d.ts +10 -0
  57. package/dist/components/message-timestamp.js +18 -0
  58. package/dist/components/name-dialog.d.ts +16 -0
  59. package/dist/components/name-dialog.js +23 -0
  60. package/dist/components/selection-popover.d.ts +12 -0
  61. package/dist/components/selection-popover.js +111 -0
  62. package/dist/components/show-document-tool-ui.d.ts +1 -0
  63. package/dist/components/show-document-tool-ui.js +53 -0
  64. package/dist/components/sidebar.d.ts +4 -0
  65. package/dist/components/sidebar.js +904 -0
  66. package/dist/components/surface-card.d.ts +9 -0
  67. package/dist/components/surface-card.js +33 -0
  68. package/dist/components/theme-runtime-provider.d.ts +15 -0
  69. package/dist/components/theme-runtime-provider.js +48 -0
  70. package/dist/components/theme-toggle.d.ts +2 -0
  71. package/dist/components/theme-toggle.js +19 -0
  72. package/dist/components/tool-call-card.d.ts +27 -0
  73. package/dist/components/tool-call-card.js +13 -0
  74. package/dist/components/tool-fallback.d.ts +26 -0
  75. package/dist/components/tool-fallback.js +129 -0
  76. package/dist/components/tool-panel-layout.d.ts +10 -0
  77. package/dist/components/tool-panel-layout.js +12 -0
  78. package/dist/components/tool-panel.d.ts +1 -0
  79. package/dist/components/tool-panel.js +89 -0
  80. package/dist/components/tooltip-icon-button.d.ts +7 -0
  81. package/dist/components/tooltip-icon-button.js +9 -0
  82. package/dist/components/user-menu.d.ts +2 -0
  83. package/dist/components/user-menu.js +14 -0
  84. package/dist/index.d.ts +68 -0
  85. package/dist/index.js +83 -0
  86. package/dist/lib/ag-ui-adapter.d.ts +29 -0
  87. package/dist/lib/ag-ui-adapter.js +382 -0
  88. package/dist/lib/app-store.d.ts +115 -0
  89. package/dist/lib/app-store.js +199 -0
  90. package/dist/lib/attachment-adapter.d.ts +20 -0
  91. package/dist/lib/attachment-adapter.js +99 -0
  92. package/dist/lib/auth-provider.d.ts +16 -0
  93. package/dist/lib/auth-provider.js +88 -0
  94. package/dist/lib/chat-bubble-store.d.ts +30 -0
  95. package/dist/lib/chat-bubble-store.js +23 -0
  96. package/dist/lib/chat-lifecycle-store.d.ts +10 -0
  97. package/{src/lib/chat-lifecycle-store.ts → dist/lib/chat-lifecycle-store.js} +3 -13
  98. package/dist/lib/client-tools.d.ts +11 -0
  99. package/dist/lib/client-tools.js +274 -0
  100. package/dist/lib/composer-submit-store.d.ts +12 -0
  101. package/dist/lib/composer-submit-store.js +17 -0
  102. package/dist/lib/dev-store.d.ts +38 -0
  103. package/dist/lib/dev-store.js +32 -0
  104. package/dist/lib/nav-config.d.ts +159 -0
  105. package/dist/lib/nav-config.js +162 -0
  106. package/dist/lib/nav-dnd.d.ts +8 -0
  107. package/dist/lib/nav-dnd.js +9 -0
  108. package/dist/lib/nav-store.d.ts +45 -0
  109. package/dist/lib/nav-store.js +49 -0
  110. package/dist/lib/selection-context.d.ts +21 -0
  111. package/dist/lib/selection-context.js +34 -0
  112. package/dist/lib/sidebar-store.d.ts +23 -0
  113. package/dist/lib/sidebar-store.js +98 -0
  114. package/dist/lib/theme-runtime.d.ts +130 -0
  115. package/dist/lib/theme-runtime.js +479 -0
  116. package/dist/lib/theme-store.d.ts +15 -0
  117. package/dist/lib/theme-store.js +53 -0
  118. package/dist/lib/tool-panel-store.d.ts +48 -0
  119. package/dist/lib/tool-panel-store.js +69 -0
  120. package/dist/lib/use-new-conversation.d.ts +8 -0
  121. package/dist/lib/use-new-conversation.js +62 -0
  122. package/dist/lib/use-page-tools.d.ts +27 -0
  123. package/{src/lib/use-page-tools.ts → dist/lib/use-page-tools.js} +11 -13
  124. package/dist/styles.css +142 -0
  125. package/dist/ui/collapsible.d.ts +5 -0
  126. package/{src/ui/collapsible.tsx → dist/ui/collapsible.js} +0 -2
  127. package/dist/ui/dialog.d.ts +15 -0
  128. package/dist/ui/dialog.js +25 -0
  129. package/dist/ui/dropdown-menu.d.ts +7 -0
  130. package/dist/ui/dropdown-menu.js +19 -0
  131. package/dist/ui/popover.d.ts +7 -0
  132. package/dist/ui/popover.js +10 -0
  133. package/dist/ui/select.d.ts +4 -0
  134. package/dist/ui/select.js +9 -0
  135. package/dist/ui/tooltip.d.ts +7 -0
  136. package/dist/ui/tooltip.js +10 -0
  137. package/package.json +31 -12
  138. package/AGENTS.md +0 -59
  139. package/CHANGELOG.md +0 -211
  140. package/CLAUDE.md +0 -1
  141. package/src/__tests__/no-spaces-imports.test.ts +0 -59
  142. package/src/__tests__/open-core-guards.test.ts +0 -307
  143. package/src/__tests__/theme-runtime.test.ts +0 -81
  144. package/src/__tests__/tool-fallback.test.tsx +0 -109
  145. package/src/components/__tests__/context-bar.test.ts +0 -112
  146. package/src/components/ag-ui-runtime-provider.tsx +0 -170
  147. package/src/components/app-brand.tsx +0 -38
  148. package/src/components/assistant-chat.tsx +0 -461
  149. package/src/components/chat-attachments.tsx +0 -68
  150. package/src/components/chat-bubble.tsx +0 -373
  151. package/src/components/chat-header.tsx +0 -44
  152. package/src/components/client-tool-executor.tsx +0 -230
  153. package/src/components/collection-empty-state.tsx +0 -37
  154. package/src/components/collection-filter-bar.tsx +0 -168
  155. package/src/components/collection-search-input.tsx +0 -41
  156. package/src/components/collection-skeleton.tsx +0 -55
  157. package/src/components/collection-sort-menu.tsx +0 -62
  158. package/src/components/collection-surface.tsx +0 -46
  159. package/src/components/collection-toolbar.tsx +0 -33
  160. package/src/components/collection-view-toggle.tsx +0 -61
  161. package/src/components/composer-submit-bridge.tsx +0 -19
  162. package/src/components/confirm-dialog.tsx +0 -49
  163. package/src/components/confirmation-card.tsx +0 -32
  164. package/src/components/context-badges.tsx +0 -136
  165. package/src/components/context-bar.tsx +0 -238
  166. package/src/components/form-dialog.tsx +0 -56
  167. package/src/components/global-selection-popover.tsx +0 -266
  168. package/src/components/infinite-scroll-sentinel.tsx +0 -48
  169. package/src/components/json-viewer.tsx +0 -232
  170. package/src/components/loading-indicator.tsx +0 -40
  171. package/src/components/markdown-text-citations.test.tsx +0 -108
  172. package/src/components/markdown-text.tsx +0 -508
  173. package/src/components/name-dialog.tsx +0 -87
  174. package/src/components/selection-popover.tsx +0 -156
  175. package/src/components/show-document-tool-ui.tsx +0 -90
  176. package/src/components/sidebar.test.tsx +0 -611
  177. package/src/components/sidebar.tsx +0 -1689
  178. package/src/components/surface-card.tsx +0 -45
  179. package/src/components/theme-runtime-provider.tsx +0 -93
  180. package/src/components/theme-toggle.tsx +0 -27
  181. package/src/components/tool-call-card.tsx +0 -126
  182. package/src/components/tool-fallback.tsx +0 -169
  183. package/src/components/tool-panel-layout.tsx +0 -36
  184. package/src/components/tool-panel.tsx +0 -233
  185. package/src/components/tooltip-icon-button.tsx +0 -28
  186. package/src/components/user-menu.tsx +0 -60
  187. package/src/index.ts +0 -185
  188. package/src/lib/__tests__/ag-ui-adapter.test.ts +0 -279
  189. package/src/lib/__tests__/app-store.test.ts +0 -405
  190. package/src/lib/__tests__/attachment-adapter.test.ts +0 -48
  191. package/src/lib/__tests__/chat-bubble-store.test.ts +0 -67
  192. package/src/lib/__tests__/client-tools.test.ts +0 -124
  193. package/src/lib/__tests__/dev-store.test.ts +0 -78
  194. package/src/lib/__tests__/nav-config.test.ts +0 -135
  195. package/src/lib/__tests__/nav-dnd.test.ts +0 -24
  196. package/src/lib/__tests__/nav-store.test.ts +0 -59
  197. package/src/lib/__tests__/selection-context.test.ts +0 -114
  198. package/src/lib/__tests__/sidebar-store.test.ts +0 -144
  199. package/src/lib/__tests__/theme-store.test.ts +0 -83
  200. package/src/lib/__tests__/tool-panel-store.test.ts +0 -61
  201. package/src/lib/__tests__/use-page-context.test.ts +0 -58
  202. package/src/lib/ag-ui-adapter.ts +0 -426
  203. package/src/lib/app-store.ts +0 -361
  204. package/src/lib/attachment-adapter.ts +0 -92
  205. package/src/lib/auth-provider.tsx +0 -144
  206. package/src/lib/chat-bubble-store.ts +0 -46
  207. package/src/lib/client-tools.ts +0 -293
  208. package/src/lib/composer-submit-store.ts +0 -32
  209. package/src/lib/dev-store.ts +0 -69
  210. package/src/lib/nav-config.ts +0 -329
  211. package/src/lib/nav-dnd.ts +0 -14
  212. package/src/lib/nav-store.ts +0 -89
  213. package/src/lib/selection-context.ts +0 -65
  214. package/src/lib/sidebar-store.ts +0 -113
  215. package/src/lib/theme-runtime.ts +0 -660
  216. package/src/lib/theme-store.ts +0 -67
  217. package/src/lib/tool-panel-store.ts +0 -121
  218. package/src/lib/use-new-conversation.test.tsx +0 -168
  219. package/src/lib/use-new-conversation.ts +0 -64
  220. package/src/ui/dialog.tsx +0 -109
  221. package/src/ui/dropdown-menu.tsx +0 -75
  222. package/src/ui/popover.tsx +0 -34
  223. package/src/ui/select.tsx +0 -15
  224. package/src/ui/tooltip.tsx +0 -30
  225. package/src/vite-env.d.ts +0 -1
  226. package/tsconfig.json +0 -16
  227. package/vitest.config.ts +0 -8
@@ -1,67 +0,0 @@
1
- /**
2
- * Theme Store -- Zustand store for dark/light/system theme preference.
3
- *
4
- * Persists the user's choice to localStorage and applies the `.dark` class
5
- * on `<html>`. In "system" mode it tracks the OS preference via matchMedia.
6
- */
7
-
8
- import { create } from "zustand";
9
-
10
- export type ThemeMode = "light" | "dark" | "system";
11
-
12
- interface ThemeState {
13
- mode: ThemeMode;
14
- setMode: (mode: ThemeMode) => void;
15
- /** Cycle through light -> dark -> system. */
16
- cycle: () => void;
17
- }
18
-
19
- const STORAGE_KEY = "theme";
20
- const CYCLE_ORDER: ThemeMode[] = ["light", "dark", "system"];
21
-
22
- function getStoredMode(): ThemeMode {
23
- if (typeof window === "undefined") return "system";
24
- const stored = localStorage.getItem(STORAGE_KEY);
25
- if (stored === "light" || stored === "dark" || stored === "system") return stored;
26
- return "system";
27
- }
28
-
29
- function prefersDark(): boolean {
30
- if (typeof window === "undefined") return false;
31
- return window.matchMedia("(prefers-color-scheme: dark)").matches;
32
- }
33
-
34
- function applyTheme(mode: ThemeMode) {
35
- if (typeof document === "undefined") return;
36
- const isDark = mode === "dark" || (mode === "system" && prefersDark());
37
- document.documentElement.classList.toggle("dark", isDark);
38
- }
39
-
40
- export const useThemeStore = create<ThemeState>((set, get) => {
41
- const initial = getStoredMode();
42
- applyTheme(initial);
43
-
44
- // Listen for OS preference changes when in system mode
45
- if (typeof window !== "undefined") {
46
- window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
47
- if (get().mode === "system") applyTheme("system");
48
- });
49
- }
50
-
51
- return {
52
- mode: initial,
53
-
54
- setMode: (mode) => {
55
- localStorage.setItem(STORAGE_KEY, mode);
56
- applyTheme(mode);
57
- set({ mode });
58
- },
59
-
60
- cycle: () => {
61
- const current = get().mode;
62
- const idx = CYCLE_ORDER.indexOf(current);
63
- const next = CYCLE_ORDER[(idx + 1) % CYCLE_ORDER.length];
64
- get().setMode(next);
65
- },
66
- };
67
- });
@@ -1,121 +0,0 @@
1
- /**
2
- * Tool Panel State Management
3
- *
4
- * Zustand store for managing the tool panel (flyout side panel).
5
- * Allows both UI components and agent tools to control the panel.
6
- */
7
-
8
- import { create } from "zustand";
9
- import type { LazyExoticComponent, ComponentType } from "react";
10
-
11
- export interface PdfHighlightRef {
12
- pageNumber: number;
13
- polygon: number[];
14
- }
15
-
16
- export interface ToolPanelContent {
17
- title: string;
18
- content: string;
19
- type: "markdown" | "text" | "page" | "pdf";
20
- /** PDF highlights for citation overlays (only when type === "pdf") */
21
- pdfHighlights?: PdfHighlightRef[];
22
- /** Initial page to scroll to (only when type === "pdf") */
23
- initialPage?: number;
24
- /** SPACES entity ID — enables pin and navigate actions in panel header */
25
- entityId?: string;
26
- }
27
-
28
- /** Pluggable content renderer for the tool panel — registered by feature modules. */
29
- export interface PanelRendererEntry {
30
- /** Check if this renderer handles the given content */
31
- match: (content: ToolPanelContent) => boolean;
32
- /** Lazy component to render. Receives content as prop. */
33
- component: LazyExoticComponent<ComponentType<{ content: ToolPanelContent }>>;
34
- }
35
-
36
- const SIDE_BY_SIDE_BREAKPOINT = 1280;
37
-
38
- const getPanelWidth = (): number => {
39
- if (typeof window === "undefined") return 800;
40
- const screenWidth = window.innerWidth;
41
- if (screenWidth < SIDE_BY_SIDE_BREAKPOINT) return screenWidth;
42
- return Math.min(1000, Math.max(600, screenWidth * 0.5));
43
- };
44
-
45
- const shouldUseOverlay = (): boolean => {
46
- if (typeof window === "undefined") return true;
47
- return window.innerWidth < SIDE_BY_SIDE_BREAKPOINT;
48
- };
49
-
50
- interface ToolPanelState {
51
- isOpen: boolean;
52
- content: ToolPanelContent | null;
53
-
54
- // Layout state
55
- panelWidth: number;
56
- useOverlay: boolean;
57
- isFullscreen: boolean;
58
-
59
- // Pluggable content renderers (registered by feature modules)
60
- renderers: PanelRendererEntry[];
61
-
62
- // Actions
63
- openPanel: (content: ToolPanelContent) => void;
64
- closePanel: () => void;
65
- setContent: (content: ToolPanelContent) => void;
66
- updateLayout: () => void;
67
- toggleFullscreen: () => void;
68
- /** Register a custom content renderer (e.g., PDF viewer, entity pages). */
69
- registerRenderer: (entry: PanelRendererEntry) => void;
70
- }
71
-
72
- export const useToolPanelStore = create<ToolPanelState>((set, get) => ({
73
- isOpen: false,
74
- content: null,
75
- panelWidth: getPanelWidth(),
76
- useOverlay: shouldUseOverlay(),
77
- isFullscreen: false,
78
- renderers: [],
79
-
80
- openPanel: (content) => set({ isOpen: true, content }),
81
- registerRenderer: (entry) =>
82
- set((state) => ({ renderers: [...state.renderers, entry] })),
83
- closePanel: () => set({ isOpen: false, isFullscreen: false }),
84
- setContent: (content) => set({ content }),
85
- updateLayout: () => {
86
- const { isFullscreen } = get();
87
- if (!isFullscreen) {
88
- set({
89
- panelWidth: getPanelWidth(),
90
- useOverlay: shouldUseOverlay(),
91
- });
92
- }
93
- },
94
- toggleFullscreen: () => {
95
- const { isFullscreen } = get();
96
- if (isFullscreen) {
97
- set({
98
- isFullscreen: false,
99
- panelWidth: getPanelWidth(),
100
- useOverlay: shouldUseOverlay(),
101
- });
102
- } else {
103
- set({
104
- isFullscreen: true,
105
- panelWidth: typeof window !== "undefined" ? window.innerWidth : 1200,
106
- useOverlay: true,
107
- });
108
- }
109
- },
110
- }));
111
-
112
- // Debounced resize listener
113
- if (typeof window !== "undefined") {
114
- let timeout: ReturnType<typeof setTimeout>;
115
- window.addEventListener("resize", () => {
116
- clearTimeout(timeout);
117
- timeout = setTimeout(() => {
118
- useToolPanelStore.getState().updateLayout();
119
- }, 100);
120
- });
121
- }
@@ -1,168 +0,0 @@
1
- import { act } from "react";
2
- import { createRoot, type Root } from "react-dom/client";
3
- import { MemoryRouter, useLocation } from "react-router";
4
- import { beforeEach, describe, expect, it, vi, afterEach } from "vitest";
5
- import { useAppStore } from "./app-store.ts";
6
- import { citationStore } from "@iloveagents/foundry-agent";
7
- import { useSidebarStore } from "./sidebar-store.ts";
8
- import { useToolPanelStore } from "./tool-panel-store.ts";
9
- import { useNewConversation } from "./use-new-conversation.ts";
10
-
11
- const switchToNewThread = vi.fn();
12
- const resetThread = vi.fn();
13
-
14
- vi.mock("@assistant-ui/react", () => ({
15
- useAui: () => ({
16
- threads: () => ({
17
- switchToNewThread,
18
- }),
19
- }),
20
- }));
21
-
22
- vi.mock("../components/ag-ui-runtime-provider.tsx", () => ({
23
- useAGUIAdapter: () => ({
24
- resetThread,
25
- }),
26
- }));
27
-
28
- function LocationProbe() {
29
- const location = useLocation();
30
- return <div data-testid="location">{location.pathname}</div>;
31
- }
32
-
33
- function Trigger() {
34
- const handleNewConversation = useNewConversation();
35
- return (
36
- <button type="button" onClick={handleNewConversation}>
37
- New Thread
38
- </button>
39
- );
40
- }
41
-
42
- function PreserveNavigationTrigger() {
43
- const handleNewConversation = useNewConversation({
44
- navigateToChat: false,
45
- preserveNavigation: true,
46
- });
47
- return (
48
- <button type="button" onClick={handleNewConversation}>
49
- New Bubble Thread
50
- </button>
51
- );
52
- }
53
-
54
- describe("useNewConversation", () => {
55
- let container: HTMLDivElement;
56
- let root: Root;
57
- let originalActEnvironment: boolean | undefined;
58
-
59
- beforeEach(() => {
60
- originalActEnvironment = globalThis.IS_REACT_ACT_ENVIRONMENT;
61
- globalThis.IS_REACT_ACT_ENVIRONMENT = true;
62
- switchToNewThread.mockReset();
63
- resetThread.mockReset();
64
- useToolPanelStore.setState({ isOpen: true });
65
- useSidebarStore.setState({ isMobileOpen: true });
66
- citationStore.setState({
67
- results: [
68
- {
69
- chunk_id: "chunk-1",
70
- entity_id: "file-1",
71
- entity_name: "Test File",
72
- content: "Excerpt",
73
- page_number: 1,
74
- bounding_regions: "[]",
75
- score: 1,
76
- },
77
- ],
78
- handler: { openCitation: vi.fn() },
79
- });
80
- useAppStore.setState({
81
- threadActive: true,
82
- currentPage: "/spaces/demo",
83
- navContext: {
84
- group: "Workspaces",
85
- groupDescription: null,
86
- groupMeta: {},
87
- label: "Demo",
88
- description: null,
89
- meta: {},
90
- contextInstructions: null,
91
- },
92
- selectedItemId: "entity-1",
93
- content: { title: "Draft" },
94
- highlights: [{ entityId: "entity-1", blockId: "block-1" }],
95
- contextItems: [],
96
- sentContext: {},
97
- });
98
- container = document.createElement("div");
99
- document.body.appendChild(container);
100
- root = createRoot(container);
101
- });
102
-
103
- afterEach(() => {
104
- act(() => {
105
- root.unmount();
106
- });
107
- container.remove();
108
- globalThis.IS_REACT_ACT_ENVIRONMENT = originalActEnvironment;
109
- });
110
-
111
- it("navigates to chat and resets thread state", async () => {
112
- await act(async () => {
113
- root.render(
114
- <MemoryRouter initialEntries={["/spaces/demo"]}>
115
- <Trigger />
116
- <LocationProbe />
117
- </MemoryRouter>,
118
- );
119
- });
120
-
121
- const button = container.querySelector("button");
122
- expect(button).toBeTruthy();
123
-
124
- await act(async () => {
125
- button?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
126
- });
127
-
128
- expect(container.querySelector('[data-testid="location"]')?.textContent).toBe("/");
129
- expect(switchToNewThread).toHaveBeenCalledTimes(1);
130
- expect(resetThread).toHaveBeenCalledTimes(1);
131
- expect(useToolPanelStore.getState().isOpen).toBe(false);
132
- expect(useSidebarStore.getState().isMobileOpen).toBe(false);
133
- expect(citationStore.getState().results).toEqual([]);
134
- // Handler is module-level state registered once at app boot by
135
- // feature modules (e.g. SPACES' ``registerSpacesCitationHandler``)
136
- // and MUST survive per-conversation resets — wiping it here would
137
- // make every ``[n]`` marker inert for the rest of the session,
138
- // since the registration guard suppresses re-binding. See
139
- // ``packages/agent/src/store/citation-store.ts``.
140
- expect(citationStore.getState().handler).not.toBeNull();
141
- });
142
-
143
- it("can reset the thread without leaving the current page", async () => {
144
- await act(async () => {
145
- root.render(
146
- <MemoryRouter initialEntries={["/spaces/demo"]}>
147
- <PreserveNavigationTrigger />
148
- <LocationProbe />
149
- </MemoryRouter>,
150
- );
151
- });
152
-
153
- const button = container.querySelector("button");
154
- expect(button).toBeTruthy();
155
-
156
- await act(async () => {
157
- button?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
158
- });
159
-
160
- expect(container.querySelector('[data-testid="location"]')?.textContent).toBe("/spaces/demo");
161
- expect(switchToNewThread).toHaveBeenCalledTimes(1);
162
- expect(resetThread).toHaveBeenCalledTimes(1);
163
- expect(useAppStore.getState().currentPage).toBe("/spaces/demo");
164
- expect(useAppStore.getState().navContext.label).toBe("Demo");
165
- expect(useAppStore.getState().threadActive).toBe(false);
166
- expect(useAppStore.getState().contextItems).toEqual([]);
167
- });
168
- });
@@ -1,64 +0,0 @@
1
- import { useCallback } from "react";
2
- import { useNavigate } from "react-router";
3
- import { useAui } from "@assistant-ui/react";
4
- import { useStore } from "zustand";
5
- import { citationStore } from "@iloveagents/foundry-agent";
6
- import { useAGUIAdapter } from "../components/ag-ui-runtime-provider.tsx";
7
- import { useToolPanelStore } from "./tool-panel-store.ts";
8
- import { useAppStore } from "./app-store.ts";
9
- import { useSidebarStore } from "./sidebar-store.ts";
10
- import { useChatLifecycleStore } from "./chat-lifecycle-store.ts";
11
-
12
- /**
13
- * Shared hook for starting a new conversation.
14
- * Used by ChatHeader and Sidebar to keep behavior in sync.
15
- */
16
- export function useNewConversation(options?: { navigateToChat?: boolean; preserveNavigation?: boolean }) {
17
- const aui = useAui();
18
- const { resetThread } = useAGUIAdapter();
19
- const navigate = useNavigate();
20
- const closePanel = useToolPanelStore((s) => s.closePanel);
21
- const resetApp = useAppStore((s) => s.resetAll);
22
- const closeMobile = useSidebarStore((s) => s.closeMobile);
23
- const clearCitations = useStore(citationStore, (s) => s.clear);
24
-
25
- return useCallback(() => {
26
- const preserved = options?.preserveNavigation
27
- ? {
28
- currentPage: useAppStore.getState().currentPage,
29
- navContext: useAppStore.getState().navContext,
30
- }
31
- : null;
32
- closePanel();
33
- resetApp();
34
- if (preserved) {
35
- useAppStore.setState(preserved);
36
- }
37
- closeMobile();
38
- clearCitations();
39
- // Notify the host app that a new thread is starting. Spaces wires
40
- // this to clear its active-chat sticky id so the AGUIRuntimeProvider's
41
- // ``effectiveThreadId`` advances to a freshly-minted UUID instead of
42
- // staying pinned to the previous chat. Must fire BEFORE ``resetThread``
43
- // so the next render's history adapter binds to the new id and
44
- // doesn't re-hydrate the prior chat's messages — the exact bug this
45
- // wiring fixes for the popover (``navigateToChat: false``) path,
46
- // where there is no ``/`` navigation to clear sticky for us.
47
- useChatLifecycleStore.getState().onNewConversation?.();
48
- resetThread();
49
- aui.threads().switchToNewThread();
50
- if (options?.navigateToChat ?? true) {
51
- navigate("/");
52
- }
53
- }, [
54
- closePanel,
55
- resetApp,
56
- options?.preserveNavigation,
57
- closeMobile,
58
- clearCitations,
59
- resetThread,
60
- aui,
61
- options?.navigateToChat,
62
- navigate,
63
- ]);
64
- }
package/src/ui/dialog.tsx DELETED
@@ -1,109 +0,0 @@
1
- import * as React from "react";
2
- import * as DialogPrimitive from "@radix-ui/react-dialog";
3
- import { X } from "lucide-react";
4
-
5
- import { cn } from "@iloveagents/foundry-web-primitives";
6
- import { ThemeScope } from "../components/theme-runtime-provider.tsx";
7
-
8
- const Dialog = DialogPrimitive.Root;
9
- const DialogTrigger = DialogPrimitive.Trigger;
10
- const DialogPortal = DialogPrimitive.Portal;
11
- const DialogClose = DialogPrimitive.Close;
12
-
13
- const DialogOverlay = React.forwardRef<
14
- React.ElementRef<typeof DialogPrimitive.Overlay>,
15
- React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
16
- >(({ className, ...props }, ref) => (
17
- <DialogPrimitive.Overlay
18
- ref={ref}
19
- className={cn(
20
- "fixed inset-0 z-50 bg-black/55 backdrop-blur-sm",
21
- "data-[state=open]:animate-in data-[state=closed]:animate-out",
22
- "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
23
- className,
24
- )}
25
- {...props}
26
- />
27
- ));
28
- DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
29
-
30
- const DialogContent = React.forwardRef<
31
- React.ElementRef<typeof DialogPrimitive.Content>,
32
- React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
33
- showCloseButton?: boolean;
34
- }
35
- >(({ className, children, showCloseButton = true, ...props }, ref) => (
36
- <DialogPortal>
37
- <DialogOverlay />
38
- <DialogPrimitive.Content
39
- ref={ref}
40
- className={cn(
41
- "fixed left-1/2 top-1/2 z-50 grid w-[calc(100%-2rem)] max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4",
42
- "rounded-[2rem] border border-border bg-background/95 p-6 shadow-2xl duration-200",
43
- "data-[state=open]:animate-in data-[state=closed]:animate-out",
44
- "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
45
- "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
46
- className,
47
- )}
48
- {...props}
49
- >
50
- {/* ThemeScope ensures portaled dialogs inherit the active theme */}
51
- <ThemeScope style={{ display: "contents" }}>
52
- {children}
53
- </ThemeScope>
54
- {showCloseButton ? (
55
- <DialogPrimitive.Close
56
- className={cn(
57
- "absolute right-5 top-5 rounded-md p-1 transition-colors hover:bg-muted",
58
- "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
59
- )}
60
- aria-label="Close"
61
- >
62
- <X className="size-4 text-muted-foreground" />
63
- </DialogPrimitive.Close>
64
- ) : null}
65
- </DialogPrimitive.Content>
66
- </DialogPortal>
67
- ));
68
- DialogContent.displayName = DialogPrimitive.Content.displayName;
69
-
70
- function DialogHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
71
- return <div className={cn("flex flex-col gap-1.5 pr-10", className)} {...props} />;
72
- }
73
-
74
- function DialogFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
75
- return <div className={cn("flex justify-end gap-2", className)} {...props} />;
76
- }
77
-
78
- const DialogTitle = React.forwardRef<
79
- React.ElementRef<typeof DialogPrimitive.Title>,
80
- React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
81
- >(({ className, ...props }, ref) => (
82
- <DialogPrimitive.Title ref={ref} className={cn("text-lg font-semibold", className)} {...props} />
83
- ));
84
- DialogTitle.displayName = DialogPrimitive.Title.displayName;
85
-
86
- const DialogDescription = React.forwardRef<
87
- React.ElementRef<typeof DialogPrimitive.Description>,
88
- React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
89
- >(({ className, ...props }, ref) => (
90
- <DialogPrimitive.Description
91
- ref={ref}
92
- className={cn("text-sm text-muted-foreground", className)}
93
- {...props}
94
- />
95
- ));
96
- DialogDescription.displayName = DialogPrimitive.Description.displayName;
97
-
98
- export {
99
- Dialog,
100
- DialogPortal,
101
- DialogOverlay,
102
- DialogTrigger,
103
- DialogClose,
104
- DialogContent,
105
- DialogHeader,
106
- DialogFooter,
107
- DialogTitle,
108
- DialogDescription,
109
- };
@@ -1,75 +0,0 @@
1
- import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
2
- import { type ComponentPropsWithRef, forwardRef } from "react";
3
- import { cn } from "@iloveagents/foundry-web-primitives";
4
-
5
- export const DropdownMenu = DropdownMenuPrimitive.Root;
6
- export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
7
-
8
- export const DropdownMenuContent = forwardRef<
9
- HTMLDivElement,
10
- ComponentPropsWithRef<typeof DropdownMenuPrimitive.Content>
11
- >(({ className, sideOffset = 4, ...props }, ref) => (
12
- <DropdownMenuPrimitive.Portal>
13
- <DropdownMenuPrimitive.Content
14
- ref={ref}
15
- sideOffset={sideOffset}
16
- className={cn(
17
- "z-50 min-w-[8rem] overflow-hidden rounded-md border border-border",
18
- "bg-popover p-1 text-popover-foreground shadow-md",
19
- "data-[state=open]:animate-in data-[state=closed]:animate-out",
20
- "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
21
- "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
22
- "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
23
- "data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
24
- className,
25
- )}
26
- {...props}
27
- />
28
- </DropdownMenuPrimitive.Portal>
29
- ));
30
- DropdownMenuContent.displayName = "DropdownMenuContent";
31
-
32
- export const DropdownMenuItem = forwardRef<
33
- HTMLDivElement,
34
- ComponentPropsWithRef<typeof DropdownMenuPrimitive.Item>
35
- >(({ className, ...props }, ref) => (
36
- <DropdownMenuPrimitive.Item
37
- ref={ref}
38
- className={cn(
39
- // ``gap-2`` is the same icon-to-text spacing the framework's
40
- // Button primitive uses, so a ``<lucide-icon /> Label`` pattern
41
- // inside any menu item renders consistently everywhere without
42
- // the consumer needing to remember the override.
43
- "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none",
44
- "transition-colors focus:bg-accent focus:text-accent-foreground",
45
- "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
46
- className,
47
- )}
48
- {...props}
49
- />
50
- ));
51
- DropdownMenuItem.displayName = "DropdownMenuItem";
52
-
53
- export const DropdownMenuSeparator = forwardRef<
54
- HTMLDivElement,
55
- ComponentPropsWithRef<typeof DropdownMenuPrimitive.Separator>
56
- >(({ className, ...props }, ref) => (
57
- <DropdownMenuPrimitive.Separator
58
- ref={ref}
59
- className={cn("-mx-1 my-1 h-px bg-border", className)}
60
- {...props}
61
- />
62
- ));
63
- DropdownMenuSeparator.displayName = "DropdownMenuSeparator";
64
-
65
- export const DropdownMenuLabel = forwardRef<
66
- HTMLDivElement,
67
- ComponentPropsWithRef<typeof DropdownMenuPrimitive.Label>
68
- >(({ className, ...props }, ref) => (
69
- <DropdownMenuPrimitive.Label
70
- ref={ref}
71
- className={cn("px-2 py-1.5 text-sm font-semibold", className)}
72
- {...props}
73
- />
74
- ));
75
- DropdownMenuLabel.displayName = "DropdownMenuLabel";
@@ -1,34 +0,0 @@
1
- import * as React from "react";
2
- import * as PopoverPrimitive from "@radix-ui/react-popover";
3
-
4
- import { cn } from "@iloveagents/foundry-web-primitives";
5
-
6
- const Popover = PopoverPrimitive.Root;
7
- const PopoverTrigger = PopoverPrimitive.Trigger;
8
- const PopoverAnchor = PopoverPrimitive.Anchor;
9
-
10
- const PopoverContent = React.forwardRef<
11
- React.ElementRef<typeof PopoverPrimitive.Content>,
12
- React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
13
- >(({ className, align = "center", sideOffset = 8, ...props }, ref) => (
14
- <PopoverPrimitive.Portal>
15
- <PopoverPrimitive.Content
16
- ref={ref}
17
- align={align}
18
- sideOffset={sideOffset}
19
- className={cn(
20
- "z-50 rounded-2xl border border-border bg-popover p-3 text-popover-foreground shadow-xl outline-none",
21
- "data-[state=open]:animate-in data-[state=closed]:animate-out",
22
- "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
23
- "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
24
- "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
25
- "data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
26
- className,
27
- )}
28
- {...props}
29
- />
30
- </PopoverPrimitive.Portal>
31
- ));
32
- PopoverContent.displayName = PopoverPrimitive.Content.displayName;
33
-
34
- export { Popover, PopoverTrigger, PopoverAnchor, PopoverContent };
package/src/ui/select.tsx DELETED
@@ -1,15 +0,0 @@
1
- import * as React from "react";
2
-
3
- import { cn } from "@iloveagents/foundry-web-primitives";
4
-
5
- const selectClassName =
6
- "flex h-10 w-full rounded-xl border border-border bg-background px-3 py-2 text-sm outline-none transition focus:border-primary focus:ring-2 focus:ring-primary/15 disabled:cursor-not-allowed disabled:opacity-50";
7
-
8
- const Select = React.forwardRef<HTMLSelectElement, React.ComponentProps<"select">>(
9
- ({ className, ...props }, ref) => {
10
- return <select ref={ref} className={cn(selectClassName, className)} {...props} />;
11
- },
12
- );
13
- Select.displayName = "Select";
14
-
15
- export { Select, selectClassName };
@@ -1,30 +0,0 @@
1
- import * as React from "react";
2
- import * as TooltipPrimitive from "@radix-ui/react-tooltip";
3
-
4
- import { cn } from "@iloveagents/foundry-web-primitives";
5
-
6
- const TooltipProvider = TooltipPrimitive.Provider;
7
-
8
- const Tooltip = TooltipPrimitive.Root;
9
-
10
- const TooltipTrigger = TooltipPrimitive.Trigger;
11
-
12
- const TooltipContent = React.forwardRef<
13
- React.ElementRef<typeof TooltipPrimitive.Content>,
14
- React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
15
- >(({ className, sideOffset = 4, ...props }, ref) => (
16
- <TooltipPrimitive.Portal>
17
- <TooltipPrimitive.Content
18
- ref={ref}
19
- sideOffset={sideOffset}
20
- className={cn(
21
- "z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
22
- className,
23
- )}
24
- {...props}
25
- />
26
- </TooltipPrimitive.Portal>
27
- ));
28
- TooltipContent.displayName = TooltipPrimitive.Content.displayName;
29
-
30
- export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
package/src/vite-env.d.ts DELETED
@@ -1 +0,0 @@
1
- /// <reference types="vite/client" />