fix(web-shell): center the managed host empty conversation as one group

The host mount entry has no .appChatEmpty ancestor, so the managed empty conversation kept its full-height content box and pinned the composer to the bottom. Give host-mounted empty conversations the same shrink-to-content centering, mount the entry through the managedHome fill wrapper, and drop the empty trailing status row below the composer.

Co-authored-by: Qwen-Coder <[email protected]>
This commit is contained in:
yiliang114andQwen-Coder committed 2026-11-02 20:55:27 +08:00
1 parent e8e72184ec
commit 1a6d59469d
4 files changed
+73 -15

No files matched your search

+14
View File
@@ -720,6 +720,20 @@
overflow: hidden;
}
/* Host-mounted managed home has no .appChatEmpty ancestor, so give its
empty conversation the same shrink-to-content behavior here. The
centered group must exclude the session header, which stays pinned at
the top. */
.chatPane[data-managed-home][data-managed-empty] .content {
flex: 0 0 auto;
overflow: visible;
}
.chatPane[data-managed-home][data-managed-empty] {
justify-content: center;
overflow-y: auto;
}
/* The chat view (message list + composer) stays mounted while a panel is shown,
just hidden — so the composer keeps its unsent draft and attachments instead
of losing them on unmount. */
@@ -195,7 +195,7 @@ export function ManagedAgentWebShell(props: ManagedAgentWebShellProps) {
<CompactModeContext.Provider value={false}>
<div
ref={setPortalRoot}
className={`${shellStyles.app} ${theme === WebShellThemeId.Dark ? `${shellStyles.themeDark} dark` : shellStyles.themeLight} ${className ?? ''}`}
className={`${shellStyles.app} ${shellStyles.managedHome} ${theme === WebShellThemeId.Dark ? `${shellStyles.themeDark} dark` : shellStyles.themeLight} ${className ?? ''}`}
style={style}
data-web-shell-root
data-web-shell-shadcn
@@ -4025,6 +4025,38 @@ describe('ManagedSessionsPage', () => {
},
);
it('marks the managed home conversation as empty only without messages or failure', async () => {
mocks.client.getTranscript.mockResolvedValue({
events: [],
lastEventId: 0,
});
await render('s1', 'en', provider, undefined, true, {
hostSessionOnly: true,
});
expect(
container
.querySelector('[data-managed-home]')
?.getAttribute('data-managed-empty'),
).toBe('true');
await act(async () => root.unmount());
container.remove();
container = document.createElement('div');
document.body.appendChild(container);
root = createRoot(container);
mocks.client.getSession.mockImplementation(async (id: string) =>
id === 's1' ? { ...summary(id), failure: 'runtime lost' } : summary(id),
);
await render('s1', 'en', provider, undefined, true, {
hostSessionOnly: true,
});
expect(
container
.querySelector('[data-managed-home]')
?.getAttribute('data-managed-empty'),
).toBeNull();
});
it('renders Chinese Managed labels and states without English fallback overriding them', async () => {
await render('s1', 'zh-CN');
expect(container.querySelector('nav')?.getAttribute('aria-label')).toBe(
@@ -1697,6 +1697,11 @@ function ManagedSessionsContent({
<section
className={`${shellStyles.chatPane} ${home ? '' : 'gap-3'}`}
data-managed-home={home || undefined}
data-managed-empty={
home && messages.length === 0 && !summary?.failure
? true
: undefined
}
aria-label={t('managed.conversation')}
>
{summary && home && (
@@ -1984,7 +1989,7 @@ function ManagedSessionsContent({
{summary?.failure && (
<div
role="alert"
className="mx-auto mb-2 flex w-full max-w-[var(--chat-shell-width)] items-start gap-2 rounded-lg border border-destructive/40 bg-destructive/10 px-3 py-[10px] text-xs leading-relaxed text-destructive"
className="mx-auto mt-auto mb-2 flex w-full max-w-[var(--chat-shell-width)] items-start gap-2 rounded-lg border border-destructive/40 bg-destructive/10 px-3 py-[10px] text-xs leading-relaxed text-destructive"
>
<span aria-hidden="true" className="mt-0.5 shrink-0">
⚠
@@ -1995,7 +2000,7 @@ function ManagedSessionsContent({
{pendingElsewhere && !busy && (
<div
role="status"
className="mb-2 flex items-center gap-3 text-sm text-muted-foreground"
className="mx-auto mt-auto mb-2 flex w-full max-w-[var(--chat-shell-width)] items-center gap-3 text-sm text-muted-foreground"
>
<p>{t('managed.pendingElsewhere')}</p>
<Button
@@ -2010,7 +2015,7 @@ function ManagedSessionsContent({
{pendingHere && !busy && (
<div
role="status"
className="mx-auto mb-2 flex w-full max-w-[var(--chat-shell-width)] items-start justify-between gap-3 rounded-lg border bg-muted/40 px-3 py-[10px] text-xs leading-relaxed text-muted-foreground"
className="mx-auto mt-auto mb-2 flex w-full max-w-[var(--chat-shell-width)] items-start justify-between gap-3 rounded-lg border bg-muted/40 px-3 py-[10px] text-xs leading-relaxed text-muted-foreground"
>
<p className="min-w-0 flex-1">{t('managed.uncertain')}</p>
<Button
@@ -2648,17 +2653,24 @@ function ManagedSessionsContent({
{t('managed.approvalFixed')}
</p>
)}
<div className="flex items-center justify-end gap-2">
{sessionId &&
!active &&
summary &&
!summary.capabilities.canSend && (
<span className="mr-auto text-sm text-muted-foreground">
{t('managed.newRequired')}
</span>
)}
{(!home || Boolean(text.trim())) && cancelButton}
</div>
{((sessionId &&
!active &&
summary &&
!summary.capabilities.canSend) ||
(!home && cancelButton) ||
Boolean(text.trim())) && (
<div className="flex items-center justify-end gap-2">
{sessionId &&
!active &&
summary &&
!summary.capabilities.canSend && (
<span className="mr-auto text-sm text-muted-foreground">
{t('managed.newRequired')}
</span>
)}
{(!home || Boolean(text.trim())) && cancelButton}
</div>
)}
</div>
) : (
cancelButton && (