feat(ui): add trajectory inspection ledger

This commit is contained in:
_Kerman
2026-07-27 15:47:44 +08:00
parent 79eb3a9035
commit 628c1bffe0
42 changed files with 4549 additions and 178 deletions

View File

@@ -72,8 +72,8 @@ export function apply(ctx: Context): void {
},
}), 'ui-conversation: input standard-kit provider')
// Resident current-session-optional shell. It owns the stable Hero/composer
// frame while strict session slots fill only their session-bound regions.
// Resident current-session-optional shell. It constructs the stable
// composer frame; the strict session child decides whether Chat mounts it.
slots.register({
name: 'conversation',
children: {
@@ -106,8 +106,8 @@ export function apply(ctx: Context): void {
}),
}, ConversationRoot)
// The strict session subtree owns only per-session store and view content;
// the resident parent keeps Hero and composer layout identity stable.
// The strict session subtree owns the per-session store and view content;
// the resident parent supplies the composer node through owner props.
slots.register({
name: 'conversation.session',
children: { 'conversation.view': { kind: 'list', scope: 'session' } },

View File

@@ -107,6 +107,8 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
/** Owner share of the strict session content seat. */
export interface ConversationSessionOwnerProps {
/** Composer chain assembled by the resident parent; only Chat mounts it. */
composer: ReactNode
}
/**

View File

@@ -74,6 +74,11 @@ export function ConversationRoot({
{inputBar}
</div>
)
const composer = renderSlotChain(
'conversation.composer',
{ interactions: pending },
{ fallback: composerBar, overlay: true },
)
return (
<div className={css.root} data-phase={hero ? 'hero' : 'active'}>
@@ -81,12 +86,9 @@ export function ConversationRoot({
renders no chrome while blank but owns the draft-persistence mirror
bind — unmounting it in the hero would lose pre-first-send text on
a refresh or scope rebuild. */}
{sessionId !== undefined && renderSlot('conversation.session', {})}
{renderSlotChain(
'conversation.composer',
{ interactions: pending },
{ fallback: composerBar, overlay: true },
)}
{sessionId !== undefined
? renderSlot('conversation.session', { composer })
: composer}
</div>
)
}

View File

@@ -1,6 +1,6 @@
/** Strict per-session conversation content: header, view ring, and chat store bindings. */
import { useEffect, useSyncExternalStore } from 'react'
import { Fragment, useEffect, useSyncExternalStore } from 'react'
import clsx from 'clsx'
import { shallowEqual } from '@deepseek-ai/dsh-client-runtime/client'
import type { SessionId, SessionListState, SessionSummary } from '@deepseek-ai/dsh-client-runtime/client'
@@ -24,7 +24,7 @@ function deriveAncestry(list: SessionListState, id: SessionId): readonly Session
export function ConversationSession({
sessionId, useSession, useSessions, useInput, inputActions, useStore, actions,
renderSlot, views, bindDraftMirror, open,
renderSlot, views, bindDraftMirror, open, composer,
}: ConversationSessionProps) {
useSyncExternalStore(views.subscribe, views.version)
const tabs = views.list()
@@ -45,11 +45,11 @@ export function ConversationSession({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [inputActions])
if (blank && composerPhase === 'blank') return null
const blankHero = blank && composerPhase === 'blank'
return (
<>
<header className={css.header}>
{!blankHero && <header className={css.header}>
<div className={css.crumbRow}>
<nav className={css.crumbs} aria-label="Session hierarchy">
{ancestry.map((summary, index) => {
@@ -88,10 +88,11 @@ export function ConversationSession({
))}
</div>
)}
</header>
<div className={css.viewArea}>
</header>}
{!blankHero && <div className={css.viewArea}>
{active !== undefined && renderSlot('conversation.view', {}, { only: active.id })}
</div>
</div>}
{(blankHero || active?.id === 'chat') && <Fragment key="composer">{composer}</Fragment>}
</>
)
}