@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.
- package/README.md +14 -5
- package/dist/components/ag-ui-runtime-provider.d.ts +64 -0
- package/dist/components/ag-ui-runtime-provider.js +71 -0
- package/dist/components/app-brand.d.ts +6 -0
- package/dist/components/app-brand.js +13 -0
- package/dist/components/assistant-chat.d.ts +26 -0
- package/dist/components/assistant-chat.js +93 -0
- package/dist/components/chat-attachments.d.ts +25 -0
- package/dist/components/chat-attachments.js +20 -0
- package/dist/components/chat-bubble.d.ts +1 -0
- package/dist/components/chat-bubble.js +103 -0
- package/dist/components/chat-header.d.ts +2 -0
- package/dist/components/chat-header.js +16 -0
- package/dist/components/client-tool-executor.d.ts +12 -0
- package/dist/components/client-tool-executor.js +108 -0
- package/dist/components/collection-empty-state.d.ts +9 -0
- package/dist/components/collection-empty-state.js +6 -0
- package/dist/components/collection-filter-bar.d.ts +19 -0
- package/dist/components/collection-filter-bar.js +28 -0
- package/dist/components/collection-search-input.d.ts +6 -0
- package/dist/components/collection-search-input.js +8 -0
- package/dist/components/collection-skeleton.d.ts +6 -0
- package/dist/components/collection-skeleton.js +10 -0
- package/dist/components/collection-sort-menu.d.ts +10 -0
- package/dist/components/collection-sort-menu.js +9 -0
- package/dist/components/collection-surface.d.ts +8 -0
- package/dist/components/collection-surface.js +5 -0
- package/dist/components/collection-toolbar.d.ts +9 -0
- package/dist/components/collection-toolbar.js +5 -0
- package/dist/components/collection-view-toggle.d.ts +7 -0
- package/dist/components/collection-view-toggle.js +17 -0
- package/dist/components/composer-submit-bridge.d.ts +1 -0
- package/dist/components/composer-submit-bridge.js +15 -0
- package/dist/components/confirm-dialog.d.ts +11 -0
- package/dist/components/confirm-dialog.js +6 -0
- package/dist/components/confirmation-card.d.ts +14 -0
- package/dist/components/confirmation-card.js +12 -0
- package/dist/components/context-badges.d.ts +16 -0
- package/dist/components/context-badges.js +56 -0
- package/dist/components/context-bar.d.ts +41 -0
- package/dist/components/context-bar.js +123 -0
- package/dist/components/form-dialog.d.ts +12 -0
- package/dist/components/form-dialog.js +13 -0
- package/dist/components/global-selection-popover.d.ts +7 -0
- package/dist/components/global-selection-popover.js +167 -0
- package/dist/components/infinite-scroll-sentinel.d.ts +6 -0
- package/dist/components/infinite-scroll-sentinel.js +25 -0
- package/dist/components/json-viewer.d.ts +10 -0
- package/dist/components/json-viewer.js +80 -0
- package/dist/components/loading-indicator.d.ts +4 -0
- package/dist/components/loading-indicator.js +22 -0
- package/dist/components/markdown-text.d.ts +13 -0
- package/dist/components/markdown-text.js +221 -0
- package/dist/components/name-dialog.d.ts +16 -0
- package/dist/components/name-dialog.js +23 -0
- package/dist/components/selection-popover.d.ts +12 -0
- package/dist/components/selection-popover.js +111 -0
- package/dist/components/show-document-tool-ui.d.ts +1 -0
- package/dist/components/show-document-tool-ui.js +53 -0
- package/dist/components/sidebar.d.ts +4 -0
- package/dist/components/sidebar.js +904 -0
- package/dist/components/surface-card.d.ts +9 -0
- package/dist/components/surface-card.js +33 -0
- package/dist/components/theme-runtime-provider.d.ts +15 -0
- package/dist/components/theme-runtime-provider.js +48 -0
- package/dist/components/theme-toggle.d.ts +2 -0
- package/dist/components/theme-toggle.js +19 -0
- package/dist/components/tool-call-card.d.ts +27 -0
- package/dist/components/tool-call-card.js +13 -0
- package/dist/components/tool-fallback.d.ts +26 -0
- package/dist/components/tool-fallback.js +129 -0
- package/dist/components/tool-panel-layout.d.ts +10 -0
- package/dist/components/tool-panel-layout.js +12 -0
- package/dist/components/tool-panel.d.ts +1 -0
- package/dist/components/tool-panel.js +89 -0
- package/dist/components/tooltip-icon-button.d.ts +7 -0
- package/dist/components/tooltip-icon-button.js +9 -0
- package/dist/components/user-menu.d.ts +2 -0
- package/dist/components/user-menu.js +14 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.js +83 -0
- package/dist/lib/ag-ui-adapter.d.ts +29 -0
- package/dist/lib/ag-ui-adapter.js +373 -0
- package/dist/lib/app-store.d.ts +115 -0
- package/dist/lib/app-store.js +199 -0
- package/dist/lib/attachment-adapter.d.ts +20 -0
- package/dist/lib/attachment-adapter.js +99 -0
- package/dist/lib/auth-provider.d.ts +16 -0
- package/dist/lib/auth-provider.js +88 -0
- package/dist/lib/chat-bubble-store.d.ts +30 -0
- package/dist/lib/chat-bubble-store.js +23 -0
- package/dist/lib/chat-lifecycle-store.d.ts +10 -0
- package/{src/lib/chat-lifecycle-store.ts → dist/lib/chat-lifecycle-store.js} +3 -13
- package/dist/lib/client-tools.d.ts +11 -0
- package/dist/lib/client-tools.js +274 -0
- package/dist/lib/composer-submit-store.d.ts +12 -0
- package/dist/lib/composer-submit-store.js +17 -0
- package/dist/lib/dev-store.d.ts +38 -0
- package/dist/lib/dev-store.js +32 -0
- package/dist/lib/nav-config.d.ts +159 -0
- package/dist/lib/nav-config.js +162 -0
- package/dist/lib/nav-dnd.d.ts +8 -0
- package/dist/lib/nav-dnd.js +9 -0
- package/dist/lib/nav-store.d.ts +45 -0
- package/dist/lib/nav-store.js +49 -0
- package/dist/lib/selection-context.d.ts +21 -0
- package/dist/lib/selection-context.js +34 -0
- package/dist/lib/sidebar-store.d.ts +23 -0
- package/dist/lib/sidebar-store.js +98 -0
- package/dist/lib/theme-runtime.d.ts +130 -0
- package/dist/lib/theme-runtime.js +479 -0
- package/dist/lib/theme-store.d.ts +15 -0
- package/dist/lib/theme-store.js +53 -0
- package/dist/lib/tool-panel-store.d.ts +48 -0
- package/dist/lib/tool-panel-store.js +69 -0
- package/dist/lib/use-new-conversation.d.ts +8 -0
- package/dist/lib/use-new-conversation.js +62 -0
- package/dist/lib/use-page-tools.d.ts +27 -0
- package/{src/lib/use-page-tools.ts → dist/lib/use-page-tools.js} +11 -13
- package/dist/styles.css +142 -0
- package/dist/ui/collapsible.d.ts +5 -0
- package/{src/ui/collapsible.tsx → dist/ui/collapsible.js} +0 -2
- package/dist/ui/dialog.d.ts +15 -0
- package/dist/ui/dialog.js +25 -0
- package/dist/ui/dropdown-menu.d.ts +7 -0
- package/dist/ui/dropdown-menu.js +19 -0
- package/dist/ui/popover.d.ts +7 -0
- package/dist/ui/popover.js +10 -0
- package/dist/ui/select.d.ts +4 -0
- package/dist/ui/select.js +9 -0
- package/dist/ui/tooltip.d.ts +7 -0
- package/dist/ui/tooltip.js +10 -0
- package/package.json +31 -12
- package/AGENTS.md +0 -59
- package/CHANGELOG.md +0 -203
- package/CLAUDE.md +0 -1
- package/src/__tests__/no-spaces-imports.test.ts +0 -59
- package/src/__tests__/open-core-guards.test.ts +0 -307
- package/src/__tests__/theme-runtime.test.ts +0 -81
- package/src/__tests__/tool-fallback.test.tsx +0 -109
- package/src/components/__tests__/context-bar.test.ts +0 -112
- package/src/components/ag-ui-runtime-provider.tsx +0 -170
- package/src/components/app-brand.tsx +0 -38
- package/src/components/assistant-chat.tsx +0 -461
- package/src/components/chat-attachments.tsx +0 -68
- package/src/components/chat-bubble.tsx +0 -373
- package/src/components/chat-header.tsx +0 -44
- package/src/components/client-tool-executor.tsx +0 -230
- package/src/components/collection-empty-state.tsx +0 -37
- package/src/components/collection-filter-bar.tsx +0 -168
- package/src/components/collection-search-input.tsx +0 -41
- package/src/components/collection-skeleton.tsx +0 -55
- package/src/components/collection-sort-menu.tsx +0 -62
- package/src/components/collection-surface.tsx +0 -46
- package/src/components/collection-toolbar.tsx +0 -33
- package/src/components/collection-view-toggle.tsx +0 -61
- package/src/components/composer-submit-bridge.tsx +0 -19
- package/src/components/confirm-dialog.tsx +0 -49
- package/src/components/confirmation-card.tsx +0 -32
- package/src/components/context-badges.tsx +0 -136
- package/src/components/context-bar.tsx +0 -238
- package/src/components/form-dialog.tsx +0 -56
- package/src/components/global-selection-popover.tsx +0 -266
- package/src/components/infinite-scroll-sentinel.tsx +0 -48
- package/src/components/json-viewer.tsx +0 -232
- package/src/components/loading-indicator.tsx +0 -40
- package/src/components/markdown-text-citations.test.tsx +0 -108
- package/src/components/markdown-text.tsx +0 -508
- package/src/components/name-dialog.tsx +0 -87
- package/src/components/selection-popover.tsx +0 -156
- package/src/components/show-document-tool-ui.tsx +0 -90
- package/src/components/sidebar.test.tsx +0 -611
- package/src/components/sidebar.tsx +0 -1689
- package/src/components/surface-card.tsx +0 -45
- package/src/components/theme-runtime-provider.tsx +0 -93
- package/src/components/theme-toggle.tsx +0 -27
- package/src/components/tool-call-card.tsx +0 -126
- package/src/components/tool-fallback.tsx +0 -169
- package/src/components/tool-panel-layout.tsx +0 -36
- package/src/components/tool-panel.tsx +0 -233
- package/src/components/tooltip-icon-button.tsx +0 -28
- package/src/components/user-menu.tsx +0 -60
- package/src/index.ts +0 -185
- package/src/lib/__tests__/ag-ui-adapter.test.ts +0 -273
- package/src/lib/__tests__/app-store.test.ts +0 -405
- package/src/lib/__tests__/attachment-adapter.test.ts +0 -48
- package/src/lib/__tests__/chat-bubble-store.test.ts +0 -67
- package/src/lib/__tests__/client-tools.test.ts +0 -124
- package/src/lib/__tests__/dev-store.test.ts +0 -78
- package/src/lib/__tests__/nav-config.test.ts +0 -135
- package/src/lib/__tests__/nav-dnd.test.ts +0 -24
- package/src/lib/__tests__/nav-store.test.ts +0 -59
- package/src/lib/__tests__/selection-context.test.ts +0 -114
- package/src/lib/__tests__/sidebar-store.test.ts +0 -144
- package/src/lib/__tests__/theme-store.test.ts +0 -83
- package/src/lib/__tests__/tool-panel-store.test.ts +0 -61
- package/src/lib/__tests__/use-page-context.test.ts +0 -58
- package/src/lib/ag-ui-adapter.ts +0 -412
- package/src/lib/app-store.ts +0 -361
- package/src/lib/attachment-adapter.ts +0 -92
- package/src/lib/auth-provider.tsx +0 -144
- package/src/lib/chat-bubble-store.ts +0 -46
- package/src/lib/client-tools.ts +0 -293
- package/src/lib/composer-submit-store.ts +0 -32
- package/src/lib/dev-store.ts +0 -69
- package/src/lib/nav-config.ts +0 -329
- package/src/lib/nav-dnd.ts +0 -14
- package/src/lib/nav-store.ts +0 -89
- package/src/lib/selection-context.ts +0 -65
- package/src/lib/sidebar-store.ts +0 -113
- package/src/lib/theme-runtime.ts +0 -660
- package/src/lib/theme-store.ts +0 -67
- package/src/lib/tool-panel-store.ts +0 -121
- package/src/lib/use-new-conversation.test.tsx +0 -168
- package/src/lib/use-new-conversation.ts +0 -64
- package/src/ui/dialog.tsx +0 -109
- package/src/ui/dropdown-menu.tsx +0 -75
- package/src/ui/popover.tsx +0 -34
- package/src/ui/select.tsx +0 -15
- package/src/ui/tooltip.tsx +0 -30
- package/src/vite-env.d.ts +0 -1
- package/tsconfig.json +0 -16
- 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
|
-
|