diff --git a/apps/desktop/src/app/chat/index.tsx b/apps/desktop/src/app/chat/index.tsx index f890a5bfe..c9f525653 100644 --- a/apps/desktop/src/app/chat/index.tsx +++ b/apps/desktop/src/app/chat/index.tsx @@ -42,6 +42,7 @@ import { $sessions, sessionPinId } from '@/store/session' +import { isNewSessionWindow, isSecondaryWindow } from '@/store/windows' import type { ModelOptionsResponse } from '@/types/hermes' import { routeSessionId } from '../routes' @@ -122,7 +123,7 @@ function ChatHeader({ // A brand-new session has no session to pin/delete/rename, so the header is // just a dead "New session" label + chevron. Drop it (and its border) // entirely until there's a real session to act on. - if (!selectedSessionId && !activeSessionId && !isRoutedSessionView) { + if (isNewSessionWindow() || (!selectedSessionId && !activeSessionId && !isRoutedSessionView)) { return null } @@ -302,7 +303,10 @@ export function ChatView({ // waiting for the resume effect (which paints a frame later) to clear them. const routeSessionMismatch = isRoutedSessionView && routedSessionId !== selectedSessionId - const showIntro = freshDraftReady && !isRoutedSessionView && !selectedSessionId && !activeSessionId && messagesEmpty + // The compact new-session pop-out skips the wordmark/tagline intro — it's a + // scratch window, not the full-height empty state. + const showIntro = + !isSecondaryWindow() && freshDraftReady && !isRoutedSessionView && !selectedSessionId && !activeSessionId && messagesEmpty // Session is still loading if the route references a session we haven't // resumed yet. Once `activeSessionId` is set (runtime has resumed), the diff --git a/apps/desktop/src/app/shell/app-shell.tsx b/apps/desktop/src/app/shell/app-shell.tsx index 8e5487344..ade1f8a3c 100644 --- a/apps/desktop/src/app/shell/app-shell.tsx +++ b/apps/desktop/src/app/shell/app-shell.tsx @@ -16,7 +16,7 @@ import { } from '@/store/layout' import { $paneWidthOverride } from '@/store/panes' import { $connection } from '@/store/session' -import { isSecondaryWindow } from '@/store/windows' +import { isNewSessionWindow, isSecondaryWindow } from '@/store/windows' import { SIDEBAR_COLLAPSE_MEDIA_QUERY } from '../layout-constants' @@ -80,6 +80,7 @@ export function AppShell({ const connection = useStore($connection) const viewportFullscreen = useSyncExternalStore(subscribeWindowSize, viewportIsFullscreen, () => false) const isFullscreen = Boolean(connection?.isFullscreen) || viewportFullscreen + const hideTitlebarControls = isNewSessionWindow() const titlebarControls = titlebarControlsPosition(connection?.windowButtonPosition, isFullscreen) // Width Windows/Linux reserve for the OS-painted min/max/close overlay (zero // on macOS, where window controls sit on the left and are reported via @@ -162,7 +163,9 @@ export function AppShell({ } as CSSProperties } > - + {!hideTitlebarControls && ( + + )}
@@ -183,7 +186,9 @@ export function AppShell({ the panes' z-20 resize handles, keeping every pane resizable. */} {mainOverlays} - + {/* The compact pop-out drops the statusbar — it's a scratch window, not + the full shell. */} + {!isSecondaryWindow() && }
{overlays} diff --git a/apps/desktop/src/components/assistant-ui/thread-list.tsx b/apps/desktop/src/components/assistant-ui/thread-list.tsx index 397ed2aa9..0a4be8396 100644 --- a/apps/desktop/src/components/assistant-ui/thread-list.tsx +++ b/apps/desktop/src/components/assistant-ui/thread-list.tsx @@ -1,5 +1,6 @@ import { ThreadPrimitive, useAuiEvent, useAuiState } from '@assistant-ui/react' import { + type CSSProperties, type ComponentProps, type FC, memo, @@ -21,6 +22,7 @@ import { resetThreadScroll, setThreadAtBottom } from '@/store/thread-scroll' +import { isNewSessionWindow, isSecondaryWindow } from '@/store/windows' import { MessageRenderBoundary } from './message-render-boundary' @@ -132,6 +134,13 @@ const ThreadMessageListInner: FC = ({ const hiddenCount = firstVisible const visibleGroups = hiddenCount > 0 ? groups.slice(hiddenCount) : groups const restoreFromBottomRef = useRef(null) + const newSessionWindow = isNewSessionWindow() + const newSessionTitlebarGap = 'calc(var(--titlebar-height)+0.75rem)' + const threadContentTopPad = newSessionWindow + ? 'pt-[calc(var(--titlebar-height)+0.75rem)]' + : isSecondaryWindow() + ? 'pt-6' + : 'pt-[calc(var(--titlebar-height)+1.5rem)]' useEffect(() => setThreadAtBottom(isAtBottom), [isAtBottom]) useEffect(() => () => resetThreadScroll(), []) @@ -235,7 +244,12 @@ const ThreadMessageListInner: FC = ({ return (
= ({
) : (
} >