scopeThreadRef(thread.environmentId, thread.id),
[thread.environmentId, thread.id],
@@ -1208,6 +1290,16 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
branchMismatch={branchMismatch}
terminalStatus={terminalStatus}
terminalProcessCount={terminalProcessCount}
+ compactStatus={
+ compact
+ ? (topStatus?.label ??
+ (variantAction === "unsnooze"
+ ? "Snoozed"
+ : variantAction === "unsettle"
+ ? "Settled"
+ : "Ready"))
+ : undefined
+ }
/>
);
@@ -1254,6 +1346,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
[isRenaming, onStartRename, thread.title, threadRef],
);
const [isFileDragOver, setIsFileDragOver] = useState(false);
+ const [tooltipOpen, setTooltipOpen] = useState(false);
const fileDropHandlers = useMemo(
() =>
onFileDropThreads
@@ -1416,7 +1509,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
// A zero-height boundary also makes dnd-kit scale the source to
// zero. Only projected peers use scaleY as a visibility sentinel.
visibility:
- !sortable.isDragging && sortable.transform?.scaleY === 0
+ sortable.hidden || (!sortable.isDragging && sortable.transform?.scaleY === 0)
? ("hidden" as const)
: undefined,
},
@@ -1557,6 +1650,77 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
)
) : null;
+ if (compact) {
+ return (
+
+
+
+ }
+ >
+ {props.project ? (
+
+ ) : driverKind ? (
+
+ ) : (
+
+ )}
+ {topStatus ? (
+
+ ) : hasUnsentDraft ? (
+
+ ) : null}
+ {props.jumpLabel ? : null}
+
+ {sortable?.isDragging ? (
+ {dragDestination}
+ ) : (
+ detailsTooltip
+ )}
+
+
+ );
+ }
+
if (variant === "slim") {
return (
-
+
-
+
void;
}) {
const { thread } = props;
+ const compactEnabled = useCompactSidebarEnabled();
+ const { state, isMobile } = useSidebar();
+ const compact = compactEnabled && state === "collapsed" && !isMobile;
const threadRef = useMemo(
() => scopeThreadRef(thread.environmentId, thread.id),
[thread.environmentId, thread.id],
@@ -2074,6 +2249,7 @@ const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: {
onClick={props.onSelect}
className={cn(
"flex h-9 w-full cursor-pointer items-center gap-2.5 rounded-md px-2.5 text-left text-sm outline-none",
+ compact && "justify-center px-0",
props.isHighlighted || props.isRouteActive
? "bg-sidebar-row-active text-sidebar-foreground"
: "text-sidebar-muted-foreground/75 hover:bg-sidebar-row-hover hover:text-sidebar-foreground",
@@ -2085,9 +2261,15 @@ const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: {
>
{props.project ? (
+ ) : compact ? (
+
) : null}
- {thread.title}
-
+ {thread.title}
+
{threadTimeLabel(thread)}
@@ -2115,7 +2297,10 @@ export default function Sidebar() {
const projectOrder = useUiStateStore((store) => store.projectOrder);
const threads = useThreadShells();
const router = useRouter();
- const { isMobile, setOpenMobile } = useSidebar();
+ const { isMobile, setOpenMobile, setOpen, state: sidebarState } = useSidebar();
+ const compactEnabled = useCompactSidebarEnabled();
+ const compact = compactEnabled && sidebarState === "collapsed" && !isMobile;
+ const [snoozedFooter, setSnoozedFooter] = useState(null);
const keybindings = useAtomValue(primaryServerKeybindingsAtom);
const confirmThreadDelete = useClientSettings((s) => s.confirmThreadDelete);
const confirmThreadArchive = useClientSettings((s) => s.confirmThreadArchive);
@@ -2687,6 +2872,7 @@ export default function Sidebar() {
[setSettledShelfExpanded],
);
const renderedSettledThreads = useMemo(() => {
+ if (compact) return EMPTY_THREADS;
if (settledShelfExpanded) return visibleSettledThreads;
if (routeThreadKey === null) return EMPTY_THREADS;
const routeThread = visibleSettledThreads.find(
@@ -2694,7 +2880,7 @@ export default function Sidebar() {
scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)) === routeThreadKey,
);
return routeThread === undefined ? EMPTY_THREADS : [routeThread];
- }, [routeThreadKey, settledShelfExpanded, visibleSettledThreads]);
+ }, [compact, routeThreadKey, settledShelfExpanded, visibleSettledThreads]);
// The snoozed shelf is collapsed by default: out of the way, never gone.
// Collapsed threads don't render (and so don't participate in jump
@@ -2922,10 +3108,14 @@ export default function Sidebar() {
const [renamingThreadKey, setRenamingThreadKey] = useState(null);
const [renamingTitle, setRenamingTitle] = useState("");
- const startThreadRename = useCallback((threadRef: ScopedThreadRef, title: string) => {
- setRenamingThreadKey(scopedThreadKey(threadRef));
- setRenamingTitle(title);
- }, []);
+ const startThreadRename = useCallback(
+ (threadRef: ScopedThreadRef, title: string) => {
+ if (compact) setOpen(true);
+ setRenamingThreadKey(scopedThreadKey(threadRef));
+ setRenamingTitle(title);
+ },
+ [compact, setOpen],
+ );
const cancelThreadRename = useCallback(() => setRenamingThreadKey(null), []);
const commitThreadRename = useCallback(
(threadRef: ScopedThreadRef, title: string, originalTitle: string) => {
@@ -3087,8 +3277,18 @@ export default function Sidebar() {
const threadListRef = useRef(null);
const dragLabelOffsetRef = useRef(0);
const restrictBelowPins = useCallback(
- (args) => restrictBelowSidebarLabel(args, dragLabelOffsetRef.current),
- [],
+ (args) =>
+ restrictBelowSidebarLabel(
+ {
+ ...args,
+ // The fixed snoozed shelf shares the main list's drag boundary.
+ containerNodeRect: compact
+ ? (threadListRef.current?.getBoundingClientRect() ?? args.containerNodeRect)
+ : args.containerNodeRect,
+ },
+ dragLabelOffsetRef.current,
+ ),
+ [compact],
);
const listMotionRef = useRef | null>(null);
const attachListMotionRef = useCallback((node: HTMLUListElement | null) => {
@@ -3310,7 +3510,7 @@ export default function Sidebar() {
pinnedThreads.length +
activeThreads.length +
snoozedThreads.length +
- settledThreads.length ===
+ (compact ? 0 : settledThreads.length) ===
0
) {
return [];
@@ -3326,13 +3526,15 @@ export default function Sidebar() {
items.push({ kind: "marker", marker: "snoozed-header" });
items.push(...rowsOf(visibleSnoozedThreads, "snoozed"));
}
- items.push({ kind: "marker", marker: "settled-header" });
- const settledRows = rowsOf(renderedSettledThreads, "settled");
- items.push({ kind: "marker", marker: "settled-placeholder" });
- items.push(...settledRows);
+ if (!compact) {
+ items.push({ kind: "marker", marker: "settled-header" });
+ items.push({ kind: "marker", marker: "settled-placeholder" });
+ items.push(...rowsOf(renderedSettledThreads, "settled"));
+ }
return items;
}, [
activeThreads,
+ compact,
pinnedThreads,
renderedSettledThreads,
settledThreads.length,
@@ -3402,6 +3604,7 @@ export default function Sidebar() {
() =>
createSidebarSortingStrategy({
items: sidebarListItems,
+ compact,
boundaryLabelHeight: SIDEBAR_DRAG_LABEL_HEIGHT,
settledOrder: draggedSettledOrder,
settledExpanded: settledShelfExpanded,
@@ -3410,6 +3613,7 @@ export default function Sidebar() {
snoozedThreadCount: snoozedThreads.length,
}),
[
+ compact,
draggedSettledOrder,
routeThreadKey,
settledShelfExpanded,
@@ -3418,6 +3622,22 @@ export default function Sidebar() {
snoozedThreads.length,
],
);
+ const draggingCompactSnoozed = compact && dragState?.activeSection === "snoozed";
+ const compactSnoozedDragThread =
+ draggingCompactSnoozed && dragState ? threadByKey.get(dragState.activeKey) : undefined;
+ const compactSidebarSortingStrategy = useCallback(
+ (args) => {
+ const item = sidebarListItems[args.index];
+ // Footer rows stay anchored while the main list previews a reorder.
+ if (
+ item?.kind === "thread" ? item.section === "snoozed" : item?.marker === "snoozed-header"
+ ) {
+ return null;
+ }
+ return sidebarSortingStrategy(args);
+ },
+ [sidebarListItems, sidebarSortingStrategy],
+ );
// Hidden and filtered threads keep their keys. Reserve those slots without
// including the rows in the visible drop order or writing to them.
const { pinnedKeysById, activeKeysById } = useMemo(
@@ -4321,7 +4541,16 @@ export default function Sidebar() {
<>
0 ? (
+
+ ) : null
+ }
fixedHeader={
// Lifted above the stage backdrop, whose fade bleeds below the
// header and would otherwise paint across the search row's outline.
@@ -4381,8 +4610,12 @@ export default function Sidebar() {
// popup opens under the field, is at least as wide as it,
// and grows to fit project names up to a cap, past which
// the rows truncate.
- anchor={headerSearchRef}
- className="max-w-[min(18rem,var(--available-width))] overflow-hidden"
+ anchor={compact ? undefined : headerSearchRef}
+ side={compact ? "right" : "bottom"}
+ className={cn(
+ "max-w-[min(18rem,var(--available-width))] overflow-hidden",
+ compact && "min-w-56",
+ )}
>
No threads found
@@ -4556,14 +4792,17 @@ export default function Sidebar() {
modifiers={[
restrictToVerticalAxis,
restrictBelowPins,
- restrictToFirstScrollableAncestor,
+ ...(compact ? [] : [restrictToFirstScrollableAncestor]),
]}
onDragStart={handleThreadDragStart}
onDragOver={handleThreadDragOver}
onDragEnd={handleThreadDragEnd}
>
-
+
- {(bag) => renderThreadRowInner(thread, section, bag)}
+ {(bag) =>
+ renderThreadRowInner(
+ thread,
+ section,
+ draggingCompactSnoozed && bag.isDragging
+ ? { ...bag, hidden: true }
+ : bag,
+ )
+ }
);
};
const from = dragState?.activeSection ?? null;
+ const showDragLabels =
+ from !== null &&
+ (!compact ||
+ dragTargetSection === "active" ||
+ dragTargetSection === "pinned");
+ const snoozedItems: ReactNode[] = [];
const items: ReactNode[] = [
,
];
for (const item of sidebarListItems) {
+ const destination =
+ compact &&
+ (item.kind === "thread"
+ ? item.section === "snoozed"
+ : item.marker === "snoozed-header")
+ ? snoozedItems
+ : items;
if (item.kind === "thread") {
- items.push(renderThreadRow(threadByKey.get(item.key)!, item.section));
+ destination.push(
+ renderThreadRow(threadByKey.get(item.key)!, item.section),
+ );
continue;
}
switch (item.marker) {
@@ -4720,7 +4982,7 @@ export default function Sidebar() {
key="pinned-header"
marker="pinned-header"
label="Pinned"
- visible={from !== null}
+ visible={showDragLabels}
isDropTarget={dragTargetSection === "pinned"}
/>,
);
@@ -4731,7 +4993,7 @@ export default function Sidebar() {
key="pinned-divider"
marker="pinned-divider"
label="Active"
- visible={from !== null}
+ visible={showDragLabels}
isDropTarget={dragTargetSection === "active"}
/>,
);
@@ -4755,7 +5017,7 @@ export default function Sidebar() {
);
break;
case "snoozed-header":
- items.push(
+ destination.push(
+
+ {renderThreadRowInner(compactSnoozedDragThread, "snoozed", {
+ isDragging: true,
+ listeners: undefined,
+ setNodeRef: () => {},
+ transform: null,
+ transition: undefined,
+ })}
+
+ ,
+ document.body,
+ "compact-snoozed-drag",
+ )
+ : null,
+ ];
})()}
- {settledShelfExpanded && hiddenSettledCount > 0 ? (
+ {!compact && settledShelfExpanded && hiddenSettledCount > 0 ? (
-
-
+
+
+ }
+ >
+
+
+ Show {Math.min(hiddenSettledCount, SETTLED_TAIL_PAGE_COUNT)} more
+
+
+
+ Show {Math.min(hiddenSettledCount, SETTLED_TAIL_PAGE_COUNT)} more
+
+
) : null}
@@ -4836,20 +5136,29 @@ export default function Sidebar() {
snoozedThreads.length +
settledThreads.length ===
0 ? (
-
+
{projects.length === 0 ? (
<>
-
No projects yet
+
No projects yet
>
- ) : scopedProjectGroup ? (
+ ) : compact ? null : scopedProjectGroup ? (
`No threads in ${scopedProjectGroup.displayName} yet`
) : (
"No threads yet"
diff --git a/apps/web/src/components/settings/CompactSidebarPreview.tsx b/apps/web/src/components/settings/CompactSidebarPreview.tsx
new file mode 100644
index 000000000000..49fb0754e015
--- /dev/null
+++ b/apps/web/src/components/settings/CompactSidebarPreview.tsx
@@ -0,0 +1,53 @@
+import { useState } from "react";
+
+import { cn } from "~/lib/utils";
+
+export function CompactSidebarPreview() {
+ const [collapsed, setCollapsed] = useState(false);
+
+ return (
+
+ );
+}
diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx
index c9700496f420..efefc79c32cd 100644
--- a/apps/web/src/components/settings/SettingsPanels.tsx
+++ b/apps/web/src/components/settings/SettingsPanels.tsx
@@ -160,6 +160,7 @@ import {
import { searchableSetting } from "./settingsSearch";
import { ProjectFavicon } from "../ProjectFavicon";
import { PanelAnimationsPreview } from "./PanelAnimationsPreview";
+import { CompactSidebarPreview } from "./CompactSidebarPreview";
const ENVIRONMENT_IDENTIFICATION_LABELS: Record
= {
artwork: "Artwork",
@@ -501,6 +502,9 @@ export function useSettingsRestore(onRestored?: () => void) {
...(theme !== "system" ? ["Theme"] : []),
...(!followSystem ? ["Follow system"] : []),
...(themeHalves !== null ? ["Theme mix"] : []),
+ ...(settings.compactSidebarEnabled !== DEFAULT_UNIFIED_SETTINGS.compactSidebarEnabled
+ ? ["Compact sidebar"]
+ : []),
...(settings.appearanceContrast !== DEFAULT_UNIFIED_SETTINGS.appearanceContrast
? ["Contrast"]
: []),
@@ -605,6 +609,7 @@ export function useSettingsRestore(onRestored?: () => void) {
settings.browserLinkTarget,
settings.browserAutoShowFloatingPreview,
settings.appearanceContrast,
+ settings.compactSidebarEnabled,
settings.diffColorScheme,
settings.enableAgentBrowserAccess,
settings.confirmQuit,
@@ -712,6 +717,7 @@ export function useSettingsRestore(onRestored?: () => void) {
}
updateSettings({
appearanceContrast: DEFAULT_UNIFIED_SETTINGS.appearanceContrast,
+ compactSidebarEnabled: DEFAULT_UNIFIED_SETTINGS.compactSidebarEnabled,
diffColorScheme: DEFAULT_UNIFIED_SETTINGS.diffColorScheme,
timestampFormat: DEFAULT_UNIFIED_SETTINGS.timestampFormat,
notificationMode: DEFAULT_UNIFIED_SETTINGS.notificationMode,
@@ -1356,6 +1362,27 @@ export function AppearanceSettingsPanel() {
/>
+
+ }
+ />
+
+
);
diff --git a/apps/web/src/components/settings/SettingsSidebarNav.tsx b/apps/web/src/components/settings/SettingsSidebarNav.tsx
index c29068934efa..c206174a397a 100644
--- a/apps/web/src/components/settings/SettingsSidebarNav.tsx
+++ b/apps/web/src/components/settings/SettingsSidebarNav.tsx
@@ -24,6 +24,7 @@ import {
XIcon,
} from "lucide-react";
import { useLocation, useNavigate } from "@tanstack/react-router";
+import { useCompactSidebarEnabled } from "../../hooks/useSettings";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
@@ -111,12 +112,13 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) {
(item) => item.to !== "/settings/projects" || isSettingsOverviewVisible(scopeSearch),
);
const { isMobile, setOpenMobile, open, setOpen } = useSidebar();
+ const compactSidebarEnabled = useCompactSidebarEnabled();
const searchInputRef = useRef
(null);
const [query, setQuery] = useState("");
const [activeResultIndex, setActiveResultIndex] = useState(0);
const searchableItems = useAvailableSettingsSearchItems();
const results = useMemo(() => searchSettings(query, searchableItems), [query, searchableItems]);
- const isSearching = query.trim().length > 0;
+ const isSearching = query.trim().length > 0 && !(compactSidebarEnabled && !isMobile && !open);
const hasResults = results.length > 0;
useEffect(() => {
@@ -233,7 +235,18 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) {
<>
-
+
{
+ setOpen(true);
+ requestAnimationFrame(() => searchInputRef.current?.focus());
+ }}
+ >
+
+
+
handleSectionClick(item.to)}
>
- {item.label}
+
+ {item.label}
+
);
@@ -343,10 +360,12 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) {
-
-
-
-
+
+
+
+
+
+
diff --git a/apps/web/src/components/settings/settingsSearch.ts b/apps/web/src/components/settings/settingsSearch.ts
index 9a8af52b90f5..be62ad6b25b5 100644
--- a/apps/web/src/components/settings/settingsSearch.ts
+++ b/apps/web/src/components/settings/settingsSearch.ts
@@ -159,6 +159,12 @@ export const SETTINGS_SEARCH_ITEMS = [
title: "Panel animations",
to: "/settings/appearance",
},
+ {
+ id: "compact-sidebar",
+ title: "Compact sidebar",
+ to: "/settings/appearance",
+ searchTerms: ["collapsed icons rail hover navigation preview"],
+ },
{
id: "environment-identification",
title: "Environment identification",
diff --git a/apps/web/src/components/sidebar/SidebarChrome.tsx b/apps/web/src/components/sidebar/SidebarChrome.tsx
index afbbf7671dfc..2f65cf8c3697 100644
--- a/apps/web/src/components/sidebar/SidebarChrome.tsx
+++ b/apps/web/src/components/sidebar/SidebarChrome.tsx
@@ -86,7 +86,7 @@ function SidebarBrand({ onBackdrop }: { onBackdrop: boolean }) {
+
{currentFooterPage ? (
-
+
- Back
+ Back
) : (
@@ -224,8 +224,10 @@ export const SidebarUtilityMenu = memo(function SidebarUtilityMenu() {
export const SidebarChromeFooter = memo(function SidebarChromeFooter() {
return (
-
-
+
+
+
+
);
diff --git a/apps/web/src/components/sidebar/SidebarThreadHeader.tsx b/apps/web/src/components/sidebar/SidebarThreadHeader.tsx
index 878235615b39..d0718d9856f8 100644
--- a/apps/web/src/components/sidebar/SidebarThreadHeader.tsx
+++ b/apps/web/src/components/sidebar/SidebarThreadHeader.tsx
@@ -20,9 +20,10 @@ import {
} from "react";
import { cn } from "~/lib/utils";
+import { useCompactSidebarEnabled } from "../../hooks/useSettings";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
-import { SidebarMenuButton } from "../ui/sidebar";
+import { SidebarMenuButton, useSidebar } from "../ui/sidebar";
import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
export interface SidebarThreadHeaderProps {
@@ -69,6 +70,9 @@ export function SidebarThreadHeader({
activeSearchResultIndex,
onClearSearch,
}: SidebarThreadHeaderProps) {
+ const compactEnabled = useCompactSidebarEnabled();
+ const { state, isMobile, setOpen } = useSidebar();
+ const compact = compactEnabled && state === "collapsed" && !isMobile;
const resultsVisible = isSearching && searchResultCount > 0;
// Results shrink as the query narrows, so the active index can outrun the
// list; pointing aria-activedescendant at a removed option strands the
@@ -79,10 +83,24 @@ export function SidebarThreadHeader({
: "New thread";
return (
-
+
+ {compact ? (
+
{
+ setOpen(true);
+ requestAnimationFrame(() => searchInputRef.current?.focus());
+ }}
+ >
+
+
+ ) : null}
{/* Segmented well: the icons read as one control instead of three loose
buttons competing with the search field beside them. */}
-
+
{hasProjects ? (
<>
{projectScope}
diff --git a/apps/web/src/components/sidebar/SidebarUpdatePill.tsx b/apps/web/src/components/sidebar/SidebarUpdatePill.tsx
index a94b7801ecfd..8c04eec6fe7d 100644
--- a/apps/web/src/components/sidebar/SidebarUpdatePill.tsx
+++ b/apps/web/src/components/sidebar/SidebarUpdatePill.tsx
@@ -348,7 +348,7 @@ function SidebarUpdateControl() {
);
return (
-
+
{
diff --git a/apps/web/src/components/ui/sidebar.tsx b/apps/web/src/components/ui/sidebar.tsx
index a7abcb3bb550..c8938497cc04 100644
--- a/apps/web/src/components/ui/sidebar.tsx
+++ b/apps/web/src/components/ui/sidebar.tsx
@@ -690,9 +690,11 @@ function SidebarSeparator({ className, ...props }: React.ComponentProps & {
fixedHeader?: React.ReactNode;
+ fixedFooter?: React.ReactNode;
}) {
return (
<>
@@ -716,6 +718,7 @@ function SidebarContent({
{...props}
/>
+ {fixedFooter ? {fixedFooter}
: null}
>
);
}
diff --git a/apps/web/src/hooks/useSettings.ts b/apps/web/src/hooks/useSettings.ts
index 194cc36c55f4..fa4b8bc8fd31 100644
--- a/apps/web/src/hooks/useSettings.ts
+++ b/apps/web/src/hooks/useSettings.ts
@@ -379,6 +379,13 @@ export function useLegacySidebarEnabled(): boolean {
return settingsHydrated && legacySidebarEnabled;
}
+/** Keep the default collapsed sidebar until persisted client settings hydrate. */
+export function useCompactSidebarEnabled(): boolean {
+ const settingsHydrated = useClientSettingsHydrated();
+ const compactSidebarEnabled = useClientSettingsValue().compactSidebarEnabled;
+ return settingsHydrated && compactSidebarEnabled;
+}
+
/** Read current settings for one environment, merged with client-local preferences. */
export function useEnvironmentSettings(
environmentId: EnvironmentId,
diff --git a/apps/web/src/workspaceTitlebar.ts b/apps/web/src/workspaceTitlebar.ts
index b481221e63aa..aed95897cc55 100644
--- a/apps/web/src/workspaceTitlebar.ts
+++ b/apps/web/src/workspaceTitlebar.ts
@@ -1,2 +1,2 @@
export const COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS =
- "[[data-sidebar-state=collapsed]_&]:pl-[var(--workspace-titlebar-content-left)]";
+ "[[data-sidebar-state=collapsed]_&]:pl-[var(--workspace-titlebar-content-left)] [[data-sidebar-state=collapsed]:has([data-side=left][data-collapsible=icon])_&]:pl-[max(calc(env(safe-area-inset-left)+1.25rem),calc(var(--workspace-titlebar-content-left)-var(--sidebar-width-icon)))]";
diff --git a/packages/contracts/src/settings.ts b/packages/contracts/src/settings.ts
index 004a53b320e8..826440556d2c 100644
--- a/packages/contracts/src/settings.ts
+++ b/packages/contracts/src/settings.ts
@@ -436,6 +436,7 @@ export const ClientSettingsSchema = Schema.Struct({
// old keys, so everyone, including prior beta opt-outs, resets to the new
// default sidebar.
legacySidebarEnabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
+ compactSidebarEnabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
sidebarProjectGroupingMode: SidebarProjectGroupingMode.pipe(
Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_PROJECT_GROUPING_MODE)),
),
@@ -1490,6 +1491,7 @@ export const ClientSettingsPatch = Schema.Struct({
proactivePanelsEnabled: Schema.optionalKey(Schema.Boolean),
showSkillsInSlashMenu: Schema.optionalKey(Schema.Boolean),
legacySidebarEnabled: Schema.optionalKey(Schema.Boolean),
+ compactSidebarEnabled: Schema.optionalKey(Schema.Boolean),
sidebarProjectGroupingMode: Schema.optionalKey(SidebarProjectGroupingMode),
sidebarProjectGroupingOverrides: Schema.optionalKey(
Schema.Record(TrimmedNonEmptyString, SidebarProjectGroupingMode),