@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,48 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { FileAttachmentAdapter } from "../attachment-adapter";
3
-
4
- const adapter = new FileAttachmentAdapter();
5
-
6
- describe("FileAttachmentAdapter.add", () => {
7
- it("uses browser-supplied MIME type", async () => {
8
- const file = new File(["x"], "photo.png", { type: "image/png" });
9
- const result = await adapter.add({ file });
10
- expect(result.contentType).toBe("image/png");
11
- expect(result.type).toBe("image");
12
- });
13
-
14
- it("resolves MIME from extension when browser type is empty", async () => {
15
- const file = new File(["x"], "report.docx", { type: "" });
16
- const result = await adapter.add({ file });
17
- expect(result.contentType).toBe(
18
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
19
- );
20
- expect(result.type).toBe("document");
21
- });
22
-
23
- it("falls back to application/octet-stream for unknown extensions", async () => {
24
- const file = new File(["x"], "data.xyz", { type: "" });
25
- const result = await adapter.add({ file });
26
- expect(result.contentType).toBe("application/octet-stream");
27
- });
28
-
29
- it("throws for files exceeding 20 MB", async () => {
30
- const big = new File(["x"], "big.png", { type: "image/png" });
31
- Object.defineProperty(big, "size", { value: 20 * 1024 * 1024 + 1 });
32
- await expect(adapter.add({ file: big })).rejects.toThrow("File too large");
33
- });
34
-
35
- it("accepts a file at exactly 20 MB", async () => {
36
- const exact = new File(["x"], "ok.png", { type: "image/png" });
37
- Object.defineProperty(exact, "size", { value: 20 * 1024 * 1024 });
38
- await expect(adapter.add({ file: exact })).resolves.toBeDefined();
39
- });
40
-
41
- it("classifies images vs documents correctly", async () => {
42
- const image = new File(["x"], "pic.jpg", { type: "image/jpeg" });
43
- const pdf = new File(["x"], "doc.pdf", { type: "application/pdf" });
44
-
45
- expect((await adapter.add({ file: image })).type).toBe("image");
46
- expect((await adapter.add({ file: pdf })).type).toBe("document");
47
- });
48
- });
@@ -1,67 +0,0 @@
1
- import { beforeEach, describe, expect, it } from "vitest";
2
- import { useChatBubbleStore } from "../chat-bubble-store";
3
-
4
- const reset = () =>
5
- useChatBubbleStore.setState({ isOpen: false, isExpanded: false, showPagePanel: true });
6
- const store = () => useChatBubbleStore.getState();
7
-
8
- describe("chat-bubble-store", () => {
9
- beforeEach(reset);
10
-
11
- it("starts closed", () => {
12
- expect(store().isOpen).toBe(false);
13
- expect(store().isExpanded).toBe(false);
14
- });
15
-
16
- it("open sets isOpen to true", () => {
17
- store().open();
18
- expect(store().isOpen).toBe(true);
19
- expect(store().isExpanded).toBe(false);
20
- });
21
-
22
- it("close resets both states", () => {
23
- store().expand();
24
- store().close();
25
- expect(store().isOpen).toBe(false);
26
- expect(store().isExpanded).toBe(false);
27
- });
28
-
29
- it("toggle flips isOpen", () => {
30
- store().toggle();
31
- expect(store().isOpen).toBe(true);
32
- store().toggle();
33
- expect(store().isOpen).toBe(false);
34
- });
35
-
36
- it("expand hides bubble and shows expanded layout", () => {
37
- store().open();
38
- store().expand();
39
- expect(store().isOpen).toBe(false);
40
- expect(store().isExpanded).toBe(true);
41
- });
42
-
43
- it("collapse returns to bubble mode", () => {
44
- store().expand();
45
- store().collapse();
46
- expect(store().isOpen).toBe(true);
47
- expect(store().isExpanded).toBe(false);
48
- });
49
-
50
- it("expand defaults to showing page panel", () => {
51
- store().expand();
52
- expect(store().showPagePanel).toBe(true);
53
- store().togglePagePanel();
54
- expect(store().showPagePanel).toBe(false);
55
- store().collapse();
56
- store().expand();
57
- expect(store().showPagePanel).toBe(true);
58
- });
59
-
60
- it("togglePagePanel flips showPagePanel", () => {
61
- expect(store().showPagePanel).toBe(true);
62
- store().togglePagePanel();
63
- expect(store().showPagePanel).toBe(false);
64
- store().togglePagePanel();
65
- expect(store().showPagePanel).toBe(true);
66
- });
67
- });
@@ -1,124 +0,0 @@
1
- // @vitest-environment jsdom
2
- import { beforeEach, describe, expect, it, vi } from "vitest";
3
-
4
- // Mock matchMedia before importing modules that use theme-store
5
- vi.stubGlobal(
6
- "matchMedia",
7
- vi.fn().mockReturnValue({
8
- matches: false,
9
- addEventListener: vi.fn(),
10
- removeEventListener: vi.fn(),
11
- }),
12
- );
13
-
14
- const { clientToolRegistry } = await import("@iloveagents/foundry-agent");
15
- await import("../client-tools"); // triggers registerGlobalTools()
16
-
17
- const registry = () => clientToolRegistry.getState();
18
-
19
- describe("Global tool registration", () => {
20
- it("registers all global tools with ui_ prefix", () => {
21
- const names = registry()
22
- .getActiveSchemas()
23
- .map((s) => s.name)
24
- .sort();
25
- expect(names).toEqual([
26
- "ui_confirm",
27
- "ui_get_context",
28
- "ui_highlight",
29
- "ui_navigate",
30
- "ui_open_panel",
31
- "ui_select_item",
32
- "ui_set_theme",
33
- "ui_update_content",
34
- ]);
35
- });
36
-
37
- it("each schema has required fields", () => {
38
- for (const schema of registry().getActiveSchemas()) {
39
- expect(schema.name).toBeTruthy();
40
- expect(schema.description).toBeTruthy();
41
- expect(schema.parameters).toBeDefined();
42
- }
43
- });
44
- });
45
-
46
- describe("isRegistered", () => {
47
- it("returns true for global tools", () => {
48
- expect(registry().isRegistered("ui_navigate")).toBe(true);
49
- expect(registry().isRegistered("ui_set_theme")).toBe(true);
50
- });
51
-
52
- it("returns false for unknown tools", () => {
53
- expect(registry().isRegistered("unknown_tool")).toBe(false);
54
- });
55
- });
56
-
57
- describe("executeTool", () => {
58
- it("ui_set_theme returns success", async () => {
59
- const result = await registry().executeTool("ui_set_theme", '{"mode":"dark"}');
60
- expect(JSON.parse(result).status).toBe("success");
61
- });
62
-
63
- it("ui_confirm returns pending", async () => {
64
- const result = await registry().executeTool("ui_confirm", '{"action":"Delete"}');
65
- expect(JSON.parse(result).status).toBe("pending");
66
- });
67
-
68
- it("ui_update_content updates multiple fields", async () => {
69
- const result = await registry().executeTool(
70
- "ui_update_content",
71
- '{"fields":{"title":"Test","priority":"High"}}',
72
- );
73
- const parsed = JSON.parse(result);
74
- expect(parsed.status).toBe("success");
75
- expect(parsed.fields).toEqual({ title: "Test", priority: "High" });
76
- });
77
-
78
- it("unknown tool returns error", async () => {
79
- const result = await registry().executeTool("unknown", "{}");
80
- expect(JSON.parse(result).error).toContain("Unknown client tool");
81
- });
82
- });
83
-
84
- describe("Page tools", () => {
85
- beforeEach(() => {
86
- registry().clearPageTools();
87
- });
88
-
89
- it("registers and clears page tools", () => {
90
- const before = registry().getActiveSchemas().length;
91
-
92
- registry().registerPageTools([
93
- {
94
- name: "page_custom_tool",
95
- description: "Page tool",
96
- parameters: { type: "object", properties: {} },
97
- execute: async () => JSON.stringify({ ok: true }),
98
- },
99
- ]);
100
-
101
- expect(registry().getActiveSchemas().length).toBe(before + 1);
102
- expect(registry().isRegistered("page_custom_tool")).toBe(true);
103
-
104
- registry().clearPageTools();
105
- expect(registry().isRegistered("page_custom_tool")).toBe(false);
106
- });
107
-
108
- it("page tools can be executed", async () => {
109
- registry().registerPageTools([
110
- {
111
- name: "page_my_tool",
112
- description: "Test",
113
- parameters: { type: "object", properties: {} },
114
- execute: async (argsJson) => {
115
- const { value } = JSON.parse(argsJson);
116
- return JSON.stringify({ received: value });
117
- },
118
- },
119
- ]);
120
-
121
- const result = await registry().executeTool("page_my_tool", '{"value":"hello"}');
122
- expect(JSON.parse(result).received).toBe("hello");
123
- });
124
- });
@@ -1,78 +0,0 @@
1
- import { describe, it, expect, beforeEach } from "vitest";
2
- import { useDevStore } from "../dev-store.ts";
3
- import type { DevToolCall } from "../dev-store.ts";
4
-
5
- function makeToolCall(id: string): DevToolCall {
6
- return {
7
- id,
8
- name: `tool_${id}`,
9
- args: "{}",
10
- isClientSide: false,
11
- timestamp: Date.now(),
12
- };
13
- }
14
-
15
- describe("useDevStore", () => {
16
- beforeEach(() => {
17
- useDevStore.setState({
18
- lastRequest: null,
19
- lastRequestAt: null,
20
- toolCalls: [],
21
- events: [],
22
- });
23
- });
24
-
25
- describe("captureRequest", () => {
26
- it("stores request payload and timestamp", () => {
27
- useDevStore.getState().captureRequest({ thread_id: "t1" });
28
- expect(useDevStore.getState().lastRequest).toEqual({ thread_id: "t1" });
29
- expect(useDevStore.getState().lastRequestAt).toBeTypeOf("number");
30
- });
31
-
32
- it("resets tool calls and events on fresh turn", () => {
33
- useDevStore.getState().logEvent("TEXT_DELTA");
34
- useDevStore.getState().captureRequest({ thread_id: "t1" });
35
- expect(useDevStore.getState().toolCalls).toEqual([]);
36
- expect(useDevStore.getState().events).toEqual([]);
37
- });
38
-
39
- it("preserves tool calls on follow-up (multi-turn)", () => {
40
- useDevStore.getState().addToolCall(makeToolCall("tc1"));
41
- useDevStore.getState().captureRequest({ thread_id: "t1" });
42
- expect(useDevStore.getState().toolCalls.length).toBe(1);
43
- });
44
- });
45
-
46
- describe("addToolCall", () => {
47
- it("appends tool call", () => {
48
- useDevStore.getState().addToolCall(makeToolCall("tc1"));
49
- useDevStore.getState().addToolCall(makeToolCall("tc2"));
50
- expect(useDevStore.getState().toolCalls.length).toBe(2);
51
- });
52
- });
53
-
54
- describe("updateToolCall", () => {
55
- it("updates args and result by id", () => {
56
- useDevStore.getState().addToolCall(makeToolCall("tc1"));
57
- useDevStore.getState().updateToolCall("tc1", { result: { temp: 22 } });
58
- expect(useDevStore.getState().toolCalls[0].result).toEqual({ temp: 22 });
59
- });
60
- });
61
-
62
- describe("logEvent", () => {
63
- it("appends events", () => {
64
- useDevStore.getState().logEvent("TEXT_DELTA", "hello");
65
- expect(useDevStore.getState().events.length).toBe(1);
66
- expect(useDevStore.getState().events[0].type).toBe("TEXT_DELTA");
67
- });
68
-
69
- it("caps at MAX_EVENTS (50)", () => {
70
- for (let i = 0; i < 60; i++) {
71
- useDevStore.getState().logEvent(`EVENT_${i}`);
72
- }
73
- expect(useDevStore.getState().events.length).toBe(50);
74
- // Should keep the last 50 (events 10-59)
75
- expect(useDevStore.getState().events[0].type).toBe("EVENT_10");
76
- });
77
- });
78
- });
@@ -1,135 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { findNavItem, DEFAULT_NAV_CONFIG, type NavGroup } from "../nav-config";
3
-
4
- describe("findNavItem", () => {
5
- it("finds item in default config", () => {
6
- const result = findNavItem(DEFAULT_NAV_CONFIG, "/");
7
- expect(result).not.toBeNull();
8
- expect(result!.group).toBe("Pages");
9
- expect(result!.item.label).toBe("Chat");
10
- expect(result!.item.description).toBeTruthy();
11
- });
12
-
13
- it("returns null for unknown path", () => {
14
- expect(findNavItem(DEFAULT_NAV_CONFIG, "/unknown")).toBeNull();
15
- });
16
-
17
- it("finds item across multiple groups", () => {
18
- const config: NavGroup[] = [
19
- ...DEFAULT_NAV_CONFIG,
20
- {
21
- label: "Contracts",
22
- items: [
23
- {
24
- to: "/contracts/123",
25
- label: "Acme NDA",
26
- icon: DEFAULT_NAV_CONFIG[0].items[0].icon,
27
- description: "Review NDA",
28
- meta: { contractId: "123" },
29
- },
30
- ],
31
- },
32
- ];
33
-
34
- const result = findNavItem(config, "/contracts/123");
35
- expect(result).not.toBeNull();
36
- expect(result!.group).toBe("Contracts");
37
- expect(result!.item.label).toBe("Acme NDA");
38
- expect(result!.item.meta).toEqual({ contractId: "123" });
39
- });
40
-
41
- it("returns first match when paths duplicate across groups", () => {
42
- const config: NavGroup[] = [
43
- {
44
- label: "A",
45
- items: [{ to: "/x", label: "X-A", icon: DEFAULT_NAV_CONFIG[0].items[0].icon }],
46
- },
47
- {
48
- label: "B",
49
- items: [{ to: "/x", label: "X-B", icon: DEFAULT_NAV_CONFIG[0].items[0].icon }],
50
- },
51
- ];
52
- const result = findNavItem(config, "/x");
53
- expect(result!.group).toBe("A");
54
- });
55
-
56
- it("all default config items are findable", () => {
57
- for (const group of DEFAULT_NAV_CONFIG) {
58
- for (const item of group.items) {
59
- const result = findNavItem(DEFAULT_NAV_CONFIG, item.to);
60
- expect(result).not.toBeNull();
61
- expect(result!.item.to).toBe(item.to);
62
- }
63
- }
64
- });
65
-
66
- it("finds deeply nested child items", () => {
67
- const icon = DEFAULT_NAV_CONFIG[0].items[0].icon;
68
- const config: NavGroup[] = [
69
- {
70
- label: "Workspaces",
71
- items: [
72
- {
73
- to: "/spaces/root",
74
- label: "Workspace",
75
- icon,
76
- children: [
77
- {
78
- to: "/spaces/folder",
79
- label: "Folder",
80
- icon,
81
- children: [
82
- {
83
- to: "/spaces/entity",
84
- label: "Entity",
85
- icon,
86
- meta: { spacesEntityId: "entity" },
87
- },
88
- ],
89
- },
90
- ],
91
- },
92
- ],
93
- },
94
- ];
95
-
96
- const result = findNavItem(config, "/spaces/entity");
97
- expect(result).not.toBeNull();
98
- expect(result!.item.label).toBe("Entity");
99
- expect(result!.item.meta).toEqual({ spacesEntityId: "entity" });
100
- });
101
-
102
- it("uses the deepest prefix match for nested routes", () => {
103
- const icon = DEFAULT_NAV_CONFIG[0].items[0].icon;
104
- const config: NavGroup[] = [
105
- {
106
- label: "Workspaces",
107
- items: [
108
- {
109
- to: "/spaces/root",
110
- label: "Workspace",
111
- icon,
112
- children: [
113
- {
114
- to: "/spaces/root/views",
115
- label: "Views",
116
- icon,
117
- children: [
118
- {
119
- to: "/spaces/root/views/contradictions",
120
- label: "Contradictions",
121
- icon,
122
- },
123
- ],
124
- },
125
- ],
126
- },
127
- ],
128
- },
129
- ];
130
-
131
- const result = findNavItem(config, "/spaces/root/views/contradictions/details");
132
- expect(result).not.toBeNull();
133
- expect(result!.item.label).toBe("Contradictions");
134
- });
135
- });
@@ -1,24 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { getContainerDropZoneLabel, hasAnyDropTarget, hasExternalFilesData } from "../nav-dnd";
3
-
4
- describe("nav-dnd helpers", () => {
5
- it("detects external file drags from Files type alone", () => {
6
- expect(
7
- hasExternalFilesData({
8
- types: ["Files"],
9
- files: { length: 0 },
10
- }),
11
- ).toBe(true);
12
- });
13
-
14
- it("treats file-only targets as active drop targets", () => {
15
- expect(hasAnyDropTarget(false, true)).toBe(true);
16
- expect(hasAnyDropTarget(true, false)).toBe(true);
17
- expect(hasAnyDropTarget(false, false)).toBe(false);
18
- });
19
-
20
- it("uses upload copy for external file container drops", () => {
21
- expect(getContainerDropZoneLabel(true)).toBe("Upload to root");
22
- expect(getContainerDropZoneLabel(false)).toBe("Move to root");
23
- });
24
- });
@@ -1,59 +0,0 @@
1
- import { describe, it, expect, beforeEach } from "vitest";
2
- import { useNavStore } from "../nav-store.ts";
3
- import { DEFAULT_NAV_CONFIG } from "../nav-config.ts";
4
- import type { NavGroup } from "../nav-config.ts";
5
-
6
- const testGroup: NavGroup = {
7
- label: "TestGroup",
8
- items: [{ to: "/test", label: "Test", icon: {} as NavGroup["items"][0]["icon"] }],
9
- };
10
-
11
- describe("useNavStore", () => {
12
- beforeEach(() => {
13
- useNavStore.setState({ config: DEFAULT_NAV_CONFIG });
14
- });
15
-
16
- it("initializes with DEFAULT_NAV_CONFIG", () => {
17
- expect(useNavStore.getState().config).toBe(DEFAULT_NAV_CONFIG);
18
- });
19
-
20
- describe("setConfig", () => {
21
- it("replaces entire config", () => {
22
- useNavStore.getState().setConfig([testGroup]);
23
- expect(useNavStore.getState().config).toEqual([testGroup]);
24
- });
25
- });
26
-
27
- describe("addGroup", () => {
28
- it("appends new group", () => {
29
- const before = useNavStore.getState().config.length;
30
- useNavStore.getState().addGroup(testGroup);
31
- expect(useNavStore.getState().config.length).toBe(before + 1);
32
- expect(useNavStore.getState().config.at(-1)).toEqual(testGroup);
33
- });
34
-
35
- it("replaces existing group with same label (preserves order)", () => {
36
- useNavStore.getState().addGroup(testGroup);
37
- const updated: NavGroup = { ...testGroup, description: "updated" };
38
- useNavStore.getState().addGroup(updated);
39
-
40
- const matches = useNavStore.getState().config.filter((g) => g.label === "TestGroup");
41
- expect(matches.length).toBe(1);
42
- expect(matches[0].description).toBe("updated");
43
- });
44
- });
45
-
46
- describe("removeGroup", () => {
47
- it("removes group by label", () => {
48
- useNavStore.getState().addGroup(testGroup);
49
- useNavStore.getState().removeGroup("TestGroup");
50
- expect(useNavStore.getState().config.find((g) => g.label === "TestGroup")).toBeUndefined();
51
- });
52
-
53
- it("no-op for non-existent label", () => {
54
- const before = useNavStore.getState().config.length;
55
- useNavStore.getState().removeGroup("NoSuchGroup");
56
- expect(useNavStore.getState().config.length).toBe(before);
57
- });
58
- });
59
- });
@@ -1,114 +0,0 @@
1
- import { afterEach, describe, expect, it } from "vitest";
2
- import {
3
- registerSelectionContextResolver,
4
- resolveSelectionContext,
5
- type SelectionContextResolver,
6
- } from "../selection-context.ts";
7
-
8
- const unregisters: Array<() => void> = [];
9
-
10
- function register(resolver: SelectionContextResolver) {
11
- const unregister = registerSelectionContextResolver(resolver);
12
- unregisters.push(unregister);
13
- return unregister;
14
- }
15
-
16
- function input(text = "selected text") {
17
- const node = document.createElement("span");
18
- const range = document.createRange();
19
- range.selectNodeContents(node);
20
- return {
21
- text,
22
- anchorNode: node,
23
- focusNode: node,
24
- range,
25
- sourcePage: "Current page",
26
- };
27
- }
28
-
29
- describe("selection-context", () => {
30
- afterEach(() => {
31
- for (const unregister of unregisters.splice(0)) unregister();
32
- document.body.innerHTML = "";
33
- });
34
-
35
- it("falls back to a plain text selection context", () => {
36
- const resolved = resolveSelectionContext(input("hello"));
37
-
38
- expect(resolved.item).toMatchObject({
39
- type: "selection",
40
- label: "hello",
41
- payload: { kind: "text", text: "hello" },
42
- persistence: "ephemeral",
43
- });
44
- expect(resolved.supportsModify).toBeUndefined();
45
- });
46
-
47
- it("lets modules resolve richer selection context and opt into Modify", () => {
48
- register({
49
- id: "test-rich-selection",
50
- resolve: (selection) => ({
51
- item: {
52
- type: "ref",
53
- label: `Field · ${selection.text}`,
54
- payload: {
55
- kind: "ref",
56
- refType: "block",
57
- refId: "entity:block",
58
- preview: selection.text,
59
- meta: { selectionScope: "atom", targetPath: "$.blocks[0].field" },
60
- },
61
- sourcePage: "/spaces/entity#block-block",
62
- persistence: "ephemeral",
63
- },
64
- supportsModify: true,
65
- }),
66
- });
67
-
68
- const resolved = resolveSelectionContext(input("cell"));
69
-
70
- expect(resolved.item).toMatchObject({
71
- type: "ref",
72
- label: "Field · cell",
73
- payload: {
74
- kind: "ref",
75
- refType: "block",
76
- refId: "entity:block",
77
- meta: { targetPath: "$.blocks[0].field" },
78
- },
79
- });
80
- expect(resolved.supportsModify).toBe(true);
81
- });
82
-
83
- it("uses resolver priority before falling through to lower priority resolvers", () => {
84
- register({
85
- id: "low",
86
- priority: 1,
87
- resolve: (selection) => ({
88
- item: {
89
- type: "selection",
90
- label: `low ${selection.text}`,
91
- payload: { kind: "text", text: selection.text },
92
- sourcePage: selection.sourcePage,
93
- persistence: "ephemeral",
94
- },
95
- }),
96
- });
97
- register({
98
- id: "high",
99
- priority: 100,
100
- resolve: (selection) => ({
101
- item: {
102
- type: "selection",
103
- label: `high ${selection.text}`,
104
- payload: { kind: "text", text: selection.text },
105
- sourcePage: selection.sourcePage,
106
- persistence: "ephemeral",
107
- },
108
- }),
109
- });
110
-
111
- expect(resolveSelectionContext(input("x")).item.label).toBe("high x");
112
- });
113
- });
114
-