diff --git a/apps/desktop/src/settings/DesktopClientSettings.test.ts b/apps/desktop/src/settings/DesktopClientSettings.test.ts index 47800f3192d..8f50aa8f882 100644 --- a/apps/desktop/src/settings/DesktopClientSettings.test.ts +++ b/apps/desktop/src/settings/DesktopClientSettings.test.ts @@ -19,6 +19,7 @@ const clientSettings: ClientSettings = { dismissedProviderUpdateNotificationKeys: [], diffIgnoreWhitespace: true, favorites: [], + glassOpacity: 80, providerModelPreferences: {}, sidebarAutoSettleAfterDays: 3, sidebarProjectGroupingMode: "repository_path", diff --git a/apps/web/src/components/AppSidebarLayout.tsx b/apps/web/src/components/AppSidebarLayout.tsx index ef6b64be9c9..3a70390d0c4 100644 --- a/apps/web/src/components/AppSidebarLayout.tsx +++ b/apps/web/src/components/AppSidebarLayout.tsx @@ -106,6 +106,7 @@ export function AppSidebarLayout({ children }: { children: ReactNode }) { const pathname = useLocation({ select: (location) => location.pathname }); const isOnSettings = pathname === "/settings" || pathname.startsWith("/settings/"); const useSidebarV2 = sidebarV2Enabled && !isOnSettings; + const useSidebarV2Theme = useSidebarV2 || isOnSettings; const isMacosDesktop = isElectron && isMacPlatform(navigator.platform); const [sidebarWidth, setSidebarWidth] = useState(readInitialThreadSidebarWidth); const sidebarMaximumWidth = resolveThreadSidebarMaximumWidth(window.innerWidth); @@ -165,7 +166,7 @@ export function AppSidebarLayout({ children }: { children: ReactNode }) { side="left" collapsible="offcanvas" data-app-sidebar="" - data-sidebar-version={useSidebarV2 ? "v2" : "v1"} + data-sidebar-version={useSidebarV2Theme ? "v2" : "v1"} className="border-r border-sidebar-border bg-sidebar text-sidebar-foreground" resizable={{ maxWidth: sidebarMaximumWidth, diff --git a/apps/web/src/components/BranchToolbar.tsx b/apps/web/src/components/BranchToolbar.tsx index 0354c2e0cd7..c2db48184c7 100644 --- a/apps/web/src/components/BranchToolbar.tsx +++ b/apps/web/src/components/BranchToolbar.tsx @@ -248,7 +248,7 @@ export const BranchToolbar = memo(function BranchToolbar({ if (!hasActiveThread || !activeProject) return null; return ( -
+
{isMobile ? ( store.threadLastVisitedAtById[routeThreadKey], ); const settings = useEnvironmentSettings(environmentId); + // New-thread defaults live in the primary environment's settings.json (the + // settings UI never writes to remote environments), so read them from the + // primary server rather than the thread's environment. + const primaryServerSettings = useAtomValue(primaryServerSettingsAtom); const setStickyComposerModelSelection = useComposerDraftStore( (store) => store.setStickyModelSelection, ); @@ -2320,6 +2329,22 @@ function ChatViewContent(props: ChatViewProps) { const defaultInstanceId = defaultInstanceIdForDriver(selectedProvider); return providerStatuses.find((status) => status.instanceId === defaultInstanceId) ?? null; }, [activeProviderInstanceId, providerStatuses, selectedProvider]); + const providerStatusBannerKey = getProviderStatusBannerKey(activeProviderStatus); + const [dismissedProviderStatusBannerKey, setDismissedProviderStatusBannerKey] = useState< + string | null + >(null); + useEffect(() => { + if (providerStatusBannerKey === null && dismissedProviderStatusBannerKey !== null) { + setDismissedProviderStatusBannerKey(null); + } + }, [dismissedProviderStatusBannerKey, providerStatusBannerKey]); + const visibleProviderStatus = shouldShowProviderStatusBanner( + activeProviderStatus, + dismissedProviderStatusBannerKey, + ) + ? activeProviderStatus + : null; + const hasTimelineTopBanner = Boolean(threadError) || visibleProviderStatus !== null; const activeProjectCwd = activeProject?.workspaceRoot ?? null; const activeThreadWorktreePath = activeThread?.worktreePath ?? null; const activeWorkspaceRoot = activeThreadWorktreePath ?? activeProjectCwd ?? undefined; @@ -3870,7 +3895,7 @@ function ChatViewContent(props: ChatViewProps) { ? (draftThread?.startFromOrigin ?? false) : canOverrideServerThreadEnvMode ? (pendingServerThreadStartFromOriginByThreadId[activeThread?.id ?? ""] ?? - settings.newWorktreesStartFromOrigin) + primaryServerSettings.newWorktreesStartFromOrigin) : false; const sendEnvMode = resolveSendEnvMode({ requestedEnvMode: envMode, @@ -5343,7 +5368,7 @@ function ChatViewContent(props: ChatViewProps) { envMode: mode, startFromOrigin: resolveNewDraftStartFromOrigin({ envMode: mode, - newWorktreesStartFromOrigin: settings.newWorktreesStartFromOrigin, + newWorktreesStartFromOrigin: primaryServerSettings.newWorktreesStartFromOrigin, }), ...(mode === "worktree" && draftThread?.worktreePath ? { worktreePath: null } : {}), }); @@ -5355,7 +5380,7 @@ function ChatViewContent(props: ChatViewProps) { composerDraftTarget, draftThread?.worktreePath, isLocalDraftThread, - settings.newWorktreesStartFromOrigin, + primaryServerSettings.newWorktreesStartFromOrigin, setPendingServerThreadEnvMode, scheduleComposerFocus, setDraftThreadContext, @@ -5521,7 +5546,7 @@ function ChatViewContent(props: ChatViewProps) {
- {/* Error banner */} - setThreadError(activeThread.id, null)} @@ -5567,6 +5590,13 @@ function ChatViewContent(props: ChatViewProps) {
{/* Chat column */}
+ {/* Provider status overlays the timeline without changing its content height. */} +
+ setDismissedProviderStatusBannerKey(providerStatusBannerKey)} + /> +
{/* Messages Wrapper */}
{/* Messages — LegendList handles virtualization and scrolling internally */} @@ -5603,6 +5633,7 @@ function ChatViewContent(props: ChatViewProps) { onIsAtEndChange={onIsAtEndChange} onManualNavigation={cancelTimelineLiveFollowForUserNavigation} hideEmptyPlaceholder={isDraftHeroState} + topFadeEnabled={!hasTimelineTopBanner} /> {/* scroll to end pill — shown when user has scrolled away from the live edge */} @@ -5635,17 +5666,6 @@ function ChatViewContent(props: ChatViewProps) { : "pointer-events-none absolute inset-x-0 bottom-0 z-20 pt-1.5 sm:pt-2" } > - {!isDraftHeroState ? ( -