Merge remote-tracking branch 'origin/master' into worktree/chat-ui-polish

# Conflicts:
#	packages/client/ui-conversation/README.i18n.yaml
#	packages/client/ui-conversation/src/client/chat/MessageItem.tsx
#	packages/client/ui-conversation/tests/chat-branch-tails.spec.tsx
This commit is contained in:
Yichen Jiang
2026-07-30 19:58:25 +08:00
25 changed files with 540 additions and 244 deletions
@@ -107,15 +107,48 @@ describe('MessageItem arms', () => {
expect(view.queryByRole('button', { name: '复制' })).toBeNull()
})
it('context and unknown nodes render their JSON rows', () => {
it('context uses the Tool calls disclosure chrome and keeps its JSON collapsed by default', () => {
const ctxView = render(
<MessageItem node={{ kind: 'context', seq: 3, content: [], source: null } as never} />,
<MessageItem node={{
kind: 'context',
seq: 3,
content: [{ type: 'text', text: 'x\n"y":,[{}]' }],
source: { kind: 'plugin', plugin: 'fixture', empty: {}, list: [] },
} as never}
/>,
)
const contextToggle = ctxView.getByRole('button', { name: '上下文注入' })
expect(contextToggle.getAttribute('aria-expanded')).toBe('false')
expect(contextToggle.querySelector('svg')).not.toBeNull()
fireEvent.click(contextToggle)
expect(contextToggle.getAttribute('aria-expanded')).toBe('true')
const disclosure = ctxView.getByRole('button', { name: '上下文注入' })
expect(disclosure.getAttribute('aria-expanded')).toBe('false')
expect(ctxView.container.querySelector('[data-context-injection-body]')).toBeNull()
expect(ctxView.container.querySelector('svg')).not.toBeNull()
fireEvent.click(disclosure)
expect(disclosure.getAttribute('aria-expanded')).toBe('true')
expect(ctxView.container.querySelector('[data-context-injection-body]')?.textContent).toBe(
'{ "content": [ { "type": "text", "text": "x\\n\\"y\\":,[{}]" } ], '
+ '"source": { "kind": "plugin", "plugin": "fixture", "empty": {}, "list": [] } }',
)
fireEvent.keyDown(disclosure, { key: ' ' })
expect(disclosure.getAttribute('aria-expanded')).toBe('false')
})
it('context preserves the bounded JSON truncation contract', () => {
const view = render(
<MessageItem node={{
kind: 'context',
seq: 3,
content: [{ type: 'text', text: 'x'.repeat(21_000) }],
source: null,
} as never}
/>,
)
fireEvent.click(view.getByRole('button', { name: '上下文注入' }))
expect(view.container.querySelector('[data-context-injection-body]')?.textContent)
.toMatch(/… 已截断,共 \d+ 字符$/)
})
it('unknown nodes retain the generic JSON row', () => {
const unknownView = render(
<MessageItem node={{ kind: 'unknown', seq: 4, type: 'surface/next', data: { x: 1 } } as never} />,
)