@assistant-ui/react 0.15.16 → 0.15.17
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 +3 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -2
- package/dist/mcp-apps/McpAppRenderer.d.ts +6 -1
- package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
- package/dist/mcp-apps/McpAppRenderer.js +19 -12
- package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/mcp-apps/McpAppsRemoteHost.d.ts.map +1 -1
- package/dist/mcp-apps/McpAppsRemoteHost.js +20 -17
- package/dist/mcp-apps/McpAppsRemoteHost.js.map +1 -1
- package/dist/mcp-apps/app-frame.d.ts.map +1 -1
- package/dist/mcp-apps/app-frame.js +189 -94
- package/dist/mcp-apps/app-frame.js.map +1 -1
- package/dist/mcp-apps/index.d.ts +2 -2
- package/dist/model-context/frame/useAssistantFrameHost.d.ts +1 -1
- package/dist/model-context/frame/useAssistantFrameHost.js +10 -11
- package/dist/model-context/frame/useAssistantFrameHost.js.map +1 -1
- package/dist/model-context/makeAssistantVisible.js +1 -1
- package/dist/model-context/makeAssistantVisible.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarCopy.js +3 -3
- package/dist/primitives/actionBar/ActionBarCopy.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarExportMarkdown.js +3 -3
- package/dist/primitives/actionBar/ActionBarExportMarkdown.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +3 -3
- package/dist/primitives/actionBar/ActionBarFeedbackNegative.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +3 -3
- package/dist/primitives/actionBar/ActionBarFeedbackPositive.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarRoot.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarStopSpeaking.js +3 -3
- package/dist/primitives/actionBar/ActionBarStopSpeaking.js.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreContent.js.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreItem.js.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreRoot.js.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreSeparator.js.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreTrigger.js.map +1 -1
- package/dist/primitives/actionBarMore/scope.d.ts +2 -2
- package/dist/primitives/actionBarMore/scope.d.ts.map +1 -1
- package/dist/primitives/actionBarMore/scope.js.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalAnchor.js.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalContent.js +1 -1
- package/dist/primitives/assistantModal/AssistantModalContent.js.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalRoot.js.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalTrigger.js.map +1 -1
- package/dist/primitives/assistantModal/scope.d.ts +2 -2
- package/dist/primitives/assistantModal/scope.d.ts.map +1 -1
- package/dist/primitives/assistantModal/scope.js.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerRoot.js.map +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.js +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
- package/dist/primitives/composer/ComposerDictationTranscript.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +1 -3
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/ComposerQuote.js +5 -5
- package/dist/primitives/composer/ComposerQuote.js.map +1 -1
- package/dist/primitives/composer/ComposerRoot.js +3 -3
- package/dist/primitives/composer/ComposerRoot.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopover.d.ts +5 -0
- package/dist/primitives/composer/trigger/TriggerPopover.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopover.js +157 -130
- package/dist/primitives/composer/trigger/TriggerPopover.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverBack.js +3 -3
- package/dist/primitives/composer/trigger/TriggerPopoverBack.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverCategories.js +4 -4
- package/dist/primitives/composer/trigger/TriggerPopoverCategories.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverItems.js +4 -4
- package/dist/primitives/composer/trigger/TriggerPopoverItems.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts +2 -0
- package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverResource.js +63 -61
- package/dist/primitives/composer/trigger/TriggerPopoverResource.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverRootContext.d.ts +2 -0
- package/dist/primitives/composer/trigger/TriggerPopoverRootContext.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverRootContext.js.map +1 -1
- package/dist/primitives/composer/trigger/detectTrigger.d.ts +21 -1
- package/dist/primitives/composer/trigger/detectTrigger.d.ts.map +1 -0
- package/dist/primitives/composer/trigger/detectTrigger.js +17 -3
- package/dist/primitives/composer/trigger/detectTrigger.js.map +1 -1
- package/dist/primitives/composer/trigger/index.d.ts +3 -1
- package/dist/primitives/composer/trigger/index.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/index.js.map +1 -1
- package/dist/primitives/composer/trigger/triggerDetectionResource.d.ts +3 -4
- package/dist/primitives/composer/trigger/triggerDetectionResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerDetectionResource.js +15 -14
- package/dist/primitives/composer/trigger/triggerDetectionResource.js.map +1 -1
- package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerSelectionResource.js +18 -19
- package/dist/primitives/composer/trigger/triggerSelectionResource.js.map +1 -1
- package/dist/primitives/composer.d.ts +3 -2
- package/dist/primitives/composer.js.map +1 -1
- package/dist/primitives/error/ErrorMessage.js.map +1 -1
- package/dist/primitives/message/MessageIf.js.map +1 -1
- package/dist/primitives/message/MessageParts.js.map +1 -1
- package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
- package/dist/primitives/message/MessageRoot.js +4 -4
- package/dist/primitives/message/MessageRoot.js.map +1 -1
- package/dist/primitives/messagePart/MessagePartText.js.map +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js +3 -3
- package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js.map +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js +3 -3
- package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js.map +1 -1
- package/dist/primitives/thread/ThreadIf.js.map +1 -1
- package/dist/primitives/thread/ThreadViewport.js +3 -3
- package/dist/primitives/thread/ThreadViewport.js.map +1 -1
- package/dist/primitives/thread/ThreadViewportFooter.js +3 -3
- package/dist/primitives/thread/ThreadViewportFooter.js.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
- package/dist/primitives/threadList/ThreadListNew.js +3 -3
- package/dist/primitives/threadList/ThreadListNew.js.map +1 -1
- package/dist/primitives/threadListFocusGroup.d.ts +2 -1
- package/dist/primitives/threadListFocusGroup.d.ts.map +1 -1
- package/dist/primitives/threadListFocusGroup.js +2 -2
- package/dist/primitives/threadListFocusGroup.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemRoot.js +3 -3
- package/dist/primitives/threadListItem/ThreadListItemRoot.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemTrigger.js +1 -1
- package/dist/primitives/threadListItem/ThreadListItemTrigger.js.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.js +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.js.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreItem.js.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreRoot.js +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreRoot.js.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreSeparator.js.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.js +1 -2
- package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.js.map +1 -1
- package/dist/primitives/threadListItemMore/scope.d.ts +2 -2
- package/dist/primitives/threadListItemMore/scope.d.ts.map +1 -1
- package/dist/primitives/threadListItemMore/scope.js.map +1 -1
- package/dist/sandbox-host/SandboxHost.d.ts.map +1 -1
- package/dist/sandbox-host/SandboxHost.js +137 -70
- package/dist/sandbox-host/SandboxHost.js.map +1 -1
- package/dist/unstable/useLiveCompletionAdapter.js +3 -1
- package/dist/unstable/useLiveCompletionAdapter.js.map +1 -1
- package/dist/unstable/useSlashCommandAdapter.js +41 -27
- package/dist/unstable/useSlashCommandAdapter.js.map +1 -1
- package/dist/utils/Primitive.d.ts +2 -2
- package/dist/utils/Primitive.js +2 -2
- package/dist/utils/Primitive.js.map +1 -1
- package/dist/utils/createActionButton.js +3 -3
- package/dist/utils/createActionButton.js.map +1 -1
- package/dist/utils/hooks/useOnResizeContent.js +1 -1
- package/dist/utils/hooks/useOnResizeContent.js.map +1 -1
- package/dist/utils/hooks/useOnScrollToBottom.js +1 -1
- package/dist/utils/hooks/useOnScrollToBottom.js.map +1 -1
- package/dist/utils/smooth/SmoothContext.d.ts +24 -0
- package/dist/utils/smooth/SmoothContext.d.ts.map +1 -1
- package/dist/utils/smooth/useSmooth.js +1 -1
- package/dist/utils/smooth/useSmooth.js.map +1 -1
- package/dist/utils/useToolArgsFieldStatus.d.ts +5 -1
- package/package.json +11 -19
- package/src/index.ts +9 -1
- package/src/mcp-apps/McpAppRenderer.test.tsx +133 -4
- package/src/mcp-apps/McpAppRenderer.tsx +28 -21
- package/src/mcp-apps/McpAppsRemoteHost.concurrent.test.tsx +215 -0
- package/src/mcp-apps/McpAppsRemoteHost.ts +28 -12
- package/src/mcp-apps/app-frame.test.tsx +102 -0
- package/src/mcp-apps/app-frame.tsx +17 -4
- package/src/mcp-apps/index.ts +1 -0
- package/src/model-context/frame/AssistantFrame.test.ts +3 -3
- package/src/model-context/frame/SPEC_AssistantFrame.md +1 -1
- package/src/model-context/frame/useAssistantFrameHost.ts +2 -2
- package/src/model-context/makeAssistantVisible.tsx +1 -1
- package/src/primitives/actionBar/ActionBarCopy.tsx +1 -1
- package/src/primitives/actionBar/ActionBarExportMarkdown.tsx +1 -1
- package/src/primitives/actionBar/ActionBarFeedbackNegative.tsx +1 -1
- package/src/primitives/actionBar/ActionBarFeedbackPositive.tsx +1 -1
- package/src/primitives/actionBar/ActionBarStopSpeaking.tsx +1 -1
- package/src/primitives/actionBarMore/scope.ts +2 -2
- package/src/primitives/assistantModal/AssistantModalContent.tsx +1 -1
- package/src/primitives/assistantModal/scope.ts +2 -2
- package/src/primitives/composer/ComposerAttachmentDropzone.tsx +1 -1
- package/src/primitives/composer/ComposerInput.test.tsx +2 -1
- package/src/primitives/composer/ComposerInput.tsx +3 -3
- package/src/primitives/composer/ComposerQuote.tsx +1 -1
- package/src/primitives/composer/ComposerRoot.tsx +1 -1
- package/src/primitives/composer/trigger/TriggerPopover.tsx +16 -1
- package/src/primitives/composer/trigger/TriggerPopoverBack.tsx +1 -1
- package/src/primitives/composer/trigger/TriggerPopoverCategories.tsx +1 -1
- package/src/primitives/composer/trigger/TriggerPopoverItems.tsx +1 -1
- package/src/primitives/composer/trigger/TriggerPopoverResource.ts +4 -1
- package/src/primitives/composer/trigger/TriggerPopoverRootContext.tsx +2 -0
- package/src/primitives/composer/trigger/detectTrigger.test.ts +88 -5
- package/src/primitives/composer/trigger/detectTrigger.ts +50 -9
- package/src/primitives/composer/trigger/index.ts +1 -0
- package/src/primitives/composer/trigger/triggerDetectionResource.ts +10 -7
- package/src/primitives/composer/trigger/triggerSelectionResource.test.ts +15 -3
- package/src/primitives/composer/trigger/triggerSelectionResource.ts +1 -4
- package/src/primitives/composer.ts +1 -0
- package/src/primitives/message/MessageRoot.tsx +1 -1
- package/src/primitives/selectionToolbar/SelectionToolbarQuote.tsx +1 -1
- package/src/primitives/selectionToolbar/SelectionToolbarRoot.tsx +1 -1
- package/src/primitives/thread/ThreadViewport.tsx +1 -1
- package/src/primitives/thread/ThreadViewportFooter.tsx +1 -1
- package/src/primitives/thread/useThreadViewportAutoScroll.ts +1 -1
- package/src/primitives/threadList/ThreadListNew.tsx +1 -1
- package/src/primitives/threadListFocusGroup.ts +2 -2
- package/src/primitives/threadListItem/ThreadListItemRoot.tsx +1 -1
- package/src/primitives/threadListItem/ThreadListItemTrigger.tsx +1 -1
- package/src/primitives/threadListItemMore/ThreadListItemMoreContent.tsx +1 -1
- package/src/primitives/threadListItemMore/ThreadListItemMoreRoot.tsx +1 -1
- package/src/primitives/threadListItemMore/ThreadListItemMoreTrigger.tsx +2 -2
- package/src/primitives/threadListItemMore/scope.ts +2 -2
- package/src/sandbox-host/SandboxHost.test.tsx +45 -1
- package/src/sandbox-host/SandboxHost.tsx +16 -3
- package/src/tests/RemoteThreadListRuntime.adapterProvider.test.tsx +31 -1
- package/src/unstable/useLiveCompletionAdapter.test.tsx +60 -1
- package/src/unstable/useLiveCompletionAdapter.ts +4 -1
- package/src/unstable/useSlashCommandAdapter.test.tsx +141 -0
- package/src/unstable/useSlashCommandAdapter.ts +52 -17
- package/src/utils/Primitive.tsx +2 -2
- package/src/utils/createActionButton.tsx +1 -1
- package/src/utils/hooks/useOnResizeContent.ts +1 -1
- package/src/utils/hooks/useOnScrollToBottom.ts +1 -1
- package/src/utils/smooth/useSmooth.ts +1 -1
- package/dist/legacy-runtime/runtime/MessagePartRuntime.d.ts +0 -3
- package/dist/legacy-runtime/runtime/MessagePartRuntime.js +0 -2
- package/dist/legacy-runtime/runtime/MessageRuntime.d.ts +0 -3
- package/dist/legacy-runtime/runtime/MessageRuntime.js +0 -2
- package/dist/legacy-runtime/runtime/ThreadListRuntime.d.ts +0 -3
- package/dist/legacy-runtime/runtime/ThreadListRuntime.js +0 -2
- package/src/legacy-runtime/runtime/MessagePartRuntime.ts +0 -2
- package/src/legacy-runtime/runtime/MessageRuntime.ts +0 -3
- package/src/legacy-runtime/runtime/ThreadListRuntime.ts +0 -4
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
forwardRef,
|
|
7
7
|
} from "react";
|
|
8
8
|
import { Direction, DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
|
|
9
|
-
import { composeEventHandlers } from "
|
|
9
|
+
import { composeEventHandlers } from "radix-ui/internal";
|
|
10
10
|
import type { WithRenderPropProps } from "../../utils/Primitive";
|
|
11
11
|
import { DropdownMenuRenderContent } from "../dropdownMenuRenderPrimitives";
|
|
12
12
|
import { useThreadListItemFocus } from "../threadListFocusGroup";
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
useContext,
|
|
9
9
|
} from "react";
|
|
10
10
|
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
|
|
11
|
-
import { useControllableState } from "
|
|
11
|
+
import { useControllableState } from "radix-ui/internal";
|
|
12
12
|
import { type ScopedProps, useDropdownMenuScope } from "./scope";
|
|
13
13
|
|
|
14
14
|
const ThreadListItemMoreSetOpenContext = createContext<
|
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
Direction,
|
|
6
6
|
type DropdownMenu as DropdownMenuPrimitive,
|
|
7
7
|
} from "radix-ui";
|
|
8
|
-
import { useComposedRefs } from "
|
|
9
|
-
import { composeEventHandlers } from "
|
|
8
|
+
import { useComposedRefs } from "radix-ui/internal";
|
|
9
|
+
import { composeEventHandlers } from "radix-ui/internal";
|
|
10
10
|
import type { WithRenderPropProps } from "../../utils/Primitive";
|
|
11
11
|
import { DropdownMenuRenderTrigger } from "../dropdownMenuRenderPrimitives";
|
|
12
12
|
import { useThreadListItemFocus } from "../threadListFocusGroup";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
|
|
2
|
-
import type {
|
|
2
|
+
import type { Context } from "radix-ui/internal";
|
|
3
3
|
|
|
4
4
|
export const useDropdownMenuScope: ReturnType<
|
|
5
5
|
typeof DropdownMenuPrimitive.createDropdownMenuScope
|
|
6
6
|
> = DropdownMenuPrimitive.createDropdownMenuScope();
|
|
7
7
|
|
|
8
|
-
export type ScopedProps<P> = P & { __scopeThreadListItemMore?: Scope };
|
|
8
|
+
export type ScopedProps<P> = P & { __scopeThreadListItemMore?: Context.Scope };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
|
-
import { act } from "react";
|
|
2
|
+
import { act, startTransition, Suspense } from "react";
|
|
3
3
|
import { createRoot, type Root } from "react-dom/client";
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
5
|
|
|
@@ -318,4 +318,48 @@ describe("SandboxHost", () => {
|
|
|
318
318
|
});
|
|
319
319
|
expect(rendered.dispose).toHaveBeenCalledTimes(1);
|
|
320
320
|
});
|
|
321
|
+
|
|
322
|
+
it("keeps bridge options scoped to committed renders", async () => {
|
|
323
|
+
let resolveRender!: (frame: ReturnType<typeof fakeRendered>) => void;
|
|
324
|
+
renderHtmlMock.mockReturnValue(
|
|
325
|
+
new Promise((resolve) => {
|
|
326
|
+
resolveRender = resolve;
|
|
327
|
+
}),
|
|
328
|
+
);
|
|
329
|
+
const rendered = fakeRendered();
|
|
330
|
+
const bridge = { onMessage: vi.fn(), dispose: vi.fn() };
|
|
331
|
+
const createBridgeA = vi.fn(() => bridge);
|
|
332
|
+
const createBridgeB = vi.fn(() => bridge);
|
|
333
|
+
const interruptedRender = vi.fn();
|
|
334
|
+
const pending = new Promise<never>(() => {});
|
|
335
|
+
const Block = () => {
|
|
336
|
+
interruptedRender();
|
|
337
|
+
throw pending;
|
|
338
|
+
};
|
|
339
|
+
const view = (createBridge: typeof createBridgeA, blocked: boolean) => (
|
|
340
|
+
<Suspense fallback={null}>
|
|
341
|
+
<SandboxHost
|
|
342
|
+
content={{ html: "" }}
|
|
343
|
+
contentKey="k"
|
|
344
|
+
createBridge={createBridge}
|
|
345
|
+
/>
|
|
346
|
+
{blocked ? <Block /> : null}
|
|
347
|
+
</Suspense>
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
await act(async () => {
|
|
351
|
+
root.render(view(createBridgeA, false));
|
|
352
|
+
});
|
|
353
|
+
act(() => {
|
|
354
|
+
startTransition(() => root.render(view(createBridgeB, true)));
|
|
355
|
+
});
|
|
356
|
+
await vi.waitFor(() => expect(interruptedRender).toHaveBeenCalled());
|
|
357
|
+
await act(async () => {
|
|
358
|
+
resolveRender(rendered);
|
|
359
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
expect(createBridgeA).toHaveBeenCalledTimes(1);
|
|
363
|
+
expect(createBridgeB).not.toHaveBeenCalled();
|
|
364
|
+
});
|
|
321
365
|
});
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type CSSProperties,
|
|
5
|
+
useEffect,
|
|
6
|
+
useLayoutEffect,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
} from "react";
|
|
4
10
|
import {
|
|
5
11
|
type RenderedFrame,
|
|
6
12
|
SafeContentFrame,
|
|
@@ -81,8 +87,15 @@ export function SandboxHost({
|
|
|
81
87
|
undefined,
|
|
82
88
|
);
|
|
83
89
|
|
|
84
|
-
const liveRef = useRef<LiveSnapshot>(
|
|
85
|
-
|
|
90
|
+
const liveRef = useRef<LiveSnapshot>({
|
|
91
|
+
content,
|
|
92
|
+
sandbox,
|
|
93
|
+
createBridge,
|
|
94
|
+
onError,
|
|
95
|
+
});
|
|
96
|
+
useLayoutEffect(() => {
|
|
97
|
+
liveRef.current = { content, sandbox, createBridge, onError };
|
|
98
|
+
}, [content, sandbox, createBridge, onError]);
|
|
86
99
|
|
|
87
100
|
useEffect(() => {
|
|
88
101
|
const container = containerRef.current;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
2
|
|
|
3
3
|
import { act, render, waitFor } from "@testing-library/react";
|
|
4
|
-
import type
|
|
4
|
+
import { useState, type FC, type PropsWithChildren } from "react";
|
|
5
5
|
import { describe, expect, it } from "vitest";
|
|
6
6
|
import {
|
|
7
7
|
RuntimeAdapterProvider,
|
|
@@ -107,6 +107,36 @@ describe("RemoteThreadListAdapter.unstable_Provider", () => {
|
|
|
107
107
|
expect(capture.adapters?.history).toBe(dummyHistory);
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
+
it("picks up a post-mount useAdapters bag", async () => {
|
|
111
|
+
const capture: { adapters: CapturedAdapters } = { adapters: null };
|
|
112
|
+
const firstHistory: ThreadHistoryAdapter = {
|
|
113
|
+
load: async () => ({ messages: [] }),
|
|
114
|
+
append: async () => {},
|
|
115
|
+
};
|
|
116
|
+
const secondHistory: ThreadHistoryAdapter = {
|
|
117
|
+
load: async () => ({ messages: [] }),
|
|
118
|
+
append: async () => {},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
let bump: (() => void) | undefined;
|
|
122
|
+
const adapter = makeAdapter({
|
|
123
|
+
unstable_useAdapters: function useTestAdapters() {
|
|
124
|
+
const [swapped, setSwapped] = useState(false);
|
|
125
|
+
bump = () => setSwapped(true);
|
|
126
|
+
return { history: swapped ? secondHistory : firstHistory };
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
await renderAndWaitForBinder(adapter, capture);
|
|
131
|
+
expect(capture.adapters?.history).toBe(firstHistory);
|
|
132
|
+
|
|
133
|
+
await act(async () => {
|
|
134
|
+
bump!();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expect(capture.adapters?.history).toBe(secondHistory);
|
|
138
|
+
});
|
|
139
|
+
|
|
110
140
|
it("picks up a swapped Provider on re-render", async () => {
|
|
111
141
|
const capture: { adapters: CapturedAdapters } = { adapters: null };
|
|
112
142
|
const firstHistory: ThreadHistoryAdapter = {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** @vitest-environment jsdom */
|
|
2
|
-
import {
|
|
2
|
+
import { startTransition, Suspense } from "react";
|
|
3
|
+
import { act, render, renderHook } from "@testing-library/react";
|
|
3
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
5
|
import type { Unstable_TriggerItem } from "@assistant-ui/core";
|
|
5
6
|
import { unstable_useLiveCompletionAdapter } from "./useLiveCompletionAdapter";
|
|
@@ -230,6 +231,64 @@ describe("unstable_useLiveCompletionAdapter", () => {
|
|
|
230
231
|
expect(result.current.adapter.search!("alice")).toEqual([item("alice")]);
|
|
231
232
|
});
|
|
232
233
|
|
|
234
|
+
it("keeps pending queries scoped to the committed fetcher", async () => {
|
|
235
|
+
const fetcherA = vi.fn(async () => [item("workspace-a")]);
|
|
236
|
+
const fetcherB = vi.fn(async () => [item("workspace-b")]);
|
|
237
|
+
const interruptedRender = vi.fn();
|
|
238
|
+
const pending = new Promise<never>(() => {});
|
|
239
|
+
let adapter!: ReturnType<
|
|
240
|
+
typeof unstable_useLiveCompletionAdapter
|
|
241
|
+
>["adapter"];
|
|
242
|
+
const Harness = ({
|
|
243
|
+
fetcher,
|
|
244
|
+
cacheKey,
|
|
245
|
+
blocked,
|
|
246
|
+
}: {
|
|
247
|
+
fetcher: typeof fetcherA;
|
|
248
|
+
cacheKey: string;
|
|
249
|
+
blocked: boolean;
|
|
250
|
+
}) => {
|
|
251
|
+
const result = unstable_useLiveCompletionAdapter({
|
|
252
|
+
fetcher,
|
|
253
|
+
cacheKey,
|
|
254
|
+
debounceMs: 50,
|
|
255
|
+
});
|
|
256
|
+
if (blocked) {
|
|
257
|
+
interruptedRender();
|
|
258
|
+
throw pending;
|
|
259
|
+
}
|
|
260
|
+
adapter = result.adapter;
|
|
261
|
+
return null;
|
|
262
|
+
};
|
|
263
|
+
const view = (
|
|
264
|
+
fetcher: typeof fetcherA,
|
|
265
|
+
cacheKey: string,
|
|
266
|
+
blocked: boolean,
|
|
267
|
+
) => (
|
|
268
|
+
<Suspense fallback={null}>
|
|
269
|
+
<Harness fetcher={fetcher} cacheKey={cacheKey} blocked={blocked} />
|
|
270
|
+
</Suspense>
|
|
271
|
+
);
|
|
272
|
+
const rendered = render(view(fetcherA, "workspace-a", false));
|
|
273
|
+
|
|
274
|
+
await act(async () => {
|
|
275
|
+
adapter.search!("alice");
|
|
276
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
277
|
+
});
|
|
278
|
+
act(() => {
|
|
279
|
+
startTransition(() =>
|
|
280
|
+
rendered.rerender(view(fetcherB, "workspace-b", true)),
|
|
281
|
+
);
|
|
282
|
+
});
|
|
283
|
+
expect(interruptedRender).toHaveBeenCalled();
|
|
284
|
+
await act(async () => {
|
|
285
|
+
await vi.advanceTimersByTimeAsync(50);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
expect(fetcherA).toHaveBeenCalledOnce();
|
|
289
|
+
expect(fetcherB).not.toHaveBeenCalled();
|
|
290
|
+
});
|
|
291
|
+
|
|
233
292
|
it("drops a pending result when only the cache key changes", async () => {
|
|
234
293
|
let resolveFirst!: (items: readonly Unstable_TriggerItem[]) => void;
|
|
235
294
|
const first = new Promise<readonly Unstable_TriggerItem[]>((resolve) => {
|
|
@@ -74,7 +74,10 @@ export function unstable_useLiveCompletionAdapter(
|
|
|
74
74
|
const [isLoading, setIsLoading] = useState(false);
|
|
75
75
|
|
|
76
76
|
const fetcherRef = useRef(fetcher);
|
|
77
|
-
|
|
77
|
+
// The debounce timer must only observe fetchers from committed renders.
|
|
78
|
+
useLayoutEffect(() => {
|
|
79
|
+
fetcherRef.current = fetcher;
|
|
80
|
+
}, [fetcher]);
|
|
78
81
|
|
|
79
82
|
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
80
83
|
const tokenRef = useRef(0);
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { act, renderHook } from "@testing-library/react";
|
|
4
|
+
import { startTransition, Suspense, type PropsWithChildren } from "react";
|
|
5
|
+
import { describe, expect, it, vi } from "vitest";
|
|
6
|
+
import {
|
|
7
|
+
unstable_useSlashCommandAdapter,
|
|
8
|
+
type Unstable_SlashCommand,
|
|
9
|
+
} from "./useSlashCommandAdapter";
|
|
10
|
+
|
|
11
|
+
const command = (
|
|
12
|
+
label: string,
|
|
13
|
+
execute: () => void,
|
|
14
|
+
): Unstable_SlashCommand => ({
|
|
15
|
+
id: "run",
|
|
16
|
+
label,
|
|
17
|
+
execute,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe("unstable_useSlashCommandAdapter", () => {
|
|
21
|
+
it("keeps commands scoped to committed renders", () => {
|
|
22
|
+
const executeA = vi.fn();
|
|
23
|
+
const executeB = vi.fn();
|
|
24
|
+
const commandsA = [command("Workspace A", executeA)];
|
|
25
|
+
const commandsB = [command("Workspace B", executeB)];
|
|
26
|
+
const renderedLabel = vi.fn();
|
|
27
|
+
const interruptedRender = vi.fn();
|
|
28
|
+
const pending = new Promise<never>(() => {});
|
|
29
|
+
let blocked = false;
|
|
30
|
+
|
|
31
|
+
const Blocker = () => {
|
|
32
|
+
if (blocked) {
|
|
33
|
+
interruptedRender();
|
|
34
|
+
throw pending;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
};
|
|
38
|
+
const Wrapper = ({ children }: PropsWithChildren) => (
|
|
39
|
+
<Suspense fallback={null}>
|
|
40
|
+
{children}
|
|
41
|
+
<Blocker />
|
|
42
|
+
</Suspense>
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const { result, rerender } = renderHook(
|
|
46
|
+
({ commands }) => {
|
|
47
|
+
renderedLabel(commands[0]?.label);
|
|
48
|
+
return unstable_useSlashCommandAdapter({ commands });
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
initialProps: { commands: commandsA },
|
|
52
|
+
wrapper: Wrapper,
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
act(() => {
|
|
57
|
+
blocked = true;
|
|
58
|
+
startTransition(() => rerender({ commands: commandsB }));
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
expect(interruptedRender).toHaveBeenCalled();
|
|
62
|
+
expect(renderedLabel).toHaveBeenCalledWith("Workspace B");
|
|
63
|
+
expect(result.current.adapter.search?.("")).toEqual([
|
|
64
|
+
{ id: "run", type: "command", label: "Workspace A" },
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
result.current.action.onExecute({
|
|
68
|
+
id: "run",
|
|
69
|
+
type: "command",
|
|
70
|
+
label: "Workspace A",
|
|
71
|
+
});
|
|
72
|
+
expect(executeA).toHaveBeenCalledOnce();
|
|
73
|
+
expect(executeB).not.toHaveBeenCalled();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("updates search while keeping actions stable after commands commit", () => {
|
|
77
|
+
const executeA = vi.fn();
|
|
78
|
+
const executeB = vi.fn();
|
|
79
|
+
const commandsA = [command("Workspace A", executeA)];
|
|
80
|
+
const commandsB = [command("Workspace B", executeB)];
|
|
81
|
+
const { result, rerender } = renderHook(
|
|
82
|
+
({ commands }) => unstable_useSlashCommandAdapter({ commands }),
|
|
83
|
+
{ initialProps: { commands: commandsA } },
|
|
84
|
+
);
|
|
85
|
+
const initial = result.current;
|
|
86
|
+
|
|
87
|
+
rerender({ commands: commandsB });
|
|
88
|
+
|
|
89
|
+
expect(result.current.adapter).not.toBe(initial.adapter);
|
|
90
|
+
expect(result.current.action).toBe(initial.action);
|
|
91
|
+
expect(result.current.adapter.search?.("")).toEqual([
|
|
92
|
+
{ id: "run", type: "command", label: "Workspace B" },
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
result.current.action.onExecute({
|
|
96
|
+
id: "run",
|
|
97
|
+
type: "command",
|
|
98
|
+
label: "Workspace B",
|
|
99
|
+
});
|
|
100
|
+
expect(executeA).not.toHaveBeenCalled();
|
|
101
|
+
expect(executeB).toHaveBeenCalledOnce();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("matches the displayed label for a command with no explicit label", () => {
|
|
105
|
+
const { result } = renderHook(() =>
|
|
106
|
+
unstable_useSlashCommandAdapter({
|
|
107
|
+
commands: [{ id: "summarize", execute: vi.fn() }],
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
expect(result.current.adapter.search?.("/")).toEqual([
|
|
112
|
+
{ id: "summarize", type: "command", label: "/summarize" },
|
|
113
|
+
]);
|
|
114
|
+
expect(result.current.adapter.search?.("sum")).toEqual([
|
|
115
|
+
{ id: "summarize", type: "command", label: "/summarize" },
|
|
116
|
+
]);
|
|
117
|
+
expect(result.current.adapter.search?.("nope")).toEqual([]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("keeps the adapter stable for equivalent inline commands", () => {
|
|
121
|
+
const executeA = vi.fn();
|
|
122
|
+
const executeB = vi.fn();
|
|
123
|
+
const { result, rerender } = renderHook(
|
|
124
|
+
({ execute }) =>
|
|
125
|
+
unstable_useSlashCommandAdapter({
|
|
126
|
+
commands: [{ id: "run", label: "Run", execute }],
|
|
127
|
+
}),
|
|
128
|
+
{ initialProps: { execute: executeA } },
|
|
129
|
+
);
|
|
130
|
+
const initialAdapter = result.current.adapter;
|
|
131
|
+
|
|
132
|
+
rerender({ execute: executeB });
|
|
133
|
+
|
|
134
|
+
expect(result.current.adapter).toBe(initialAdapter);
|
|
135
|
+
act(() =>
|
|
136
|
+
result.current.action.onExecute(result.current.adapter.search!("")[0]!),
|
|
137
|
+
);
|
|
138
|
+
expect(executeA).not.toHaveBeenCalled();
|
|
139
|
+
expect(executeB).toHaveBeenCalledOnce();
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useMemo, useRef } from "react";
|
|
3
|
+
import { useLayoutEffect, useMemo, useRef } from "react";
|
|
4
4
|
import type {
|
|
5
5
|
Unstable_TriggerAdapter,
|
|
6
6
|
Unstable_TriggerItem,
|
|
@@ -61,34 +61,51 @@ export function unstable_useSlashCommandAdapter(
|
|
|
61
61
|
const { commands, removeOnExecute } = options;
|
|
62
62
|
|
|
63
63
|
const commandsRef = useRef(commands);
|
|
64
|
-
|
|
64
|
+
const committedItemsRef = useRef<readonly Unstable_TriggerItem[]>(undefined);
|
|
65
|
+
const nextItems = commands.map(toItem);
|
|
66
|
+
// A referential cache, never state: `items` is always structurally equal to
|
|
67
|
+
// `nextItems`, so the render-time read cannot change what the adapter shows.
|
|
68
|
+
const items = areTriggerItemsEqual(committedItemsRef.current, nextItems)
|
|
69
|
+
? committedItemsRef.current
|
|
70
|
+
: nextItems;
|
|
65
71
|
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
// The adapter's callbacks must only observe commands from committed renders.
|
|
73
|
+
useLayoutEffect(() => {
|
|
74
|
+
commandsRef.current = commands;
|
|
75
|
+
committedItemsRef.current = items;
|
|
76
|
+
}, [commands, items]);
|
|
77
|
+
|
|
78
|
+
const adapter = useMemo<Unstable_TriggerAdapter>(
|
|
79
|
+
() => ({
|
|
68
80
|
categories: () => [],
|
|
69
81
|
categoryItems: () => [],
|
|
70
82
|
search: (query: string) => {
|
|
71
83
|
const lower = query.toLowerCase();
|
|
72
|
-
return
|
|
73
|
-
.filter((c) => matchesQuery(c, lower))
|
|
74
|
-
.map(toItem);
|
|
84
|
+
return items.filter((item) => matchesQuery(item, lower));
|
|
75
85
|
},
|
|
76
|
-
}
|
|
86
|
+
}),
|
|
87
|
+
[items],
|
|
88
|
+
);
|
|
77
89
|
|
|
78
|
-
|
|
90
|
+
const action = useMemo<Unstable_SlashCommandAction>(
|
|
91
|
+
() => ({
|
|
79
92
|
onExecute: (item) => {
|
|
80
93
|
commandsRef.current.find((c) => c.id === item.id)?.execute();
|
|
81
94
|
},
|
|
82
95
|
...(removeOnExecute !== undefined ? { removeOnExecute } : {}),
|
|
83
|
-
}
|
|
96
|
+
}),
|
|
97
|
+
[removeOnExecute],
|
|
98
|
+
);
|
|
84
99
|
|
|
85
|
-
|
|
100
|
+
return useMemo(
|
|
101
|
+
() => ({
|
|
86
102
|
adapter,
|
|
87
103
|
action,
|
|
88
104
|
...(options.iconMap ? { iconMap: options.iconMap } : {}),
|
|
89
105
|
...(options.fallbackIcon ? { fallbackIcon: options.fallbackIcon } : {}),
|
|
90
|
-
}
|
|
91
|
-
|
|
106
|
+
}),
|
|
107
|
+
[adapter, action, options.iconMap, options.fallbackIcon],
|
|
108
|
+
);
|
|
92
109
|
}
|
|
93
110
|
|
|
94
111
|
function toItem(cmd: Unstable_SlashCommand): Unstable_TriggerItem {
|
|
@@ -101,10 +118,28 @@ function toItem(cmd: Unstable_SlashCommand): Unstable_TriggerItem {
|
|
|
101
118
|
};
|
|
102
119
|
}
|
|
103
120
|
|
|
104
|
-
function matchesQuery(
|
|
121
|
+
function matchesQuery(item: Unstable_TriggerItem, lower: string): boolean {
|
|
105
122
|
if (!lower) return true;
|
|
106
|
-
if (
|
|
107
|
-
if (
|
|
108
|
-
if (
|
|
123
|
+
if (item.id.toLowerCase().includes(lower)) return true;
|
|
124
|
+
if (item.label.toLowerCase().includes(lower)) return true;
|
|
125
|
+
if (item.description?.toLowerCase().includes(lower)) return true;
|
|
109
126
|
return false;
|
|
110
127
|
}
|
|
128
|
+
|
|
129
|
+
function areTriggerItemsEqual(
|
|
130
|
+
previous: readonly Unstable_TriggerItem[] | undefined,
|
|
131
|
+
next: readonly Unstable_TriggerItem[],
|
|
132
|
+
): previous is readonly Unstable_TriggerItem[] {
|
|
133
|
+
if (!previous || previous.length !== next.length) return false;
|
|
134
|
+
|
|
135
|
+
return previous.every((item, index) => {
|
|
136
|
+
const nextItem = next[index];
|
|
137
|
+
return (
|
|
138
|
+
nextItem !== undefined &&
|
|
139
|
+
item.id === nextItem.id &&
|
|
140
|
+
item.label === nextItem.label &&
|
|
141
|
+
item.description === nextItem.description &&
|
|
142
|
+
item.metadata?.icon === nextItem.metadata?.icon
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
}
|
package/src/utils/Primitive.tsx
CHANGED
|
@@ -11,10 +11,10 @@ import {
|
|
|
11
11
|
forwardRef,
|
|
12
12
|
isValidElement,
|
|
13
13
|
} from "react";
|
|
14
|
-
import { Primitive as RadixPrimitive } from "
|
|
14
|
+
import { Primitive as RadixPrimitive } from "radix-ui/internal";
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Thin wrapper around
|
|
17
|
+
* Thin wrapper around Radix `Primitive` that adds `render` prop support.
|
|
18
18
|
*
|
|
19
19
|
* When `render` is provided, it is converted to the equivalent `asChild` pattern:
|
|
20
20
|
* render={<Comp props />} + children → asChild + <Comp props>{children}</Comp>
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
type MouseEventHandler,
|
|
6
6
|
} from "react";
|
|
7
7
|
import { Primitive } from "./Primitive";
|
|
8
|
-
import { composeEventHandlers } from "
|
|
8
|
+
import { composeEventHandlers } from "radix-ui/internal";
|
|
9
9
|
|
|
10
10
|
type ActionButtonCallback<TProps> = (
|
|
11
11
|
props: TProps,
|
|
@@ -8,7 +8,7 @@ import type {
|
|
|
8
8
|
TextMessagePart,
|
|
9
9
|
MessagePartState,
|
|
10
10
|
} from "@assistant-ui/core";
|
|
11
|
-
import { useCallbackRef } from "
|
|
11
|
+
import { useCallbackRef } from "radix-ui/internal";
|
|
12
12
|
import { useMediaQuery } from "../hooks/useMediaQuery";
|
|
13
13
|
import { useSmoothStatusStore } from "./SmoothContext";
|
|
14
14
|
import { writableStore } from "../../context/ReadonlyStore";
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import { ThreadListRuntime, ThreadListState } from "@assistant-ui/core";
|
|
2
|
-
import { ThreadListRuntimeCoreBinding, ThreadListRuntimeImpl } from "@assistant-ui/core/internal";
|
|
3
|
-
export { type ThreadListRuntime, type ThreadListRuntimeCoreBinding, ThreadListRuntimeImpl, type ThreadListState };
|