authorgravatar for maria@kuuro.netmaria <maria@kuuro.net> 2026-08-19 15:50:25-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-19 15:50:25-04:00
log07f8027d9a542f72b12db927caceebb2c9c72aa7
tree22131031c7f6700663e4e90ab289250527501dc3
parentcf251c3bd00ff0e0cc90a78b20c043cf58f330ad
signature Signed by PGP key B5690EEEBB952194

feat(web): unify workspace navigation (#7153)


12 files changed, 167 insertions(+), 194 deletions(-)

apps/web/src/components/ChatView.tsx+6-15
......@@ -170,7 +170,6 @@ import {
170170 WifiOffIcon,
171171} from "lucide-react";
172172import { cn, randomHex } from "~/lib/utils";
173import { COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS } from "~/workspaceTitlebar";
174173import { stackedThreadToast, toastManager } from "./ui/toast";
175174import { decodeProjectScriptKeybindingRule } from "~/lib/projectScriptKeybindings";
176175import { type NewProjectScriptInput } from "./ProjectScriptsControl";
......@@ -259,6 +258,7 @@ import { ChatHeader } from "./chat/ChatHeader";
259258import { PanelLayoutControls, RightPanelMaximizeControl } from "./chat/PanelLayoutControls";
260259import { type ExpandedImagePreview } from "./chat/ExpandedImagePreview";
261260import { NoActiveThreadState } from "./NoActiveThreadState";
261import { WorkspacePageHeader } from "./WorkspacePageHeader";
262262import {
263263 resolveEffectiveEnvMode,
264264 resolveLocalCheckoutBranchMismatch,
......@@ -6240,20 +6240,11 @@ function ChatViewContent(props: ChatViewProps) {
62406240 data-chat-column-maximized-away={rightPanelMaximized ? "true" : "false"}
62416241 >
62426242 {/* Top bar */}
6243 <header
6243 <WorkspacePageHeader
62446244 data-chat-header
6245 className={cn(
6246 "bg-background transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none",
6247 isElectron
6248 ? cn(
6249 "drag-region relative flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center px-3 sm:px-5",
6250 reserveTitleBarControlInset &&
6251 !inlineRightPanelOwnsTitleBar &&
6252 "wco:pr-[var(--workspace-native-controls-inset)]",
6253 )
6254 : "flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center pl-[calc(env(safe-area-inset-left)+0.75rem)] pr-[calc(env(safe-area-inset-right)+0.75rem)] sm:pl-[calc(env(safe-area-inset-left)+1.25rem)] sm:pr-[calc(env(safe-area-inset-right)+1.25rem)]",
6255 COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS,
6256 )}
6245 electron={isElectron}
6246 reserveNativeControls={reserveTitleBarControlInset && !inlineRightPanelOwnsTitleBar}
6247 className="relative bg-background"
62576248 >
62586249 {!rightPanelOpen ? panelLayoutControls : null}
62596250 <ChatHeader
......@@ -6284,7 +6275,7 @@ function ChatViewContent(props: ChatViewProps) {
62846275 onUpdateProjectScript={updateProjectScript}
62856276 onDeleteProjectScript={deleteProjectScript}
62866277 />
6287 </header>
6278 </WorkspacePageHeader>
62886279
62896280 <ThreadErrorBanner
62906281 error={visibleThreadError}
apps/web/src/components/NoActiveThreadState.tsx+4-15
......@@ -1,26 +1,15 @@
11import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from "./ui/empty";
22import { SidebarInset } from "./ui/sidebar";
33import { isElectron } from "../env";
4import { cn } from "~/lib/utils";
5import { COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS } from "~/workspaceTitlebar";
4import { WorkspacePageHeader } from "./WorkspacePageHeader";
65
76export function NoActiveThreadState() {
87 return (
98 <SidebarInset className="h-dvh min-h-0 overflow-hidden overscroll-y-none bg-background text-foreground">
109 <div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-x-hidden bg-background">
11 <header
12 className={cn(
13 "border-b border-border px-3 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none sm:px-5",
14 isElectron
15 ? "drag-region flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center"
16 : "flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center",
17 COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS,
18 )}
19 >
10 <WorkspacePageHeader electron={isElectron} className="border-b border-border">
2011 {isElectron ? (
21 <span className="text-xs text-muted-foreground/50 wco:pr-[var(--workspace-native-controls-inset)]">
22 No active thread
23 </span>
12 <span className="text-xs text-muted-foreground/50">No active thread</span>
2413 ) : (
2514 <div className="flex items-center gap-2">
2615 <span className="text-sm font-medium text-foreground md:text-muted-foreground/60">
......@@ -28,7 +17,7 @@ export function NoActiveThreadState() {
2817 </span>
2918 </div>
3019 )}
31 </header>
20 </WorkspacePageHeader>
3221
3322 <Empty className="flex-1">
3423 <div className="w-full max-w-lg px-8 py-12">
apps/web/src/components/WorkspacePageHeader.tsx created+28
......@@ -0,0 +1,28 @@
1import type { ComponentPropsWithoutRef } from "react";
2
3import { cn } from "../lib/utils";
4import { COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS } from "../workspaceTitlebar";
5
6/** Shared workspace top-bar geometry. */
7export function WorkspacePageHeader({
8 electron = false,
9 reserveNativeControls = electron,
10 className,
11 ...props
12}: ComponentPropsWithoutRef<"header"> & {
13 readonly electron?: boolean;
14 readonly reserveNativeControls?: boolean;
15}) {
16 return (
17 <header
18 className={cn(
19 "flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center gap-3 pl-[calc(env(safe-area-inset-left)+0.75rem)] pr-[calc(env(safe-area-inset-right)+0.75rem)] transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none sm:pl-[calc(env(safe-area-inset-left)+1.25rem)] sm:pr-[calc(env(safe-area-inset-right)+1.25rem)]",
20 electron && "drag-region",
21 reserveNativeControls && "wco:pr-[var(--workspace-native-controls-inset)]",
22 COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS,
23 className,
24 )}
25 {...props}
26 />
27 );
28}
apps/web/src/components/settings/DiagnosticsSettings.tsx+1-1
......@@ -992,7 +992,7 @@ export function DiagnosticsSettingsPanel() {
992992 : false;
993993
994994 return (
995 <SettingsPageContainer className="max-w-6xl gap-10">
995 <SettingsPageContainer width="expanded" className="gap-10">
996996 <ResourceTelemetryDiagnostics />
997997
998998 <SettingsSection
apps/web/src/components/settings/KeybindingsSettings.tsx+1-1
......@@ -1195,7 +1195,7 @@ export function KeybindingsSettingsPanel() {
11951195 );
11961196
11971197 return (
1198 <SettingsPageContainer className="max-w-5xl">
1198 <SettingsPageContainer width="wide">
11991199 <SettingsSection
12001200 {...searchableSetting("keybindings")}
12011201 headerAction={
apps/web/src/components/settings/ProjectSettingsPanel.tsx+4-22
......@@ -81,8 +81,6 @@ import {
8181 type NewProjectScriptInput,
8282 type ProjectScriptEditorRequest,
8383} from "../projectScriptEditor";
84import { cn } from "../../lib/utils";
85import { COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS } from "../../workspaceTitlebar";
8684import { Button } from "../ui/button";
8785import { Input } from "../ui/input";
8886import {
......@@ -103,6 +101,7 @@ import {
103101 WorkspaceBreadcrumbItem,
104102 WorkspaceBreadcrumbSeparator,
105103} from "../WorkspaceBreadcrumb";
104import { WorkspacePageHeader } from "../WorkspacePageHeader";
106105import {
107106 SettingResetButton,
108107 SettingsPageContainer,
......@@ -175,26 +174,9 @@ export function ProjectSettingsPage({ projectKey }: { projectKey: string }) {
175174 return (
176175 <SidebarInset className="h-dvh min-h-0 overflow-hidden overscroll-y-none bg-background text-foreground isolate">
177176 <div className="flex min-h-0 min-w-0 flex-1 flex-col bg-background text-foreground">
178 {!isElectron && (
179 <header
180 className={cn(
181 "flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center px-3 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none sm:px-5",
182 COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS,
183 )}
184 >
185 <ProjectSettingsBreadcrumb projectKey={projectKey} />
186 </header>
187 )}
188 {isElectron && (
189 <div
190 className={cn(
191 "drag-region flex h-[52px] shrink-0 items-center px-5 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none wco:h-[env(titlebar-area-height)] wco:pr-[calc(100vw-env(titlebar-area-width)-env(titlebar-area-x)+1em)]",
192 COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS,
193 )}
194 >
195 <ProjectSettingsBreadcrumb projectKey={projectKey} />
196 </div>
197 )}
177 <WorkspacePageHeader electron={isElectron}>
178 <ProjectSettingsBreadcrumb projectKey={projectKey} />
179 </WorkspacePageHeader>
198180 <ProjectSettingsPanel projectKey={projectKey} />
199181 </div>
200182 </SidebarInset>
apps/web/src/components/settings/SettingsSidebarNav.tsx+5-22
......@@ -9,7 +9,6 @@ import {
99} from "react";
1010import {
1111 ArchiveIcon,
12 ArrowLeftIcon,
1312 BlocksIcon,
1413 BotIcon,
1514 GitBranchIcon,
......@@ -20,7 +19,7 @@ import {
2019 Settings2Icon,
2120 XIcon,
2221} from "lucide-react";
23import { useCanGoBack, useLocation, useNavigate } from "@tanstack/react-router";
22import { useLocation, useNavigate } from "@tanstack/react-router";
2423
2524import { Button } from "../ui/button";
2625import { Input } from "../ui/input";
......@@ -35,6 +34,7 @@ import {
3534 useSidebar,
3635} from "../ui/sidebar";
3736import { T3ConnectSidebarAvatar, T3ConnectSidebarSignIn } from "../clerk/T3ConnectSidebarSignIn";
37import { SidebarUtilityMenu } from "../sidebar/SidebarChrome";
3838import { scrollToSettingsTarget } from "./settingsLayout";
3939import {
4040 searchSettings,
......@@ -74,7 +74,6 @@ function SettingsSectionIcon({ to }: { to: SettingsPath }) {
7474export function SettingsSidebarNav({ pathname }: { pathname: string }) {
7575 const navigate = useNavigate();
7676 const currentHash = useLocation({ select: (location) => location.hash });
77 const canGoBack = useCanGoBack();
7877 const { isMobile, setOpenMobile, open, setOpen } = useSidebar();
7978 const searchInputRef = useRef<HTMLInputElement>(null);
8079 const [query, setQuery] = useState("");
......@@ -178,17 +177,6 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) {
178177 },
179178 [activeResultIndex, clearSearch, handleSearchResultClick, isSearching, results],
180179 );
181 const handleBackClick = useCallback(() => {
182 if (isMobile) {
183 setOpenMobile(false);
184 }
185 if (canGoBack) {
186 window.history.back();
187 return;
188 }
189 void navigate({ to: "/" });
190 }, [canGoBack, isMobile, navigate, setOpenMobile]);
191
192180 return (
193181 <>
194182 <SidebarContent className="overflow-x-hidden">
......@@ -298,14 +286,9 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) {
298286 <SidebarFooter className="p-[var(--sidebar-content-inset)]">
299287 <T3ConnectSidebarSignIn />
300288 <div className="flex items-center gap-1">
301 <SidebarMenu className="min-w-0 flex-1">
302 <SidebarMenuItem>
303 <SidebarMenuButton onClick={handleBackClick}>
304 <ArrowLeftIcon />
305 <span>Back</span>
306 </SidebarMenuButton>
307 </SidebarMenuItem>
308 </SidebarMenu>
289 <div className="min-w-0 flex-1">
290 <SidebarUtilityMenu />
291 </div>
309292 <T3ConnectSidebarAvatar />
310293 </div>
311294 </SidebarFooter>
apps/web/src/components/settings/settingsLayout.tsx+18-2
......@@ -15,6 +15,14 @@ import { cn } from "../../lib/utils";
1515import { Button } from "../ui/button";
1616import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
1717
18type SettingsPageWidth = "readable" | "wide" | "expanded";
19
20const SETTINGS_PAGE_WIDTH_CLASS: Record<SettingsPageWidth, string> = {
21 readable: "max-w-4xl",
22 wide: "max-w-5xl",
23 expanded: "max-w-6xl",
24};
25
1826interface SettingsSearchTargetContextValue {
1927 readonly targetId: string | null;
2028 readonly onTargetHandled: () => void;
......@@ -233,9 +241,11 @@ export function SettingResetButton({
233241export function SettingsPageContainer({
234242 children,
235243 className,
244 width = "readable",
236245}: {
237246 children: ReactNode;
238247 className?: string;
248 width?: SettingsPageWidth;
239249}) {
240250 const navigate = useNavigate();
241251 const hash = useLocation({ select: (location) => location.hash });
......@@ -247,10 +257,16 @@ export function SettingsPageContainer({
247257 return (
248258 <SettingsSearchTargetProvider targetId={targetId} onTargetHandled={clearTargetHash}>
249259 <div
250 className="topbar-scroll-fade scrollbar-gutter-both flex-1 overflow-y-auto px-4 pt-10 pb-7 sm:px-8 sm:pt-12 sm:pb-10"
260 className="topbar-scroll-fade scrollbar-gutter-both flex-1 overflow-y-auto [--topbar-scroll-fade-height:1.5rem] sm:[--topbar-scroll-fade-height:1.5rem]"
251261 data-settings-page-scroll
252262 >
253 <div className={cn("mx-auto flex w-full max-w-4xl flex-col gap-12", className)}>
263 <div
264 className={cn(
265 "mx-auto flex w-full flex-col gap-12 px-5 pt-6 pb-12 sm:px-6",
266 SETTINGS_PAGE_WIDTH_CLASS[width],
267 className,
268 )}
269 >
254270 {children}
255271 </div>
256272 </div>
apps/web/src/components/sidebar/SidebarChrome.tsx+78-68
......@@ -4,8 +4,9 @@ import {
44 GitPullRequestIcon,
55 SettingsIcon,
66} from "lucide-react";
7import type { ReactNode } from "react";
78import { memo, useCallback } from "react";
8import { Link, useLocation, useNavigate } from "@tanstack/react-router";
9import { Link, useCanGoBack, useLocation, useNavigate } from "@tanstack/react-router";
910
1011import { useEnvironmentIdentificationMode } from "../../hooks/useSettings";
1112import { cn } from "../../lib/utils";
......@@ -117,16 +118,44 @@ function T3Wordmark() {
117118 );
118119}
119120
120export const SidebarChromeFooter = memo(function SidebarChromeFooter() {
121function SidebarUtilityItem({
122 icon,
123 label,
124 onClick,
125}: {
126 icon: ReactNode;
127 label: string;
128 onClick: () => void;
129}) {
130 return (
131 <SidebarMenuItem className="shrink-0">
132 <Tooltip>
133 <TooltipTrigger
134 render={
135 <SidebarMenuButton aria-label={label} onClick={onClick} size="icon">
136 {icon}
137 </SidebarMenuButton>
138 }
139 />
140 <TooltipPopup side="top">{label}</TooltipPopup>
141 </Tooltip>
142 </SidebarMenuItem>
143 );
144}
145
146export const SidebarUtilityMenu = memo(function SidebarUtilityMenu() {
121147 const navigate = useNavigate();
148 const canGoBack = useCanGoBack();
122149 const { isMobile, setOpenMobile } = useSidebar();
123150 const currentFooterPage = useLocation({
124151 select: (location) =>
125 location.pathname === "/usage"
126 ? "usage"
127 : location.pathname === "/pull-requests"
128 ? "pull-requests"
129 : null,
152 /^\/settings(?:\/|$)/.test(location.pathname)
153 ? "settings"
154 : location.pathname === "/usage"
155 ? "usage"
156 : location.pathname === "/pull-requests"
157 ? "pull-requests"
158 : null,
130159 });
131160 const { environments } = useEnvironments();
132161 // The page reads every connected server, so one of them offering pull requests is enough for
......@@ -157,73 +186,54 @@ export const SidebarChromeFooter = memo(function SidebarChromeFooter() {
157186
158187 const handleBackClick = useCallback(() => {
159188 closeMobileSidebar();
189 if (canGoBack) {
190 window.history.back();
191 return;
192 }
160193 void navigate({ to: "/" });
161 }, [closeMobileSidebar, navigate]);
194 }, [canGoBack, closeMobileSidebar, navigate]);
162195
196 return (
197 <SidebarMenu className="flex-row items-center">
198 {currentFooterPage ? (
199 <SidebarMenuItem className="min-w-0 flex-1">
200 <SidebarMenuButton onClick={handleBackClick}>
201 <ArrowLeftIcon />
202 <span>Back</span>
203 </SidebarMenuButton>
204 </SidebarMenuItem>
205 ) : (
206 <>
207 <SidebarUtilityItem
208 icon={<SettingsIcon />}
209 label="Settings"
210 onClick={handleSettingsClick}
211 />
212 {pullRequestsSupported ? (
213 <SidebarUtilityItem
214 icon={<GitPullRequestIcon />}
215 label="Pull Requests"
216 onClick={handlePullRequestsClick}
217 />
218 ) : null}
219 <SidebarUtilityItem
220 icon={<ChartNoAxesColumnIcon />}
221 label="Usage"
222 onClick={handleUsageClick}
223 />
224 </>
225 )}
226 <SidebarUpdatePill />
227 </SidebarMenu>
228 );
229});
230
231export const SidebarChromeFooter = memo(function SidebarChromeFooter() {
163232 return (
164233 <SidebarFooter className="p-[var(--sidebar-content-inset)]">
165234 <SidebarProviderUpdatePill />
166235 <SidebarUpdateArchitectureWarning />
167 <SidebarMenu className="flex-row items-center">
168 {currentFooterPage ? (
169 <SidebarMenuItem className="min-w-0 flex-1">
170 <SidebarMenuButton onClick={handleBackClick}>
171 <ArrowLeftIcon />
172 <span>Back</span>
173 </SidebarMenuButton>
174 </SidebarMenuItem>
175 ) : (
176 <>
177 <SidebarMenuItem className="shrink-0">
178 <Tooltip>
179 <TooltipTrigger
180 render={
181 <SidebarMenuButton
182 aria-label="Settings"
183 onClick={handleSettingsClick}
184 size="icon"
185 >
186 <SettingsIcon />
187 </SidebarMenuButton>
188 }
189 />
190 <TooltipPopup side="top">Settings</TooltipPopup>
191 </Tooltip>
192 </SidebarMenuItem>
193 {pullRequestsSupported ? (
194 <SidebarMenuItem className="shrink-0">
195 <Tooltip>
196 <TooltipTrigger
197 render={
198 <SidebarMenuButton
199 aria-label="Pull Requests"
200 onClick={handlePullRequestsClick}
201 size="icon"
202 >
203 <GitPullRequestIcon />
204 </SidebarMenuButton>
205 }
206 />
207 <TooltipPopup side="top">Pull Requests</TooltipPopup>
208 </Tooltip>
209 </SidebarMenuItem>
210 ) : null}
211 <SidebarMenuItem className="shrink-0">
212 <Tooltip>
213 <TooltipTrigger
214 render={
215 <SidebarMenuButton aria-label="Usage" onClick={handleUsageClick} size="icon">
216 <ChartNoAxesColumnIcon />
217 </SidebarMenuButton>
218 }
219 />
220 <TooltipPopup side="top">Usage</TooltipPopup>
221 </Tooltip>
222 </SidebarMenuItem>
223 </>
224 )}
225 <SidebarUpdatePill />
226 </SidebarMenu>
236 <SidebarUtilityMenu />
227237 </SidebarFooter>
228238 );
229239});
apps/web/src/routes/-chatIndexTitlebar.test.ts+9-3
......@@ -15,9 +15,15 @@ describe("hosted static onboarding header", () => {
1515
1616 const onboardingHeader = routeSource.slice(onboardingStart, onboardingEnd);
1717
18 expect(onboardingHeader).toContain("h-[var(--workspace-topbar-height)]");
19 expect(onboardingHeader).toContain("min-h-[var(--workspace-topbar-height)]");
20 expect(onboardingHeader).toContain("COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS");
18 expect(onboardingHeader).toContain("<WorkspacePageHeader");
2119 expect(onboardingHeader).not.toMatch(/(?:^|\s)(?:[\w-]+:)*py-/);
20
21 const headerSource = NodeFS.readFileSync(
22 new URL("../components/WorkspacePageHeader.tsx", import.meta.url),
23 "utf8",
24 );
25 expect(headerSource).toContain("h-[var(--workspace-topbar-height)]");
26 expect(headerSource).toContain("min-h-[var(--workspace-topbar-height)]");
27 expect(headerSource).toContain("COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS");
2228 });
2329});
apps/web/src/routes/_chat.index.tsx+3-9
......@@ -8,6 +8,7 @@ import { sortScopedProjectsForSidebar } from "../components/Sidebar.logic";
88import { Button } from "../components/ui/button";
99import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from "../components/ui/empty";
1010import { SidebarInset } from "../components/ui/sidebar";
11import { WorkspacePageHeader } from "../components/WorkspacePageHeader";
1112import { useNewThreadHandler } from "../hooks/useHandleNewThread";
1213import {
1314 useAllEnvironmentShellsBootstrapped,
......@@ -17,8 +18,6 @@ import {
1718import { useEnvironments } from "../state/environments";
1819import { APP_DISPLAY_NAME } from "~/branding";
1920import { hasCloudPublicConfig } from "~/cloud/publicConfig";
20import { cn } from "~/lib/utils";
21import { COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS } from "~/workspaceTitlebar";
2221
2322function ChatIndexRouteView() {
2423 const { authGateState } = Route.useRouteContext();
......@@ -143,18 +142,13 @@ function HostedStaticOnboardingState() {
143142 return (
144143 <SidebarInset className="h-dvh min-h-0 overflow-hidden overscroll-y-none bg-background text-foreground">
145144 <div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-x-hidden bg-background">
146 <header
147 className={cn(
148 "flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center border-b border-border px-3 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none sm:px-5",
149 COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS,
150 )}
151 >
145 <WorkspacePageHeader className="border-b border-border">
152146 <div className="flex items-center gap-2">
153147 <span className="text-sm font-medium text-foreground md:text-muted-foreground/60">
154148 {APP_DISPLAY_NAME}
155149 </span>
156150 </div>
157 </header>
151 </WorkspacePageHeader>
158152
159153 <Empty className="flex-1">
160154 <div className="w-full max-w-xl rounded-3xl border border-border/55 bg-card/20 px-8 py-12 shadow-sm/5">
apps/web/src/routes/settings.tsx+10-36
......@@ -13,9 +13,8 @@ import { useSettingsRestore } from "../components/settings/SettingsPanels";
1313import { SettingsBreadcrumb } from "../components/settings/SettingsBreadcrumb";
1414import { Button } from "../components/ui/button";
1515import { SidebarInset } from "../components/ui/sidebar";
16import { WorkspacePageHeader } from "../components/WorkspacePageHeader";
1617import { isElectron } from "../env";
17import { cn } from "~/lib/utils";
18import { COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS } from "~/workspaceTitlebar";
1918
2019function RestoreDefaultsButton({ onRestored }: { onRestored: () => void }) {
2120 const { changedSettingLabels, restoreDefaults } = useSettingsRestore(onRestored);
......@@ -72,41 +71,16 @@ function SettingsContentLayout() {
7271 return (
7372 <SidebarInset className="h-dvh min-h-0 overflow-hidden overscroll-y-none bg-background text-foreground isolate">
7473 <div className="flex min-h-0 min-w-0 flex-1 flex-col bg-background text-foreground">
75 {!isElectron && (
76 <header
77 className={cn(
78 "flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center px-3 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none sm:px-5",
79 COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS,
80 )}
81 >
82 <div className="flex w-full items-center gap-2">
83 <SettingsBreadcrumb pathname={location.pathname} />
84 {showRestoreDefaults ? (
85 <div className="ms-auto flex items-center gap-2">
86 <RestoreDefaultsButton onRestored={handleRestored} />
87 </div>
88 ) : null}
89 </div>
90 </header>
91 )}
92
93 {isElectron && (
94 <div
95 className={cn(
96 "drag-region flex h-[52px] shrink-0 items-center px-5 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none wco:h-[env(titlebar-area-height)] wco:pr-[calc(100vw-env(titlebar-area-width)-env(titlebar-area-x)+1em)]",
97 COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS,
98 )}
99 >
100 <div className="flex w-full items-center gap-2">
101 <SettingsBreadcrumb pathname={location.pathname} />
102 {showRestoreDefaults ? (
103 <div className="ms-auto flex items-center gap-2">
104 <RestoreDefaultsButton onRestored={handleRestored} />
105 </div>
106 ) : null}
107 </div>
74 <WorkspacePageHeader electron={isElectron}>
75 <div className="flex w-full items-center gap-3">
76 <SettingsBreadcrumb pathname={location.pathname} />
77 {showRestoreDefaults ? (
78 <div className="ms-auto flex items-center gap-2">
79 <RestoreDefaultsButton onRestored={handleRestored} />
80 </div>
81 ) : null}
10882 </div>
109 )}
83 </WorkspacePageHeader>
11084
11185 <div key={restoreSignal} className="min-h-0 flex flex-1 flex-col">
11286 <Outlet />