Merge remote-tracking branch 'origin/master' into worktree/web-multimodal-image-input
# Conflicts: # apps/cli/src/web.ts # apps/web/tests/smoke-fixture.e2e.ts # docs/architecture.i18n.yaml # packages/client/connection/src/client/fixture.ts # packages/client/runtime/src/client/sessions/conversation.ts # packages/client/runtime/src/client/sessions/session.ts # packages/client/ui-conversation/package.json # packages/client/ui-conversation/src/client/contract/slots.ts # packages/client/ui-conversation/src/client/index.ts # packages/client/ui-conversation/src/client/service.ts # packages/client/ui-conversation/src/client/skeleton/ConversationRoot.tsx # packages/client/ui-conversation/tests/apply-inject.spec.tsx # packages/client/ui-conversation/tests/service-orchestration.spec.ts # packages/host/runtime/src/api-proxy.ts # packages/host/runtime/src/boot.ts # packages/host/webserver/tests/webserver.spec.ts # pnpm-lock.yaml
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// @vitest-environment jsdom
|
||||
/**
|
||||
* Real tsdown artifact shape: lib/client.js hands off through
|
||||
* window.DSHClientProxy.loadPlugin, resolves externals through the injected
|
||||
* window.__ModuleLoader__.load, resolves externals through the injected
|
||||
* require, returns the export surface (apply + inject), and a mounted apply
|
||||
* registers both view tabs into a real SlotsService ring. Skips when dist/ is
|
||||
* not built (`pnpm --filter @deepseek-ai/dsh-client-ui-trajectory bundle`).
|
||||
@@ -15,7 +15,7 @@ import { SlotsService } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
const PLUGIN_ID = '@deepseek-ai/dsh-client-ui-trajectory'
|
||||
|
||||
interface Handoff { id: string; factory: (require: (spec: string) => unknown) => Record<string, unknown> }
|
||||
type Win = { DSHClientProxy?: { loadPlugin(h: Handoff): void } }
|
||||
type Win = { __ModuleLoader__?: { load(h: Handoff): void } }
|
||||
|
||||
function readBundle(): string | undefined {
|
||||
try {
|
||||
@@ -28,7 +28,7 @@ function readBundle(): string | undefined {
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
delete (window as Win).DSHClientProxy
|
||||
delete (window as Win).__ModuleLoader__
|
||||
for (const el of document.querySelectorAll('style')) el.remove()
|
||||
})
|
||||
|
||||
@@ -37,7 +37,7 @@ describe('tsdown client artifact', () => {
|
||||
|
||||
async function loadArtifact() {
|
||||
let handoff: Handoff | undefined
|
||||
;(window as Win).DSHClientProxy = { loadPlugin: (h) => { handoff = h } }
|
||||
;(window as Win).__ModuleLoader__ = { load: (h) => { handoff = h } }
|
||||
// Same execution form the loader uses (inline script eval, window scope) —
|
||||
// the implied-eval ban targets accidental string execution, not this
|
||||
// deliberate bundle-execution fixture.
|
||||
@@ -59,7 +59,7 @@ describe('tsdown client artifact', () => {
|
||||
const { handoff, surface } = await loadArtifact()
|
||||
expect(handoff.id).toBe(PLUGIN_ID)
|
||||
expect(surface.apply).toBeTypeOf('function')
|
||||
expect(surface.inject).toEqual(['slots'])
|
||||
expect(surface.inject).toEqual(['slots', 'conversation'])
|
||||
})
|
||||
|
||||
it.skipIf(code === undefined)('mounted as an object plugin, apply registers both view tabs on the real ring', async () => {
|
||||
@@ -71,6 +71,10 @@ describe('tsdown client artifact', () => {
|
||||
name: 'root',
|
||||
children: { 'conversation.view': { kind: 'list', scope: 'session' } },
|
||||
}, (_p: { renderSlot?: unknown }) => null)
|
||||
// The plugin injects 'conversation' as an ordering edge (the declaring
|
||||
// plugin provides it after declaring the ring); the bench declares the
|
||||
// ring itself, so a stub satisfies the wait.
|
||||
ctx.provide('conversation', {})
|
||||
const fiber = ctx.plugin(surface as { apply: (ctx: Context) => void })
|
||||
await fiber.await()
|
||||
expect(slots.entries('conversation.view').map(e => e.options.id)).toEqual(['trajectory', 'waterfall'])
|
||||
|
||||
@@ -28,6 +28,9 @@ import { WaterfallView } from '@deepseek-ai/dsh-client-ui-trajectory/src/client/
|
||||
import { apply as nodeApply } from '@deepseek-ai/dsh-client-ui-trajectory'
|
||||
|
||||
const SID = 's1' as SessionId
|
||||
/** Fallback-only chain stub (no composer takeover in these benches). */
|
||||
const fallbackRenderSlotChain: ConversationRootProps['renderSlotChain'] =
|
||||
(_key, _owner, opts) => opts?.fallback ?? null
|
||||
|
||||
afterEach(cleanup)
|
||||
// The chat store persists under its declared key; clear so one case's active
|
||||
@@ -80,6 +83,9 @@ async function bench() {
|
||||
const chatBody = vi.fn(() => <div data-testid="chat-body" />)
|
||||
slots.register(
|
||||
{ name: 'conversation.view', id: 'chat', order: 0, label: 'Chat' } as never, chatBody as never)
|
||||
// 'conversation' inject is an ordering edge; the bench declares the ring
|
||||
// itself, so a stub satisfies the wait.
|
||||
ctx.provide('conversation', {})
|
||||
const fiber = ctx.plugin({ inject: [...inject], apply })
|
||||
await fiber.await()
|
||||
return { ctx, slots, fiber }
|
||||
@@ -120,6 +126,7 @@ function mount(slots: SlotsService, nodes: ConversationSnapshot['nodes'] = NODES
|
||||
useStore={bindSnapshotSelector(chat)}
|
||||
actions={chat.actions}
|
||||
renderSlot={renderSlot}
|
||||
renderSlotChain={fallbackRenderSlotChain}
|
||||
SessionProvider={SessionProviderStub}
|
||||
views={{
|
||||
list: () => tabsOf(slots),
|
||||
|
||||
Reference in New Issue
Block a user