@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,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
-