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 (