Merge remote-tracking branch 'origin/master' into worktree/web-theme-settings-integration-fde706
# Conflicts: # .agents/notes/implemented/architecture/2026-07-30-client-locale-full-rollout.i18n.yaml # .agents/notes/implemented/architecture/2026-07-30-client-locale-full-rollout.zh.md # .agents/notes/implemented/feature/2026-07-30-web-queue-steer-action.i18n.yaml # .agents/notes/implemented/feature/2026-07-30-web-queue-steer-action.zh.md # .agents/notes/implemented/feature/2026-07-31-browser-derived-initial-locale.i18n.yaml # .agents/notes/implemented/feature/2026-07-31-browser-derived-initial-locale.zh.md # .agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.i18n.yaml # apps/web/tests/scaffold.ts # docs/module-graph.md # packages/client/runtime/README.i18n.yaml # packages/client/runtime/package.json # packages/client/test-runtime/README.i18n.yaml # packages/client/test-runtime/README.zh.md # packages/client/ui-conversation/README.i18n.yaml # packages/client/ui-conversation/README.zh.md # packages/client/ui-conversation/package.json # packages/client/ui-conversation/src/client/apply.ts # packages/client/ui-theme/README.i18n.yaml # packages/client/ui-theme/README.md # packages/client/ui-theme/README.zh.md # packages/host/apiproxy/README.i18n.yaml # packages/host/apiproxy/README.zh.md # pnpm-lock.yaml
This commit is contained in:
@@ -8,6 +8,7 @@ import type { PropsRenderSlots } from '@deepseek-ai/dsh-client-ui-slots'
|
||||
import { SlotTestRuntime, usePinnedBrowserLanguages } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { apply as applyConversation, inject as injectConversation } from '@deepseek-ai/dsh-client-ui-conversation/client'
|
||||
import { apply as applyTool, inject as injectTool } from '../src/client/apply.ts'
|
||||
import { toolChatSnapshot } from './tool-details-render.tsx'
|
||||
|
||||
// The service reads its initial locale from the browser; these specs assert
|
||||
// the shipped Chinese copy, so they state the browser they assume.
|
||||
@@ -75,7 +76,7 @@ async function bench(nodes: ToolResultNode[]) {
|
||||
await runtime.sessions.add({
|
||||
id: SID,
|
||||
summary: { title: 'S', displayTitle: 'S', cwd: '/proj' },
|
||||
snapshot: { nodes },
|
||||
snapshot: { nodes, chat: toolChatSnapshot(nodes) },
|
||||
session: {
|
||||
loadOlder: vi.fn<ISession['loadOlder']>(),
|
||||
prompt: vi.fn<ISession['prompt']>(async () => ({ ok: true, value: { accepted: true } })),
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
import { Context } from 'cordis'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { cleanup, fireEvent, render } from '@testing-library/react'
|
||||
import { createSnapshotStore, SlotsService } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import {
|
||||
ConversationEventRegistry, ConversationViewRegistry, createSnapshotStore, SlotsService,
|
||||
} from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import type {
|
||||
ConversationSnapshot, RunningToolCall, SessionId, SessionListState,
|
||||
ToolCallBlock, ToolResultNode, WorkspaceListState,
|
||||
@@ -21,6 +23,7 @@ import { LocaleService } from '@deepseek-ai/dsh-client-locale/client'
|
||||
import type { PropsRenderSlots } from '@deepseek-ai/dsh-client-ui-slots'
|
||||
import { apply as applyConversation, inject as injectConversation } from '@deepseek-ai/dsh-client-ui-conversation/client'
|
||||
import { apply as applyTool, inject as injectTool } from '../src/client/apply.ts'
|
||||
import { toolChatSnapshot } from './tool-details-render.tsx'
|
||||
|
||||
const SID = 's1' as SessionId
|
||||
|
||||
@@ -75,7 +78,8 @@ function snapshotWith(
|
||||
const nestedNodes = nodes.map(node => ({ ...node, subCalls }))
|
||||
const nestedRunningCalls = runningCalls.map(call => ({ ...call, subCalls }))
|
||||
return {
|
||||
sessionId: SID, nodes: nestedNodes, turnTimings: new Map(), turnEnds: new Map(), partial: null,
|
||||
sessionId: SID, chat: toolChatSnapshot(nestedNodes, nestedRunningCalls),
|
||||
nodes: nestedNodes, turnTimings: new Map(), turnEnds: new Map(), partial: null,
|
||||
runningCalls: nestedRunningCalls,
|
||||
pending: [], queue: [], running: runningCalls.length > 0, composerPhase: 'active', removed: false,
|
||||
openState: 'open', openError: null,
|
||||
@@ -89,11 +93,16 @@ function AppRoot({ renderSlot }: AppRootProps) {
|
||||
return <>{renderSlot('conversation', {})}</>
|
||||
}
|
||||
|
||||
/** Same real-stack bench as the toolview-slot spec: SlotsService + renderer + both owning package applies; fakes only at service seams. */
|
||||
/**
|
||||
* Same real-stack bench as the toolview-slot spec: SlotsService + renderer +
|
||||
* both owning package applies; fakes only at service boundaries.
|
||||
*/
|
||||
async function bench(snapshot: ConversationSnapshot) {
|
||||
const ctx = new Context()
|
||||
const slotsFiber = ctx.plugin(SlotsService)
|
||||
await slotsFiber.await()
|
||||
await ctx.plugin(ConversationEventRegistry).await()
|
||||
await ctx.plugin(ConversationViewRegistry).await()
|
||||
const slots = ctx.get('slots') as SlotsService
|
||||
|
||||
const session = createSnapshotStore<ConversationSnapshot>(snapshot)
|
||||
|
||||
@@ -20,7 +20,7 @@ import { createChatStore } from '@deepseek-ai/dsh-client-ui-conversation/src/cli
|
||||
import { GenericToolCard, type GenericToolCardProps } from '../src/client/tool/toolviews/GenericToolCard.tsx'
|
||||
import { DetailsPanel } from '@deepseek-ai/dsh-client-ui-conversation/src/client/skeleton/DetailsPanel.tsx'
|
||||
import { FileMutationRow, fileMutationToolview } from '../src/client/tool/toolviews/file-mutation-row.tsx'
|
||||
import { renderToolDetails, SessionProviderStub } from './tool-details-render.tsx'
|
||||
import { renderToolDetails, SessionProviderStub, toolChatSnapshot } from './tool-details-render.tsx'
|
||||
import { zh } from '@deepseek-ai/dsh-client-ui-conversation/src/client/locales.ts'
|
||||
|
||||
afterEach(cleanup)
|
||||
@@ -342,8 +342,11 @@ describe('DetailsPanel diff Output section', () => {
|
||||
}
|
||||
|
||||
function snapshot(over: Partial<ConversationSnapshot> = {}): ConversationSnapshot {
|
||||
const nodes = over.nodes ?? []
|
||||
const runningCalls = over.runningCalls ?? []
|
||||
return {
|
||||
sessionId: SID, nodes: [], turnTimings: new Map(), turnEnds: new Map(), partial: null, runningCalls: [],
|
||||
sessionId: SID, chat: over.chat ?? toolChatSnapshot(nodes, runningCalls),
|
||||
nodes: [], turnTimings: new Map(), turnEnds: new Map(), partial: null, runningCalls: [],
|
||||
pending: [], queue: [], running: false, composerPhase: 'active', removed: false,
|
||||
openState: 'open', openError: null, hasMore: false, loadingOlder: false,
|
||||
promptError: null, blank: false, subagent: null, lastAgentError: null, ...over,
|
||||
|
||||
@@ -24,7 +24,7 @@ import { GenericToolCard, type GenericToolCardProps } from '../src/client/tool/t
|
||||
import { zh } from '@deepseek-ai/dsh-client-ui-conversation/src/client/locales.ts'
|
||||
import { DetailsPanel } from '@deepseek-ai/dsh-client-ui-conversation/src/client/skeleton/DetailsPanel.tsx'
|
||||
import { ReadRow, readToolview } from '../src/client/tool/toolviews/read-row.tsx'
|
||||
import { renderToolDetails, SessionProviderStub } from './tool-details-render.tsx'
|
||||
import { renderToolDetails, SessionProviderStub, toolChatSnapshot } from './tool-details-render.tsx'
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
@@ -288,8 +288,11 @@ describe('DetailsPanel Output section (read)', () => {
|
||||
}
|
||||
|
||||
function snapshot(over: Partial<ConversationSnapshot> = {}): ConversationSnapshot {
|
||||
const nodes = over.nodes ?? []
|
||||
const runningCalls = over.runningCalls ?? []
|
||||
return {
|
||||
sessionId: SID, nodes: [], turnTimings: new Map(), turnEnds: new Map(), partial: null, runningCalls: [],
|
||||
sessionId: SID, chat: over.chat ?? toolChatSnapshot(nodes, runningCalls),
|
||||
nodes: [], turnTimings: new Map(), turnEnds: new Map(), partial: null, runningCalls: [],
|
||||
pending: [], queue: [], running: false, composerPhase: 'active', removed: false,
|
||||
openState: 'open', openError: null, hasMore: false, loadingOlder: false,
|
||||
promptError: null, blank: false, subagent: null, lastAgentError: null, ...over,
|
||||
|
||||
@@ -23,7 +23,7 @@ import { createChatStore } from '@deepseek-ai/dsh-client-ui-conversation/src/cli
|
||||
import { GenericToolCard, type GenericToolCardProps } from '../src/client/tool/toolviews/GenericToolCard.tsx'
|
||||
import { DetailsPanel } from '@deepseek-ai/dsh-client-ui-conversation/src/client/skeleton/DetailsPanel.tsx'
|
||||
import { SearchRow, searchToolview } from '../src/client/tool/toolviews/search-row.tsx'
|
||||
import { renderToolDetails, SessionProviderStub } from './tool-details-render.tsx'
|
||||
import { renderToolDetails, SessionProviderStub, toolChatSnapshot } from './tool-details-render.tsx'
|
||||
|
||||
/** SearchRow now composes ToolRow, so its props include the locale `t` seat. */
|
||||
type SearchRowProps = Parameters<typeof SearchRow>[0]
|
||||
@@ -404,8 +404,11 @@ describe('DetailsPanel Output section (search)', () => {
|
||||
}
|
||||
|
||||
function snapshot(over: Partial<ConversationSnapshot> = {}): ConversationSnapshot {
|
||||
const nodes = over.nodes ?? []
|
||||
const runningCalls = over.runningCalls ?? []
|
||||
return {
|
||||
sessionId: SID, nodes: [], turnTimings: new Map(), turnEnds: new Map(), partial: null, runningCalls: [],
|
||||
sessionId: SID, chat: over.chat ?? toolChatSnapshot(nodes, runningCalls),
|
||||
nodes: [], turnTimings: new Map(), turnEnds: new Map(), partial: null, runningCalls: [],
|
||||
pending: [], queue: [], running: false, composerPhase: 'active', removed: false,
|
||||
openState: 'open', openError: null, hasMore: false, loadingOlder: false,
|
||||
promptError: null, blank: false, subagent: null, lastAgentError: null, ...over,
|
||||
|
||||
@@ -20,7 +20,7 @@ import { createChatStore } from '@deepseek-ai/dsh-client-ui-conversation/src/cli
|
||||
import { GenericToolCard, type GenericToolCardProps } from '../src/client/tool/toolviews/GenericToolCard.tsx'
|
||||
import { DetailsPanel } from '@deepseek-ai/dsh-client-ui-conversation/src/client/skeleton/DetailsPanel.tsx'
|
||||
import { BashRow } from '../src/client/tool/toolviews/bash-sample.tsx'
|
||||
import { renderToolDetails, SessionProviderStub } from './tool-details-render.tsx'
|
||||
import { renderToolDetails, SessionProviderStub, toolChatSnapshot } from './tool-details-render.tsx'
|
||||
import { zh } from '@deepseek-ai/dsh-client-ui-conversation/src/client/locales.ts'
|
||||
|
||||
type BashRowProps = Parameters<typeof BashRow>[0]
|
||||
@@ -479,8 +479,11 @@ describe('DetailsPanel Output section', () => {
|
||||
}
|
||||
|
||||
function snapshot(over: Partial<ConversationSnapshot> = {}): ConversationSnapshot {
|
||||
const nodes = over.nodes ?? []
|
||||
const runningCalls = over.runningCalls ?? []
|
||||
return {
|
||||
sessionId: SID, nodes: [], turnTimings: new Map(), turnEnds: new Map(), partial: null, runningCalls: [],
|
||||
sessionId: SID, chat: over.chat ?? toolChatSnapshot(nodes, runningCalls),
|
||||
nodes: [], turnTimings: new Map(), turnEnds: new Map(), partial: null, runningCalls: [],
|
||||
pending: [], queue: [], running: false, composerPhase: 'active', removed: false,
|
||||
openState: 'open', openError: null, hasMore: false, loadingOlder: false,
|
||||
promptError: null, blank: false, subagent: null, lastAgentError: null, ...over,
|
||||
|
||||
@@ -29,12 +29,21 @@ function props(
|
||||
return {
|
||||
useSession,
|
||||
renderSlot,
|
||||
callId: block.callId,
|
||||
toolName: block.call?.name ?? '',
|
||||
block,
|
||||
node: {
|
||||
key: `tool:${block.callId}`,
|
||||
kind: 'tool-call',
|
||||
id: block.callId,
|
||||
target: 'chat',
|
||||
anchorSeq: block.seq,
|
||||
location: { kind: 'session' },
|
||||
visibility: 'visible',
|
||||
data: { root: block },
|
||||
},
|
||||
selectedCallId,
|
||||
openFile: vi.fn(),
|
||||
inspectCall: vi.fn(),
|
||||
forkAt: vi.fn(),
|
||||
fileMentions: vi.fn(),
|
||||
t,
|
||||
} as unknown as ToolTreeProps
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/** Test adapter for the production conversation.details.tool registration. */
|
||||
import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import type {
|
||||
ChatConversationViewNode, ChatSnapshot, ConversationNode, RunningToolCall, SessionId,
|
||||
} from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import type { SessionProviderComponent, TranslateNS } from '@deepseek-ai/dsh-client-ui-slots'
|
||||
import type { DetailsSlotProps, DetailsToolOwnerProps } from '@deepseek-ai/dsh-client-ui-conversation/src/client/contract/slots.ts'
|
||||
import { ToolDetails } from '../src/client/tool/ToolDetails.tsx'
|
||||
@@ -7,6 +9,45 @@ import { ToolDetails } from '../src/client/tool/ToolDetails.tsx'
|
||||
/** Framework session-area seat used by direct DetailsPanel tests. */
|
||||
export const SessionProviderStub: SessionProviderComponent = ({ children }) => children('s1' as SessionId)
|
||||
|
||||
/** Build the canonical Chat slice consumed by Tool rows and details tests. */
|
||||
export function toolChatSnapshot(
|
||||
settled: readonly ConversationNode[] = [],
|
||||
running: readonly RunningToolCall[] = [],
|
||||
): ChatSnapshot {
|
||||
const roots = [...settled.filter(node => node.kind === 'tool-result'), ...running]
|
||||
const nodes: ChatConversationViewNode[] = roots.map(root => ({
|
||||
key: `tool:${root.callId}`,
|
||||
kind: 'tool-call',
|
||||
id: root.callId,
|
||||
target: 'chat',
|
||||
anchorSeq: 'kind' in root ? root.seq : Number.MAX_SAFE_INTEGER,
|
||||
location: { kind: 'session' },
|
||||
visibility: 'visible',
|
||||
data: { root },
|
||||
}))
|
||||
const byKey = new Map(nodes.map(node => [node.key, node]))
|
||||
const empty: readonly string[] = []
|
||||
return {
|
||||
order: nodes.map(node => node.key),
|
||||
nodes: {
|
||||
get: key => byKey.get(key),
|
||||
values: () => nodes,
|
||||
},
|
||||
locations: {
|
||||
getTurn: () => empty,
|
||||
getStep: () => empty,
|
||||
},
|
||||
timeline: { turnOrder: [], turns: new Map() },
|
||||
legacy: {
|
||||
nodes: settled,
|
||||
runningCalls: running,
|
||||
partial: null,
|
||||
turnTimings: new Map(),
|
||||
turnEnds: new Map(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind ui-tool's details renderer to the conversation slot callback shape.
|
||||
* @param t - conversation locale seat used by Tool cards.
|
||||
@@ -16,7 +57,7 @@ export function renderToolDetails(t: TranslateNS<'conversation'>): DetailsSlotPr
|
||||
return (_key, owner) => {
|
||||
// PropsRenderSlots keeps its key generic even for this one-key share;
|
||||
// recover the concrete owner selected by the adapter's fixed slot.
|
||||
const details = owner as DetailsToolOwnerProps
|
||||
const details = owner as unknown as DetailsToolOwnerProps
|
||||
return <ToolDetails block={details.block} cwd={details.cwd} t={t} />
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,8 +71,8 @@ describe('tool-call-model', () => {
|
||||
expect(toolRowModel('read', running({ name: 'read', argsRaw: '{"path":"/tmp/x.ts"}' })).summary).toBe('/tmp/x.ts')
|
||||
expect(toolRowModel('write', running({ name: 'write', argsRaw: '{"file_path":"src/x.ts"}' })).summary).toBe('src/x.ts')
|
||||
expect(toolRowModel('edit', running({ name: 'edit', argsRaw: '{"file_path":"src/x.ts"}' })).summary).toBe('src/x.ts')
|
||||
// Others rows prefix the real tool name into the summary slot (figma-flows
|
||||
// ruling: static "Tool call" title, name rides the mutable summary).
|
||||
// Other rows prefix the real tool name into the summary slot (figma
|
||||
// flows: static "Tool call" title, the name rides the mutable summary).
|
||||
expect(toolRowModel('x', running({ argsRaw: '{"n":1}' })).summary).toBe('x · {"n":1}')
|
||||
expect(toolRowModel('x', running({ argsRaw: 'not json' })).summary).toBe('x · not json')
|
||||
expect(toolRowModel('x', running({ argsRaw: '' })).summary).toBe('x · c1')
|
||||
|
||||
@@ -18,6 +18,7 @@ import { LocaleService } from '@deepseek-ai/dsh-client-locale/client'
|
||||
import { apply as applyConversation, inject as injectConversation } from '@deepseek-ai/dsh-client-ui-conversation/client'
|
||||
import { apply as applyTool, inject as injectTool } from '@deepseek-ai/dsh-client-ui-tool/client'
|
||||
import type { ToolCallViewProps } from '@deepseek-ai/dsh-client-ui-tool/client'
|
||||
import { toolChatSnapshot } from './tool-details-render.tsx'
|
||||
|
||||
const SID = 's1' as SessionId
|
||||
|
||||
@@ -58,7 +59,7 @@ const LAYOUT_CHILDREN = {
|
||||
|
||||
/**
|
||||
* Real-stack bench: SlotTestRuntime with the session/layout doubles at the
|
||||
* service seams only (external boundaries), the package apply on its own
|
||||
* service boundaries only, the package apply on its own
|
||||
* fiber, and the test AppFrame occupying 'root'.
|
||||
*/
|
||||
async function bench(nodes: ToolResultNode[]) {
|
||||
@@ -72,7 +73,7 @@ async function bench(nodes: ToolResultNode[]) {
|
||||
await runtime.sessions.add({
|
||||
id: SID,
|
||||
summary: { title: 'S', displayTitle: 'S' },
|
||||
snapshot: { nodes },
|
||||
snapshot: { nodes, chat: toolChatSnapshot(nodes) },
|
||||
session: {
|
||||
loadOlder: vi.fn<ISession['loadOlder']>(),
|
||||
prompt: vi.fn<ISession['prompt']>(async () => ({ ok: true, value: { accepted: true } })),
|
||||
|
||||
@@ -23,7 +23,7 @@ import { createChatStore } from '@deepseek-ai/dsh-client-ui-conversation/src/cli
|
||||
import { GenericToolCard } from '../src/client/tool/toolviews/GenericToolCard.tsx'
|
||||
import { DetailsPanel } from '@deepseek-ai/dsh-client-ui-conversation/src/client/skeleton/DetailsPanel.tsx'
|
||||
import { WebRow, webToolview } from '../src/client/tool/toolviews/web-row.tsx'
|
||||
import { renderToolDetails, SessionProviderStub } from './tool-details-render.tsx'
|
||||
import { renderToolDetails, SessionProviderStub, toolChatSnapshot } from './tool-details-render.tsx'
|
||||
import { makeTranslate } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { zh as commonZh } from '@deepseek-ai/dsh-client-locale/src/locales/zh.ts'
|
||||
import { zh } from '@deepseek-ai/dsh-client-ui-conversation/src/client/locales.ts'
|
||||
@@ -234,8 +234,11 @@ describe('DetailsPanel web Output section', () => {
|
||||
}
|
||||
|
||||
function snapshot(over: Partial<ConversationSnapshot> = {}): ConversationSnapshot {
|
||||
const nodes = over.nodes ?? []
|
||||
const runningCalls = over.runningCalls ?? []
|
||||
return {
|
||||
sessionId: SID, nodes: [], turnTimings: new Map(), turnEnds: new Map(), partial: null, runningCalls: [],
|
||||
sessionId: SID, chat: over.chat ?? toolChatSnapshot(nodes, runningCalls),
|
||||
nodes: [], turnTimings: new Map(), turnEnds: new Map(), partial: null, runningCalls: [],
|
||||
pending: [], queue: [], running: false, composerPhase: 'active', removed: false,
|
||||
openState: 'open', openError: null, hasMore: false, loadingOlder: false,
|
||||
promptError: null, blank: false, subagent: null, lastAgentError: null, ...over,
|
||||
|
||||
Reference in New Issue
Block a user