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

This commit is contained in:
imccyu
2026-07-24 01:12:29 +08:00
734 changed files with 33423 additions and 7286 deletions

View File

@@ -4,6 +4,8 @@ Web shell library: `bootWebShell(el, seams?)` mounts the whole client — loader
The optional `seams` parameter forwards the client loader's `fetchBundle`/`executeBundle` transport overrides (`BootSeams`); production callers omit it — it exists for test environments where `<script>` execution cannot reach the page context (jsdom).
The shell owns browser-title projection. With a selected session carrying a durable title, it renders `<session title> — <existing HTML title>` and reacts to later title revisions; no selection or a selected untitled session preserves the existing title, and shell unmount restores it. The existing HTML title remains the configurable product suffix.
## Model Experience
None, as the entry shell boots the browser plugin tree; nothing here reaches a model request.

View File

@@ -0,0 +1,22 @@
import { useEffect, useRef } from 'react'
/** Props for the shell-owned browser title projection. */
export interface DocumentTitleProps {
/** Durable title of the selected session, or undefined for the product title. */
title?: string
}
/**
* Project the selected durable session title into the browser title and
* restore the shell's original product title when unmounted.
* @param props - selected session title projection.
* @returns no rendered content.
*/
export function DocumentTitle({ title }: DocumentTitleProps): null {
const original = useRef(document.title)
useEffect(() => {
document.title = title === undefined ? original.current : `${title}${original.current}`
return () => { document.title = original.current }
}, [title])
return null
}

View File

@@ -6,6 +6,9 @@
*/
import type { ReactNode } from 'react'
import type { Context } from 'cordis'
import type { SessionsService } from '@deepseek-ai/dsh-client-runtime/client'
import { bindSnapshotSelector } from '@deepseek-ai/dsh-client-web-react'
import { DocumentTitle } from './DocumentTitle.tsx'
// Type-only: pulls the runtime's SlotMap declaration merge (the 'root' key) into this program.
import type {} from '@deepseek-ai/dsh-client-runtime/client'
@@ -24,5 +27,20 @@ export interface AssemblyDeps {
*/
export function buildRenderApp(deps: AssemblyDeps): () => ReactNode {
const { ctx } = deps
return () => ctx.slots.renderSlot('root', {})
const sessions = ctx.get('sessions') as SessionsService | undefined
if (sessions === undefined) throw new Error('shell assembly: sessions service unavailable')
const useSessions = bindSnapshotSelector(sessions.list)
const SessionDocumentTitle = (): ReactNode => {
const title = useSessions((state) => {
const id = state.current
return id === undefined ? undefined : state.byId[id]?.title
})
return <DocumentTitle {...title === undefined ? {} : { title }} />
}
return () => (
<>
<SessionDocumentTitle />
{ctx.slots.renderSlot('root', {})}
</>
)
}

View File

@@ -8,4 +8,5 @@
export { bootWebShell } from './boot.tsx'
export { AppRoot, type AppRootProps } from './AppRoot.tsx'
export { buildRenderApp, type AssemblyDeps } from './app.tsx'
export { DocumentTitle, type DocumentTitleProps } from './DocumentTitle.tsx'
export { seedModules } from './seed.ts'

View File

@@ -39,7 +39,7 @@ window.DSHClientProxy.loadPlugin({
return {
apply: (ctx) => {
ctx.plugin(SlotsService)
const list = createSnapshotStore({ ids: ['s1'], byId: { s1: { id: 's1', title: 'S1', running: false, updatedAt: 1 } }, current: 's1' })
const list = createSnapshotStore({ ids: ['s1'], byId: { s1: { id: 's1', title: 'S1', displayTitle: 'S1', running: false, updatedAt: 1 } }, current: 's1' })
ctx.provide('sessions', {
list,
cell: (id) => (id === 's1' ? { sessionId: 's1', session: { getSnapshot: () => ({}), subscribe: () => () => {} } } : undefined),
@@ -134,6 +134,7 @@ afterEach(() => {
delete win.__TEST_RUNTIME_STORE__
document.body.innerHTML = ''
document.head.querySelectorAll('script').forEach((s) => { s.remove() })
document.title = ''
})
/** Hand the real runtime surface to the stub bundle (runtime is not a seeded library). */
@@ -147,6 +148,7 @@ describe('bootWebShell (real loader + real script execution)', () => {
win.__DSH_BOOT__ = { plugins: bootPlugins() }
seedSlotsService()
const el = mountPoint()
document.title = 'DeepSeek Harness'
let unmount: (() => void) | undefined
act(() => { unmount = bootWebShell(el, seams(fakeBundles())) })
expect(el.textContent).toContain('HARNESS')
@@ -155,9 +157,11 @@ describe('bootWebShell (real loader + real script execution)', () => {
await flushLoader()
expect(el.querySelector('[data-testid="fake-frame"]')).not.toBeNull()
expect(el.textContent).not.toContain('HARNESS')
expect(document.title).toBe('S1 — DeepSeek Harness')
act(() => { unmount!() })
expect(el.childElementCount).toBe(0)
expect(document.title).toBe('DeepSeek Harness')
})
it('store seat round-trips through the entry props (useStore + actions)', async () => {
@@ -218,6 +222,9 @@ describe('buildRenderApp — assembly contract', () => {
const ctx = new Context()
const fiber = ctx.plugin(SlotsService)
await fiber.await()
ctx.provide('sessions', {
list: createSnapshotStore({ ids: [], byId: {}, current: undefined }),
})
const renderApp = buildRenderApp({ ctx, requireModule: () => undefined })
expect(renderApp).toBeTypeOf('function')
// No renderer installed: the one-line shell must surface the boot-order error.

View File

@@ -0,0 +1,28 @@
// @vitest-environment jsdom
import { afterEach, describe, expect, it } from 'vitest'
import { cleanup, render } from '@testing-library/react'
import { DocumentTitle } from '../src/DocumentTitle.tsx'
afterEach(() => {
cleanup()
document.title = ''
})
describe('DocumentTitle', () => {
it('preserves the product title without a durable title and restores it on unmount', () => {
document.title = 'DeepSeek Harness'
const mounted = render(<DocumentTitle />)
expect(document.title).toBe('DeepSeek Harness')
mounted.rerender(<DocumentTitle title="First title" />)
expect(document.title).toBe('First title — DeepSeek Harness')
mounted.rerender(<DocumentTitle title="Revised title" />)
expect(document.title).toBe('Revised title — DeepSeek Harness')
mounted.rerender(<DocumentTitle />)
expect(document.title).toBe('DeepSeek Harness')
mounted.unmount()
expect(document.title).toBe('DeepSeek Harness')
})
})