@iloveagents/foundry-web-ui 0.3.0 → 0.4.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 (223) hide show
  1. package/README.md +14 -5
  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 +93 -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 +103 -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 +4 -0
  51. package/dist/components/loading-indicator.js +22 -0
  52. package/dist/components/markdown-text.d.ts +13 -0
  53. package/dist/components/markdown-text.js +221 -0
  54. package/dist/components/name-dialog.d.ts +16 -0
  55. package/dist/components/name-dialog.js +23 -0
  56. package/dist/components/selection-popover.d.ts +12 -0
  57. package/dist/components/selection-popover.js +111 -0
  58. package/dist/components/show-document-tool-ui.d.ts +1 -0
  59. package/dist/components/show-document-tool-ui.js +53 -0
  60. package/dist/components/sidebar.d.ts +4 -0
  61. package/dist/components/sidebar.js +904 -0
  62. package/dist/components/surface-card.d.ts +9 -0
  63. package/dist/components/surface-card.js +33 -0
  64. package/dist/components/theme-runtime-provider.d.ts +15 -0
  65. package/dist/components/theme-runtime-provider.js +48 -0
  66. package/dist/components/theme-toggle.d.ts +2 -0
  67. package/dist/components/theme-toggle.js +19 -0
  68. package/dist/components/tool-call-card.d.ts +27 -0
  69. package/dist/components/tool-call-card.js +13 -0
  70. package/dist/components/tool-fallback.d.ts +26 -0
  71. package/dist/components/tool-fallback.js +129 -0
  72. package/dist/components/tool-panel-layout.d.ts +10 -0
  73. package/dist/components/tool-panel-layout.js +12 -0
  74. package/dist/components/tool-panel.d.ts +1 -0
  75. package/dist/components/tool-panel.js +89 -0
  76. package/dist/components/tooltip-icon-button.d.ts +7 -0
  77. package/dist/components/tooltip-icon-button.js +9 -0
  78. package/dist/components/user-menu.d.ts +2 -0
  79. package/dist/components/user-menu.js +14 -0
  80. package/dist/index.d.ts +68 -0
  81. package/dist/index.js +83 -0
  82. package/dist/lib/ag-ui-adapter.d.ts +29 -0
  83. package/dist/lib/ag-ui-adapter.js +373 -0
  84. package/dist/lib/app-store.d.ts +115 -0
  85. package/dist/lib/app-store.js +199 -0
  86. package/dist/lib/attachment-adapter.d.ts +20 -0
  87. package/dist/lib/attachment-adapter.js +99 -0
  88. package/dist/lib/auth-provider.d.ts +16 -0
  89. package/dist/lib/auth-provider.js +88 -0
  90. package/dist/lib/chat-bubble-store.d.ts +30 -0
  91. package/dist/lib/chat-bubble-store.js +23 -0
  92. package/dist/lib/chat-lifecycle-store.d.ts +10 -0
  93. package/{src/lib/chat-lifecycle-store.ts → dist/lib/chat-lifecycle-store.js} +3 -13
  94. package/dist/lib/client-tools.d.ts +11 -0
  95. package/dist/lib/client-tools.js +274 -0
  96. package/dist/lib/composer-submit-store.d.ts +12 -0
  97. package/dist/lib/composer-submit-store.js +17 -0
  98. package/dist/lib/dev-store.d.ts +38 -0
  99. package/dist/lib/dev-store.js +32 -0
  100. package/dist/lib/nav-config.d.ts +159 -0
  101. package/dist/lib/nav-config.js +162 -0
  102. package/dist/lib/nav-dnd.d.ts +8 -0
  103. package/dist/lib/nav-dnd.js +9 -0
  104. package/dist/lib/nav-store.d.ts +45 -0
  105. package/dist/lib/nav-store.js +49 -0
  106. package/dist/lib/selection-context.d.ts +21 -0
  107. package/dist/lib/selection-context.js +34 -0
  108. package/dist/lib/sidebar-store.d.ts +23 -0
  109. package/dist/lib/sidebar-store.js +98 -0
  110. package/dist/lib/theme-runtime.d.ts +130 -0
  111. package/dist/lib/theme-runtime.js +479 -0
  112. package/dist/lib/theme-store.d.ts +15 -0
  113. package/dist/lib/theme-store.js +53 -0
  114. package/dist/lib/tool-panel-store.d.ts +48 -0
  115. package/dist/lib/tool-panel-store.js +69 -0
  116. package/dist/lib/use-new-conversation.d.ts +8 -0
  117. package/dist/lib/use-new-conversation.js +62 -0
  118. package/dist/lib/use-page-tools.d.ts +27 -0
  119. package/{src/lib/use-page-tools.ts → dist/lib/use-page-tools.js} +11 -13
  120. package/dist/styles.css +142 -0
  121. package/dist/ui/collapsible.d.ts +5 -0
  122. package/{src/ui/collapsible.tsx → dist/ui/collapsible.js} +0 -2
  123. package/dist/ui/dialog.d.ts +15 -0
  124. package/dist/ui/dialog.js +25 -0
  125. package/dist/ui/dropdown-menu.d.ts +7 -0
  126. package/dist/ui/dropdown-menu.js +19 -0
  127. package/dist/ui/popover.d.ts +7 -0
  128. package/dist/ui/popover.js +10 -0
  129. package/dist/ui/select.d.ts +4 -0
  130. package/dist/ui/select.js +9 -0
  131. package/dist/ui/tooltip.d.ts +7 -0
  132. package/dist/ui/tooltip.js +10 -0
  133. package/package.json +31 -12
  134. package/AGENTS.md +0 -59
  135. package/CHANGELOG.md +0 -203
  136. package/CLAUDE.md +0 -1
  137. package/src/__tests__/no-spaces-imports.test.ts +0 -59
  138. package/src/__tests__/open-core-guards.test.ts +0 -307
  139. package/src/__tests__/theme-runtime.test.ts +0 -81
  140. package/src/__tests__/tool-fallback.test.tsx +0 -109
  141. package/src/components/__tests__/context-bar.test.ts +0 -112
  142. package/src/components/ag-ui-runtime-provider.tsx +0 -170
  143. package/src/components/app-brand.tsx +0 -38
  144. package/src/components/assistant-chat.tsx +0 -461
  145. package/src/components/chat-attachments.tsx +0 -68
  146. package/src/components/chat-bubble.tsx +0 -373
  147. package/src/components/chat-header.tsx +0 -44
  148. package/src/components/client-tool-executor.tsx +0 -230
  149. package/src/components/collection-empty-state.tsx +0 -37
  150. package/src/components/collection-filter-bar.tsx +0 -168
  151. package/src/components/collection-search-input.tsx +0 -41
  152. package/src/components/collection-skeleton.tsx +0 -55
  153. package/src/components/collection-sort-menu.tsx +0 -62
  154. package/src/components/collection-surface.tsx +0 -46
  155. package/src/components/collection-toolbar.tsx +0 -33
  156. package/src/components/collection-view-toggle.tsx +0 -61
  157. package/src/components/composer-submit-bridge.tsx +0 -19
  158. package/src/components/confirm-dialog.tsx +0 -49
  159. package/src/components/confirmation-card.tsx +0 -32
  160. package/src/components/context-badges.tsx +0 -136
  161. package/src/components/context-bar.tsx +0 -238
  162. package/src/components/form-dialog.tsx +0 -56
  163. package/src/components/global-selection-popover.tsx +0 -266
  164. package/src/components/infinite-scroll-sentinel.tsx +0 -48
  165. package/src/components/json-viewer.tsx +0 -232
  166. package/src/components/loading-indicator.tsx +0 -40
  167. package/src/components/markdown-text-citations.test.tsx +0 -108
  168. package/src/components/markdown-text.tsx +0 -508
  169. package/src/components/name-dialog.tsx +0 -87
  170. package/src/components/selection-popover.tsx +0 -156
  171. package/src/components/show-document-tool-ui.tsx +0 -90
  172. package/src/components/sidebar.test.tsx +0 -611
  173. package/src/components/sidebar.tsx +0 -1689
  174. package/src/components/surface-card.tsx +0 -45
  175. package/src/components/theme-runtime-provider.tsx +0 -93
  176. package/src/components/theme-toggle.tsx +0 -27
  177. package/src/components/tool-call-card.tsx +0 -126
  178. package/src/components/tool-fallback.tsx +0 -169
  179. package/src/components/tool-panel-layout.tsx +0 -36
  180. package/src/components/tool-panel.tsx +0 -233
  181. package/src/components/tooltip-icon-button.tsx +0 -28
  182. package/src/components/user-menu.tsx +0 -60
  183. package/src/index.ts +0 -185
  184. package/src/lib/__tests__/ag-ui-adapter.test.ts +0 -273
  185. package/src/lib/__tests__/app-store.test.ts +0 -405
  186. package/src/lib/__tests__/attachment-adapter.test.ts +0 -48
  187. package/src/lib/__tests__/chat-bubble-store.test.ts +0 -67
  188. package/src/lib/__tests__/client-tools.test.ts +0 -124
  189. package/src/lib/__tests__/dev-store.test.ts +0 -78
  190. package/src/lib/__tests__/nav-config.test.ts +0 -135
  191. package/src/lib/__tests__/nav-dnd.test.ts +0 -24
  192. package/src/lib/__tests__/nav-store.test.ts +0 -59
  193. package/src/lib/__tests__/selection-context.test.ts +0 -114
  194. package/src/lib/__tests__/sidebar-store.test.ts +0 -144
  195. package/src/lib/__tests__/theme-store.test.ts +0 -83
  196. package/src/lib/__tests__/tool-panel-store.test.ts +0 -61
  197. package/src/lib/__tests__/use-page-context.test.ts +0 -58
  198. package/src/lib/ag-ui-adapter.ts +0 -412
  199. package/src/lib/app-store.ts +0 -361
  200. package/src/lib/attachment-adapter.ts +0 -92
  201. package/src/lib/auth-provider.tsx +0 -144
  202. package/src/lib/chat-bubble-store.ts +0 -46
  203. package/src/lib/client-tools.ts +0 -293
  204. package/src/lib/composer-submit-store.ts +0 -32
  205. package/src/lib/dev-store.ts +0 -69
  206. package/src/lib/nav-config.ts +0 -329
  207. package/src/lib/nav-dnd.ts +0 -14
  208. package/src/lib/nav-store.ts +0 -89
  209. package/src/lib/selection-context.ts +0 -65
  210. package/src/lib/sidebar-store.ts +0 -113
  211. package/src/lib/theme-runtime.ts +0 -660
  212. package/src/lib/theme-store.ts +0 -67
  213. package/src/lib/tool-panel-store.ts +0 -121
  214. package/src/lib/use-new-conversation.test.tsx +0 -168
  215. package/src/lib/use-new-conversation.ts +0 -64
  216. package/src/ui/dialog.tsx +0 -109
  217. package/src/ui/dropdown-menu.tsx +0 -75
  218. package/src/ui/popover.tsx +0 -34
  219. package/src/ui/select.tsx +0 -15
  220. package/src/ui/tooltip.tsx +0 -30
  221. package/src/vite-env.d.ts +0 -1
  222. package/tsconfig.json +0 -16
  223. 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" />