+
Projects
+
No projects yet
)}
diff --git a/apps/web/src/components/Sidebar.drag.ts b/apps/web/src/components/Sidebar.drag.ts
index 0b5710c671fc..bfd112d18998 100644
--- a/apps/web/src/components/Sidebar.drag.ts
+++ b/apps/web/src/components/Sidebar.drag.ts
@@ -103,7 +103,6 @@ export function createSidebarSortingStrategy(input: {
snoozedThreadCount?: number;
cardHeight?: number;
slimHeight?: number;
- compact?: boolean;
/** Space each pinned boundary opens for its label while dragging. The
* markers stay zero height at rest, so nothing is reserved until pickup. */
boundaryLabelHeight?: number;
@@ -141,14 +140,11 @@ export function createSidebarSortingStrategy(input: {
else slimHeight ??= rects[index]?.height;
if (item.key !== active.key) groups[item.section].push(item);
}
- // Compact icons use h-7; expanded cards include their vertical padding.
- const scale = input.compact
- ? (cardHeight ?? slimHeight ?? 28) / 28
- : slimHeight !== undefined
- ? slimHeight / 36
- : (headerScale ?? (cardHeight ?? 82) / 82);
- cardHeight ??= (input.compact ? 28 : 82) * scale;
- slimHeight ??= (input.compact ? 28 : 36) * scale;
+ // Cards are 4.875rem + 0.25rem padding; slim rows/placeholders are h-9.
+ const scale =
+ slimHeight !== undefined ? slimHeight / 36 : (headerScale ?? (cardHeight ?? 82) / 82);
+ cardHeight ??= 82 * scale;
+ slimHeight ??= 36 * scale;
const labelHeight = (input.boundaryLabelHeight ?? 0) * scale;
const group = groups[target.section];
const order =
@@ -222,11 +218,7 @@ export function createSidebarSortingStrategy(input: {
// Consume the shelf's auto margin as drag labels and resized rows need
// room, keeping the combined shelves at their measured bottom.
let shelfSpace =
- !input.compact &&
- shelfRect &&
- beforeShelf &&
- lastRect &&
- shelfRect.top > beforeShelf.bottom + 1
+ shelfRect && beforeShelf && lastRect && shelfRect.top > beforeShelf.bottom + 1
? Math.max(
0,
lastRect.bottom - rects[0].top - heights.reduce((sum, height) => sum + height + 1, -1),
diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx
index 9cee123e4ecf..cc3487b595ed 100644
--- a/apps/web/src/components/Sidebar.tsx
+++ b/apps/web/src/components/Sidebar.tsx
@@ -1,12 +1,10 @@
import { useSupportsMultiplePullRequests } from "~/hooks/useSupportsMultiplePullRequests";
-import { useCompactSidebarEnabled } from "../hooks/useSettings";
import { resolveThreadCurrentPullRequestLink } from "@t3tools/shared/threadPullRequests";
import { useAtomValue } from "@effect/atom-react";
import { replaceComposerContextReferences } from "@t3tools/shared/composerContextReferences";
import * as Schema from "effect/Schema";
import {
DndContext,
- DragOverlay,
useSensor,
useSensors,
type DragEndEvent,
@@ -74,7 +72,6 @@ import {
type MouseEvent as ReactMouseEvent,
type ReactNode,
} from "react";
-import { createPortal } from "react-dom";
import { useParams, useRouter } from "@tanstack/react-router";
import { useRightPanelStore } from "../rightPanelStore";
@@ -124,7 +121,6 @@ import { startNewThreadFromContext } from "../lib/chatThreadActions";
import { useClientSettings } from "../hooks/useSettings";
import { useCopyToClipboard } from "../hooks/useCopyToClipboard";
import { useLocalStorage } from "../hooks/useLocalStorage";
-import { SidebarCompletedTime } from "./sidebar/SidebarCompletedTime";
import { useNowMinute } from "../hooks/useNowMinute";
import { useEnvironments, usePrimaryEnvironmentId } from "../state/environments";
import {
@@ -325,7 +321,6 @@ function SidebarThreadTooltip({
branchMismatch,
terminalStatus,
terminalProcessCount,
- compactStatus,
}: {
thread: SidebarThreadSummary;
project: ProjectFaviconProject | null;
@@ -342,7 +337,6 @@ function SidebarThreadTooltip({
} | null;
terminalStatus: TerminalStatusIndicator | null;
terminalProcessCount: number;
- compactStatus?: string | undefined;
}) {
const driverKind = providerEntry?.driverKind ?? null;
const supportsMultiplePullRequests = useSupportsMultiplePullRequests(thread.environmentId);
@@ -359,7 +353,6 @@ function SidebarThreadTooltip({
{thread.title}
- {compactStatus ?
{compactStatus}
: null}
{projectDisplayName ? (
{project ?
: null}
@@ -509,7 +502,7 @@ function SnoozePopoverButton(props: {
type SortableThreadRowBag = Pick<
ReturnType
,
"listeners" | "setNodeRef" | "transform" | "transition" | "isDragging"
-> & { hidden?: boolean };
+>;
function SortableThreadRow(props: {
id: string;
@@ -590,18 +583,7 @@ function SidebarSectionPlaceholder(props: {
props.isDropTarget && "border-primary/40 bg-primary/5 text-primary",
)}
>
- {props.label}
- {props.marker === "settled-placeholder" ? (
-
- ) : (
-
- )}
+ {props.label}
) : null}
@@ -625,30 +607,19 @@ function SidebarDragBoundary(props: {
className="pointer-events-none relative mx-0.5 -mb-px h-0"
>
{props.visible ? (
-
+
- {props.label}
- {props.marker === "pinned-header" ? (
-
- ) : (
-
- )}
+ {props.label}
@@ -669,32 +640,20 @@ function SidebarSectionHeader(props: {
isDropTarget?: boolean;
toggle: { expanded: boolean; onToggle: () => void };
}) {
- const compactEnabled = useCompactSidebarEnabled();
- const { state, isMobile } = useSidebar();
- const compact = compactEnabled && state === "collapsed" && !isMobile;
const snoozed = props.marker === "snoozed-header";
const className = cn(
"flex h-full w-full items-center gap-2 px-2 text-left text-xs font-medium",
- compact && "justify-center px-0",
snoozed ? "text-blue-600 dark:text-blue-400" : "text-sidebar-muted-foreground/60",
props.dragging && "text-sidebar-foreground/80",
props.isDropTarget && "text-primary",
);
const content = (
<>
-
{props.label}
- {compact ? (
- snoozed ? (
-
- ) : (
-
- )
- ) : null}
+
{props.label}
@@ -716,22 +674,15 @@ function SidebarSectionHeader(props: {
data-testid={`sidebar-${props.marker}`}
className={cn("mx-0.5 h-8", props.className)}
>
-
-
- }
- >
- {content}
-
- {props.label}
-
+
);
}
@@ -754,9 +705,6 @@ const SidebarDraftRow = memo(function SidebarDraftRow(props: {
onDiscard: (draftId: DraftId) => void;
}) {
const { composer, draftId, onDiscard, onNavigate, session } = props;
- const compactEnabled = useCompactSidebarEnabled();
- const { state, isMobile } = useSidebar();
- const compact = compactEnabled && state === "collapsed" && !isMobile;
const promptPreview =
replaceComposerContextReferences(composer.prompt, (occurrence) => occurrence.label)
.trim()
@@ -795,34 +743,6 @@ const SidebarDraftRow = memo(function SidebarDraftRow(props: {
},
[draftId, onDiscard],
);
- if (compact) {
- return (
-
-
-
- }
- >
-
-
-
- {props.projectDisplayName}
- {preview}
-
-
-
- );
- }
return (
= {
const SidebarThreadRow = memo(function SidebarThreadRow(props: {
thread: SidebarThreadSummary;
variant: "card" | "slim";
- compact: boolean;
// Slim rows are either settled (action: un-settle) or merely quiet
// (seen Ready threads — action: settle).
variantAction: "settle" | "unsettle" | "unsnooze";
@@ -1113,9 +1032,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
variant,
variantAction,
} = 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],
@@ -1293,16 +1209,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
branchMismatch={branchMismatch}
terminalStatus={terminalStatus}
terminalProcessCount={terminalProcessCount}
- compactStatus={
- compact || (props.compact && variant === "card")
- ? (topStatus?.label ??
- (variantAction === "unsnooze"
- ? "Snoozed"
- : variantAction === "unsettle"
- ? "Settled"
- : "Ready"))
- : undefined
- }
/>
);
@@ -1349,7 +1255,6 @@ 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
@@ -1512,7 +1417,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.hidden || (!sortable.isDragging && sortable.transform?.scaleY === 0)
+ !sortable.isDragging && sortable.transform?.scaleY === 0
? ("hidden" as const)
: undefined,
},
@@ -1584,21 +1489,18 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
useRightPanelStore.getState().open(threadRef, "pull-requests");
if (!props.isActive) onThreadActivate(threadRef);
}, [onThreadActivate, props.isActive, threadRef]);
- const renderPrBadge = (iconOnly: boolean, variant: "underline" | "badge" = "underline") =>
+ const prBadge =
prBadgeShape?.kind === "stack" || pr || currentLinkedPr ? (
) : null;
- const hasPrBadge = prBadgeShape?.kind === "stack" || pr !== null || currentLinkedPr !== null;
- const prBadge = renderPrBadge(false);
const terminalStatusIcon = terminalStatus ? (
-
-
- }
- >
-
- {props.project ? (
-
- ) : driverKind ? (
-
- ) : (
-
- )}
- {isRemote ? (
-
-
- }
- >
-
-
-
- {props.environmentLabel ?? "Remote environment"}
-
-
- ) : null}
- {hasPrBadge ? (
-
- {renderPrBadge(true, "badge")}
-
- ) : null}
-
- {topStatus ? (
-
- ) : hasUnsentDraft ? (
-
- ) : null}
- {props.jumpLabel ? : null}
-
- {sortable?.isDragging ? (
- {dragDestination}
- ) : (
- detailsTooltip
- )}
-
-
- );
- }
-
if (variant === "slim") {
return (
-
+
-
+
}
>
-
-
+
+
{draftIndicator}
{props.project ? (
-
-
- {compactRows && isRemote ? (
-
-
- }
- >
-
-
-
- {props.environmentLabel ?? "Remote environment"}
-
-
- ) : null}
-
- ) : compactRows && isRemote ? (
-
-
- }
- >
-
-
-
- {props.environmentLabel ?? "Remote environment"}
-
-
+
) : null}
- {compactRows ? (
- title
- ) : props.projectDisplayName ? (
+ {props.projectDisplayName ? (
)}
{pinIndicator}
- {compactRows ? (
- <>
- {terminalStatusIcon}
- {topStatus && CompactStatusIcon ? (
- isWokeStatus ? (
-
- ) : (
-
-
- {topStatus.label}
-
- )
- ) : null}
- {renderPrBadge(true)}
- >
- ) : null}
{/* The visible state owns this slot's width: status at rest,
actions on hover/keyboard focus or while the popover is open. Keeping
the hidden state out of flow lets the project label reclaim
@@ -2067,35 +1766,20 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
{sortable?.isDragging ? (
dragDestination
) : (
-
+
{/* Read-only status labels yield to the hover actions. Woke is
itself an action, so it stays pointer-enabled and visible
while the other controls appear beside it. */}
- {compactRows ? (
- status === "working" ? (
-
-
-
- ) : compactCompletedAt ? (
-
- ) : (
- threadTimeLabel(thread)
- )
- ) : topStatus ? (
+ {topStatus ? (
isWokeStatus ? (
- {compactRows ? null : "Settle"}
+ Settle
Settle thread
@@ -2211,70 +1895,68 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
)}
- {isRegeneratingTitle ? (
-
- Regenerating title
-
- ) : null}
- {compactRows ? null : (
- <>
-
{title}
-
- {/* Always the branch. The plan step used to take this slot while
+
+ {title}
+ {isRegeneratingTitle ? (
+
+ Regenerating title
+
+ ) : null}
+
+
+ {/* Always the branch. The plan step used to take this slot while
working, but it truncated to a half-sentence and dropped the
branch, so the row lost its most stable identifier. */}
- {thread.branch ? (
- <>
-
-
- {thread.branch}
-
- >
- ) : (
-
- )}
- {terminalStatusIcon}
- {prBadge}
- {diff ? (
-
- +{diff.insertions}{" "}
- −{diff.deletions}
-
- ) : null}
-
- {isRemote ? (
-
-
-
- ) : null}
- {driverKind ? (
-
-
-
- ) : null}
+ {thread.branch ? (
+ <>
+
+
+ {thread.branch}
-
- >
- )}
+ >
+ ) : (
+
+ )}
+ {terminalStatusIcon}
+ {prBadge}
+ {diff ? (
+
+ +{diff.insertions}{" "}
+ −{diff.deletions}
+
+ ) : null}
+
+ {isRemote ? (
+
+
+
+ ) : null}
+ {driverKind ? (
+
+
+
+ ) : null}
+
+
{props.jumpLabel ?
: null}
@@ -2308,9 +1990,6 @@ const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: {
onFileDropThreads: (threadRef: ScopedThreadRef, files: File[]) => 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],
@@ -2396,7 +2075,6 @@ 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",
@@ -2408,15 +2086,9 @@ const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: {
>
{props.project ? (
- ) : compact ? (
-
) : null}
-
{thread.title}
-
+ {thread.title}
+
{threadTimeLabel(thread)}
@@ -2444,12 +2116,8 @@ export default function Sidebar() {
const projectOrder = useUiStateStore((store) => store.projectOrder);
const threads = useThreadShells();
const router = useRouter();
- const { isMobile, setOpenMobile, setOpen, state: sidebarState } = useSidebar();
- const compactEnabled = useCompactSidebarEnabled();
- const compact = compactEnabled && sidebarState === "collapsed" && !isMobile;
- const [snoozedFooter, setSnoozedFooter] = useState(null);
+ const { isMobile, setOpenMobile } = useSidebar();
const keybindings = useAtomValue(primaryServerKeybindingsAtom);
- const compactThreadRows = useClientSettings((s) => s.sidebarCompactThreadRows);
const confirmThreadDelete = useClientSettings((s) => s.confirmThreadDelete);
const confirmThreadArchive = useClientSettings((s) => s.confirmThreadArchive);
const sidebarProjectSortOrder = useClientSettings((s) => s.sidebarProjectSortOrder);
@@ -3020,7 +2688,6 @@ 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(
@@ -3028,7 +2695,7 @@ export default function Sidebar() {
scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)) === routeThreadKey,
);
return routeThread === undefined ? EMPTY_THREADS : [routeThread];
- }, [compact, routeThreadKey, settledShelfExpanded, visibleSettledThreads]);
+ }, [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
@@ -3256,14 +2923,10 @@ export default function Sidebar() {
const [renamingThreadKey, setRenamingThreadKey] = useState(null);
const [renamingTitle, setRenamingTitle] = useState("");
- const startThreadRename = useCallback(
- (threadRef: ScopedThreadRef, title: string) => {
- if (compact) setOpen(true);
- setRenamingThreadKey(scopedThreadKey(threadRef));
- setRenamingTitle(title);
- },
- [compact, setOpen],
- );
+ const startThreadRename = useCallback((threadRef: ScopedThreadRef, title: string) => {
+ setRenamingThreadKey(scopedThreadKey(threadRef));
+ setRenamingTitle(title);
+ }, []);
const cancelThreadRename = useCallback(() => setRenamingThreadKey(null), []);
const commitThreadRename = useCallback(
(threadRef: ScopedThreadRef, title: string, originalTitle: string) => {
@@ -3425,18 +3088,8 @@ export default function Sidebar() {
const threadListRef = useRef(null);
const dragLabelOffsetRef = useRef(0);
const restrictBelowPins = useCallback(
- (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],
+ (args) => restrictBelowSidebarLabel(args, dragLabelOffsetRef.current),
+ [],
);
const listMotionRef = useRef | null>(null);
const attachListMotionRef = useCallback((node: HTMLUListElement | null) => {
@@ -3658,7 +3311,7 @@ export default function Sidebar() {
pinnedThreads.length +
activeThreads.length +
snoozedThreads.length +
- (compact ? 0 : settledThreads.length) ===
+ settledThreads.length ===
0
) {
return [];
@@ -3674,15 +3327,13 @@ export default function Sidebar() {
items.push({ kind: "marker", marker: "snoozed-header" });
items.push(...rowsOf(visibleSnoozedThreads, "snoozed"));
}
- if (!compact) {
- items.push({ kind: "marker", marker: "settled-header" });
- items.push({ kind: "marker", marker: "settled-placeholder" });
- items.push(...rowsOf(renderedSettledThreads, "settled"));
- }
+ items.push({ kind: "marker", marker: "settled-header" });
+ const settledRows = rowsOf(renderedSettledThreads, "settled");
+ items.push({ kind: "marker", marker: "settled-placeholder" });
+ items.push(...settledRows);
return items;
}, [
activeThreads,
- compact,
pinnedThreads,
renderedSettledThreads,
settledThreads.length,
@@ -3752,7 +3403,6 @@ export default function Sidebar() {
() =>
createSidebarSortingStrategy({
items: sidebarListItems,
- compact,
boundaryLabelHeight: SIDEBAR_DRAG_LABEL_HEIGHT,
settledOrder: draggedSettledOrder,
settledExpanded: settledShelfExpanded,
@@ -3761,7 +3411,6 @@ export default function Sidebar() {
snoozedThreadCount: snoozedThreads.length,
}),
[
- compact,
draggedSettledOrder,
routeThreadKey,
settledShelfExpanded,
@@ -3770,22 +3419,6 @@ 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(
@@ -4689,19 +4322,7 @@ export default function Sidebar() {
<>
0 ? (
-
- ) : null
- }
+ className="gap-0 min-h-full"
fixedHeader={
// Lifted above the stage backdrop, whose fade bleeds below the
// header and would otherwise paint across the search row's outline.
@@ -4761,12 +4382,8 @@ 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={compact ? undefined : headerSearchRef}
- side={compact ? "right" : "bottom"}
- className={cn(
- "max-w-[min(18rem,var(--available-width))] overflow-hidden",
- compact && "min-w-56",
- )}
+ anchor={headerSearchRef}
+ className="max-w-[min(18rem,var(--available-width))] overflow-hidden"
>
}
>
-
+
{isSearchingThreads ? (
threadSearchResults.length > 0 ? (
No threads found
@@ -4948,23 +4557,20 @@ export default function Sidebar() {
modifiers={[
restrictToVerticalAxis,
restrictBelowPins,
- ...(compact ? [] : [restrictToFirstScrollableAncestor]),
+ restrictToFirstScrollableAncestor,
]}
onDragStart={handleThreadDragStart}
onDragOver={handleThreadDragOver}
onDragEnd={handleThreadDragEnd}
>
-
+
0 && "flex-1",
+ sidebarListItems.length > 0 && "flex-1",
)}
>
{(() => {
@@ -4976,9 +4582,10 @@ export default function Sidebar() {
const threadKey = scopedThreadKey(
scopeThreadRef(thread.environmentId, thread.id),
);
- // Settled and snoozed always use slim rows. Active and
- // pinned threads use cards unless the user has explicitly
- // enabled the compact thread-list preference.
+ // Settled and snoozed are the ONLY things that collapse a
+ // row: every other thread is a full card. Density comes
+ // from users (or the auto rules) actually parking work,
+ // not from the sidebar second-guessing what still matters.
const isCard = section === "active" || section === "pinned";
const rowVariant = isCard ? "card" : "slim";
return (
@@ -4988,7 +4595,6 @@ export default function Sidebar() {
key={`${threadKey}:${rowVariant}`}
thread={thread}
variant={rowVariant}
- compact={compactThreadRows}
// Snoozed rows wake, settled rows un-settle, and cards settle.
variantAction={
section === "snoozed"
@@ -5091,25 +4697,11 @@ export default function Sidebar() {
!draggableThreadKeys.has(threadKey) || optimisticDrop !== null
}
>
- {(bag) =>
- renderThreadRowInner(
- thread,
- section,
- draggingCompactSnoozed && bag.isDragging
- ? { ...bag, hidden: true }
- : bag,
- )
- }
+ {(bag) => renderThreadRowInner(thread, section, 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") {
- destination.push(
- renderThreadRow(threadByKey.get(item.key)!, item.section),
- );
+ items.push(renderThreadRow(threadByKey.get(item.key)!, item.section));
continue;
}
switch (item.marker) {
@@ -5141,7 +4724,7 @@ export default function Sidebar() {
key="pinned-header"
marker="pinned-header"
label="Pinned"
- visible={showDragLabels}
+ visible={from !== null}
isDropTarget={dragTargetSection === "pinned"}
/>,
);
@@ -5152,7 +4735,7 @@ export default function Sidebar() {
key="pinned-divider"
marker="pinned-divider"
label="Active"
- visible={showDragLabels}
+ visible={from !== null}
isDropTarget={dragTargetSection === "active"}
/>,
);
@@ -5176,11 +4759,11 @@ export default function Sidebar() {
);
break;
case "snoozed-header":
- destination.push(
+ items.push(
-
- {renderThreadRowInner(compactSnoozedDragThread, "snoozed", {
- isDragging: true,
- listeners: undefined,
- setNodeRef: () => {},
- transform: null,
- transition: undefined,
- })}
-
- ,
- document.body,
- "compact-snoozed-drag",
- )
- : null,
- ];
+ return items;
})()}
- {!compact && settledShelfExpanded && hiddenSettledCount > 0 ? (
+ {settledShelfExpanded && hiddenSettledCount > 0 ? (
-
-
-
- }
- >
-
-
- Show {Math.min(hiddenSettledCount, SETTLED_TAIL_PAGE_COUNT)} more
-
-
-
- Show {Math.min(hiddenSettledCount, SETTLED_TAIL_PAGE_COUNT)} more
-
-
+
) : null}
@@ -5297,29 +4842,20 @@ export default function Sidebar() {
snoozedThreads.length +
settledThreads.length ===
0 ? (
-
+
{projects.length === 0 ? (
<>
-
No projects yet
+
No projects yet
>
- ) : compact ? null : scopedProjectGroup ? (
+ ) : scopedProjectGroup ? (
`No threads in ${scopedProjectGroup.displayName} yet`
) : (
"No threads yet"
diff --git a/apps/web/src/components/ThreadStatusIndicators.tsx b/apps/web/src/components/ThreadStatusIndicators.tsx
index 4b26d0442021..7201779f13f1 100644
--- a/apps/web/src/components/ThreadStatusIndicators.tsx
+++ b/apps/web/src/components/ThreadStatusIndicators.tsx
@@ -144,17 +144,14 @@ export function ThreadPullRequestBadgeControl({
number,
url,
status,
- iconOnly = false,
onOpenStack,
onOpenPullRequest,
}: {
- variant: "underline" | "ghost" | "badge";
+ variant: "underline" | "ghost";
badge: ThreadPullRequestBadge | null;
number?: number | undefined;
url?: string | undefined;
status: PrStatusIndicator | null;
- /** Dense rows drop the number/layer count and keep only the state glyph. */
- iconOnly?: boolean;
onOpenStack: () => void;
onOpenPullRequest: (event: MouseEvent
) => void;
}) {
@@ -171,9 +168,7 @@ export function ThreadPullRequestBadgeControl({
const className = cn(
variant === "ghost"
? buttonVariants({ variant: "ghost", size: "xs" })
- : variant === "badge"
- ? "inline-flex size-3 shrink-0 cursor-pointer items-center justify-center rounded-full bg-sidebar ring-1 ring-sidebar outline-none focus-visible:ring-2 focus-visible:ring-ring"
- : "inline-flex shrink-0 cursor-pointer items-center gap-0.5 whitespace-nowrap border-b border-transparent hover:border-current focus-visible:outline-2 focus-visible:outline-ring",
+ : "inline-flex shrink-0 cursor-pointer items-center gap-0.5 whitespace-nowrap border-b border-transparent hover:border-current focus-visible:outline-2 focus-visible:outline-ring",
"text-xs tabular-nums",
variant === "ghost" &&
"font-normal text-xs! active:scale-100 [--control-icon-color:currentColor]",
@@ -183,11 +178,8 @@ export function ThreadPullRequestBadgeControl({
);
const content = (
<>
-
- {iconOnly ? null : isStack ? badge.layers : linkedCount !== null ? `+${linkedCount}` : number}
+
+ {isStack ? badge.layers : linkedCount !== null ? `+${linkedCount}` : number}
>
);
return (
diff --git a/apps/web/src/components/settings/CompactSidebarPreview.tsx b/apps/web/src/components/settings/CompactSidebarPreview.tsx
deleted file mode 100644
index 4d1e861d14e7..000000000000
--- a/apps/web/src/components/settings/CompactSidebarPreview.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import { useEffect, useRef, useState } from "react";
-
-import { cn } from "~/lib/utils";
-
-export function CompactSidebarPreview({
- railEnabled,
- compactRows,
-}: {
- railEnabled: boolean;
- compactRows: boolean;
-}) {
- const [collapsed, setCollapsed] = useState(false);
- const sidebarRef = useRef(null);
-
- useEffect(() => {
- if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
- const animation = sidebarRef.current?.animate(
- [
- { width: "36px", offset: 0 },
- { width: railEnabled ? "12px" : "0px", offset: 0.45 },
- { width: railEnabled ? "12px" : "0px", offset: 0.6 },
- { width: "36px", offset: 1 },
- ],
- { duration: 800, easing: "ease-in-out" },
- );
- return () => animation?.cancel();
- }, [railEnabled, compactRows]);
-
- return (
-
- );
-}
diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx
index a0d713f58bee..6baa561a91fe 100644
--- a/apps/web/src/components/settings/SettingsPanels.tsx
+++ b/apps/web/src/components/settings/SettingsPanels.tsx
@@ -170,7 +170,6 @@ import {
import { searchableSetting } from "./settingsSearch";
import { ProjectFavicon } from "../ProjectFavicon";
import { PanelAnimationsPreview } from "./PanelAnimationsPreview";
-import { CompactSidebarPreview } from "./CompactSidebarPreview";
const ENVIRONMENT_IDENTIFICATION_LABELS: Record = {
artwork: "Artwork",
@@ -524,10 +523,6 @@ export function useSettingsRestore(onRestored?: () => void) {
...(theme !== "system" ? ["Theme"] : []),
...(!followSystem ? ["Follow system"] : []),
...(themeHalves !== null ? ["Theme mix"] : []),
- ...(settings.compactSidebarEnabled !== DEFAULT_UNIFIED_SETTINGS.compactSidebarEnabled ||
- settings.sidebarCompactThreadRows !== DEFAULT_UNIFIED_SETTINGS.sidebarCompactThreadRows
- ? ["Compact sidebar"]
- : []),
...(settings.appearanceContrast !== DEFAULT_UNIFIED_SETTINGS.appearanceContrast
? ["Contrast"]
: []),
@@ -634,7 +629,6 @@ export function useSettingsRestore(onRestored?: () => void) {
settings.browserLinkTarget,
settings.browserAutoShowFloatingPreview,
settings.appearanceContrast,
- settings.compactSidebarEnabled,
settings.diffColorScheme,
settings.enableAgentBrowserAccess,
settings.confirmQuit,
@@ -666,7 +660,6 @@ export function useSettingsRestore(onRestored?: () => void) {
settings.continueThreadsAfterServerUpdate,
settings.sidebarAutoSettleAfterDays,
settings.sidebarAutoSettleOnMerge,
- settings.sidebarCompactThreadRows,
settings.sidebarProjectGroupingMode,
settings.sidebarThreadPreviewCount,
settings.showSkillsInSlashMenu,
@@ -744,7 +737,6 @@ 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,
@@ -762,7 +754,6 @@ export function useSettingsRestore(onRestored?: () => void) {
panelAnimationDurationMs: DEFAULT_UNIFIED_SETTINGS.panelAnimationDurationMs,
sidebarThreadPreviewCount: DEFAULT_UNIFIED_SETTINGS.sidebarThreadPreviewCount,
sidebarProjectGroupingMode: DEFAULT_UNIFIED_SETTINGS.sidebarProjectGroupingMode,
- sidebarCompactThreadRows: DEFAULT_UNIFIED_SETTINGS.sidebarCompactThreadRows,
sidebarAutoSettleAfterDays: DEFAULT_UNIFIED_SETTINGS.sidebarAutoSettleAfterDays,
sidebarAutoSettleOnMerge: DEFAULT_UNIFIED_SETTINGS.sidebarAutoSettleOnMerge,
responseStreamingMode: DEFAULT_UNIFIED_SETTINGS.responseStreamingMode,
@@ -1143,19 +1134,6 @@ export function AppearanceSettingsPanel() {
const [isImportThemeOpen, setIsImportThemeOpen] = useState(false);
const settings = useScopedSettings();
const updateSettings = useUpdateScopedSettings();
- const compactSidebarMode = settings.compactSidebarEnabled
- ? settings.sidebarCompactThreadRows
- ? "both"
- : "rail"
- : settings.sidebarCompactThreadRows
- ? "threads"
- : "off";
- const compactSidebarModes = {
- off: "Off",
- rail: "Rail only",
- threads: "Threads only",
- both: "Both",
- };
const environmentStageLabel = useEnvironmentStageLabel();
const showEnvironmentIdentification =
resolveEnvironmentIdentificationPillLabel(environmentStageLabel) !== null;
@@ -1442,64 +1420,6 @@ export function AppearanceSettingsPanel() {
/>
-
-
);
diff --git a/apps/web/src/components/settings/SettingsSidebarNav.tsx b/apps/web/src/components/settings/SettingsSidebarNav.tsx
index c206174a397a..c29068934efa 100644
--- a/apps/web/src/components/settings/SettingsSidebarNav.tsx
+++ b/apps/web/src/components/settings/SettingsSidebarNav.tsx
@@ -24,7 +24,6 @@ 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";
@@ -112,13 +111,12 @@ 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 && !(compactSidebarEnabled && !isMobile && !open);
+ const isSearching = query.trim().length > 0;
const hasResults = results.length > 0;
useEffect(() => {
@@ -235,18 +233,7 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) {
<>
- {
- setOpen(true);
- requestAnimationFrame(() => searchInputRef.current?.focus());
- }}
- >
-
-
-
+
handleSectionClick(item.to)}
>
-
- {item.label}
-
+ {item.label}
);
@@ -360,12 +343,10 @@ 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 1fe96d2b4df5..837aa7b47ce3 100644
--- a/apps/web/src/components/settings/settingsSearch.ts
+++ b/apps/web/src/components/settings/settingsSearch.ts
@@ -159,14 +159,6 @@ 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 expanded dense density one line rows chats threads compact thread list",
- ],
- },
{
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 2f65cf8c3697..afbbf7671dfc 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,10 +224,8 @@ export const SidebarUtilityMenu = memo(function SidebarUtilityMenu() {
export const SidebarChromeFooter = memo(function SidebarChromeFooter() {
return (
-
-
-
-
+
+
);
diff --git a/apps/web/src/components/sidebar/SidebarCompletedTime.test.tsx b/apps/web/src/components/sidebar/SidebarCompletedTime.test.tsx
deleted file mode 100644
index 7327af79e794..000000000000
--- a/apps/web/src/components/sidebar/SidebarCompletedTime.test.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import { act, memo } from "react";
-import { create, type ReactTestRenderer } from "react-test-renderer";
-import { afterEach, beforeEach, expect, it, vi } from "vite-plus/test";
-
-import { SidebarCompletedTime } from "./SidebarCompletedTime";
-
-let renderer: ReactTestRenderer | undefined;
-
-beforeEach(() => {
- vi.useFakeTimers();
- vi.setSystemTime(new Date("2026-09-07T01:01:00Z"));
- vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
- vi.stubGlobal("window", {
- setTimeout,
- clearTimeout,
- setInterval,
- clearInterval,
- });
-});
-
-afterEach(async () => {
- await act(() => renderer?.unmount());
- renderer = undefined;
- vi.unstubAllGlobals();
- vi.useRealTimers();
-});
-
-it("advances visible and accessible completion times without rerendering its memoized row", async () => {
- const rowRender = vi.fn();
- const Row = memo(function Row() {
- rowRender();
- return ;
- });
- await act(() => {
- renderer = create(
);
- });
- expect(renderer!.root.findByType("time").props.dateTime).toBe("2026-09-07T01:00:00Z");
- expect(renderer!.root.findByProps({ className: "sr-only" }).children).toEqual(["Completed "]);
- expect(
- renderer!.root.findAll((node) => node.props.role === "status" || node.props["aria-live"]),
- ).toHaveLength(0);
- expect(renderer!.root.findByProps({ className: "text-secondary-label" }).children).toEqual([
- "1m",
- ]);
-
- await act(() => vi.advanceTimersByTime(60_000));
-
- expect(renderer!.root.findByProps({ className: "sr-only" }).children).toEqual(["Completed "]);
- expect(renderer!.root.findByProps({ className: "text-secondary-label" }).children).toEqual([
- "2m",
- ]);
- expect(rowRender).toHaveBeenCalledTimes(1);
- await act(() => renderer!.unmount());
- renderer = undefined;
- expect(vi.getTimerCount()).toBe(0);
-});
diff --git a/apps/web/src/components/sidebar/SidebarCompletedTime.tsx b/apps/web/src/components/sidebar/SidebarCompletedTime.tsx
deleted file mode 100644
index 785b8d9459d2..000000000000
--- a/apps/web/src/components/sidebar/SidebarCompletedTime.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { useNowMinute } from "../../hooks/useNowMinute";
-import { formatRelativeTimeLabel } from "../../timestampFormat";
-
-export function SidebarCompletedTime({ completedAt }: { completedAt: string }) {
- // Subscribe inside the label so time advances even when the row is memoized.
- const nowMinute = useNowMinute();
- const relativeTime = formatRelativeTimeLabel(completedAt, Date.parse(`${nowMinute}:00Z`));
- const label = relativeTime === "just now" ? "now" : relativeTime.replace(/ ago$/, "");
-
- return (
-
- );
-}
diff --git a/apps/web/src/components/sidebar/SidebarThreadHeader.tsx b/apps/web/src/components/sidebar/SidebarThreadHeader.tsx
index d0718d9856f8..878235615b39 100644
--- a/apps/web/src/components/sidebar/SidebarThreadHeader.tsx
+++ b/apps/web/src/components/sidebar/SidebarThreadHeader.tsx
@@ -20,10 +20,9 @@ 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, useSidebar } from "../ui/sidebar";
+import { SidebarMenuButton } from "../ui/sidebar";
import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
export interface SidebarThreadHeaderProps {
@@ -70,9 +69,6 @@ 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
@@ -83,24 +79,10 @@ 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 8c04eec6fe7d..a94b7801ecfd 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 404295f5f5c1..307feda7abfc 100644
--- a/apps/web/src/components/ui/sidebar.tsx
+++ b/apps/web/src/components/ui/sidebar.tsx
@@ -591,11 +591,9 @@ function SidebarSeparator({ className, ...props }: React.ComponentProps & {
fixedHeader?: React.ReactNode;
- fixedFooter?: React.ReactNode;
}) {
return (
<>
@@ -619,7 +617,6 @@ function SidebarContent({
{...props}
/>
- {fixedFooter ? {fixedFooter}
: null}
>
);
}
diff --git a/apps/web/src/hooks/useSettings.ts b/apps/web/src/hooks/useSettings.ts
index fa4b8bc8fd31..194cc36c55f4 100644
--- a/apps/web/src/hooks/useSettings.ts
+++ b/apps/web/src/hooks/useSettings.ts
@@ -379,13 +379,6 @@ 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/timestampFormat.test.ts b/apps/web/src/timestampFormat.test.ts
index 5169e438bb2d..8c6287010d8f 100644
--- a/apps/web/src/timestampFormat.test.ts
+++ b/apps/web/src/timestampFormat.test.ts
@@ -228,15 +228,3 @@ describe("formatElapsedDurationLabel", () => {
expect(formatElapsedDurationLabel("2026-04-03T12:00:00.000Z")).toBe("4d");
});
});
-
-describe("explicit relative-time clock", () => {
- it("uses the supplied minute instead of the wall clock", () => {
- const completedAt = "2026-09-07T01:00:00Z";
- expect(formatRelativeTimeLabel(completedAt, Date.parse("2026-09-07T01:01:00Z"))).toBe("1m ago");
- expect(formatRelativeTimeLabel(completedAt, Date.parse("2026-09-07T01:02:00Z"))).toBe("2m ago");
- expect(formatRelativeTime(completedAt, Date.parse("2026-09-07T01:02:00Z"))).toEqual({
- value: "2m",
- suffix: "ago",
- });
- });
-});
diff --git a/apps/web/src/timestampFormat.ts b/apps/web/src/timestampFormat.ts
index 983a9bb8b232..9dd463bb50fa 100644
--- a/apps/web/src/timestampFormat.ts
+++ b/apps/web/src/timestampFormat.ts
@@ -196,10 +196,10 @@ export type RelativeTimeState =
| { status: "invalid" }
| { status: "relative"; value: string; suffix: string | null };
-export function formatRelativeTime(isoDate: string, nowMs = Date.now()): RelativeTimeParts | null {
+export function formatRelativeTime(isoDate: string): RelativeTimeParts | null {
const date = parseTimestampDate(isoDate);
if (!date) return null;
- const diffMs = nowMs - date.getTime();
+ const diffMs = Date.now() - date.getTime();
if (diffMs < 0) return { value: "just now", suffix: null };
const seconds = Math.floor(diffMs / 1000);
if (seconds < 60) return { value: "just now", suffix: null };
@@ -211,8 +211,8 @@ export function formatRelativeTime(isoDate: string, nowMs = Date.now()): Relativ
return { value: `${days}d`, suffix: "ago" };
}
-export function formatRelativeTimeLabel(isoDate: string, nowMs = Date.now()) {
- const relative = formatRelativeTime(isoDate, nowMs);
+export function formatRelativeTimeLabel(isoDate: string) {
+ const relative = formatRelativeTime(isoDate);
if (!relative) return "";
return relative.suffix ? `${relative.value} ${relative.suffix}` : relative.value;
}
diff --git a/apps/web/src/workspaceTitlebar.ts b/apps/web/src/workspaceTitlebar.ts
index aed95897cc55..b481221e63aa 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]: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)))]";
+ "[[data-sidebar-state=collapsed]_&]:pl-[var(--workspace-titlebar-content-left)]";
diff --git a/packages/contracts/src/settings.test.ts b/packages/contracts/src/settings.test.ts
index c8c6922ec020..b090b47fba5b 100644
--- a/packages/contracts/src/settings.test.ts
+++ b/packages/contracts/src/settings.test.ts
@@ -482,18 +482,7 @@ describe("ClientSettings environment identification", () => {
describe("ClientSettings sidebar", () => {
it("defaults to the current sidebar", () => {
- const settings = decodeClientSettings({});
- expect(settings.legacySidebarEnabled).toBe(false);
- expect(settings.sidebarCompactThreadRows).toBe(false);
- });
-
- it("preserves an explicit compact thread row preference", () => {
- expect(decodeClientSettings({ sidebarCompactThreadRows: true }).sidebarCompactThreadRows).toBe(
- true,
- );
- expect(
- decodeClientSettingsPatch({ sidebarCompactThreadRows: true }).sidebarCompactThreadRows,
- ).toBe(true);
+ expect(decodeClientSettings({}).legacySidebarEnabled).toBe(false);
});
it("drops the retired sidebar v2 beta keys, resetting everyone to the default", () => {
@@ -506,6 +495,14 @@ describe("ClientSettings sidebar", () => {
expect(decoded).not.toHaveProperty("sidebarV2ConfiguredByUser");
});
+ it("drops the retired compact sidebar keys for users who opted in", () => {
+ const stored = { compactSidebarEnabled: true, sidebarCompactThreadRows: true };
+ const decoded = decodeClientSettings(stored);
+ expect(decoded).not.toHaveProperty("compactSidebarEnabled");
+ expect(decoded).not.toHaveProperty("sidebarCompactThreadRows");
+ expect(decodeClientSettingsPatch(stored)).toEqual({});
+ });
+
it("preserves an explicit legacy sidebar opt-in", () => {
expect(decodeClientSettings({ legacySidebarEnabled: true }).legacySidebarEnabled).toBe(true);
expect(decodeClientSettingsPatch({ legacySidebarEnabled: true }).legacySidebarEnabled).toBe(
diff --git a/packages/contracts/src/settings.ts b/packages/contracts/src/settings.ts
index 1262a303ba41..7b3715d704be 100644
--- a/packages/contracts/src/settings.ts
+++ b/packages/contracts/src/settings.ts
@@ -437,7 +437,6 @@ 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)),
),
@@ -451,7 +450,6 @@ export const ClientSettingsSchema = Schema.Struct({
sidebarThreadSortOrder: SidebarThreadSortOrder.pipe(
Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_THREAD_SORT_ORDER)),
),
- sidebarCompactThreadRows: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
sidebarThreadPreviewCount: SidebarThreadPreviewCount.pipe(
Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_THREAD_PREVIEW_COUNT)),
),
@@ -1504,14 +1502,12 @@ 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),
),
sidebarProjectSortOrder: Schema.optionalKey(SidebarProjectSortOrder),
sidebarThreadSortOrder: Schema.optionalKey(SidebarThreadSortOrder),
- sidebarCompactThreadRows: Schema.optionalKey(Schema.Boolean),
sidebarThreadPreviewCount: Schema.optionalKey(SidebarThreadPreviewCount),
timestampFormat: Schema.optionalKey(TimestampFormat),
snapShotEnabled: Schema.optionalKey(Schema.Boolean),