Skip to content
Open
7 changes: 5 additions & 2 deletions apps/web/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1940,6 +1940,7 @@ export default function Sidebar({ routeReveal }: { routeReveal: SidebarRouteReve
);
};
const sortableIds = sidebarListItems.map(sidebarListItemId);
const sectionDividersEnabled = useClientSettings((s) => s.sidebarSectionDividersEnabled);
// Keep non-drag orders constant to preserve sorting strategy and row identity during streaming.
const { settledOrder: draggedSettledOrder, activeOrder: draggedActiveOrder } =
dragState === null
Expand Down Expand Up @@ -3355,7 +3356,8 @@ export default function Sidebar({ routeReveal }: { routeReveal: SidebarRouteReve
key="pinned-header"
marker="pinned-header"
label="Pinned"
visible={from !== null}
visible={sectionDividersEnabled || from !== null}
persistent={sectionDividersEnabled}
isDropTarget={dragTargetSection === "pinned"}
/>,
);
Expand All @@ -3366,7 +3368,8 @@ export default function Sidebar({ routeReveal }: { routeReveal: SidebarRouteReve
key="pinned-divider"
marker="pinned-divider"
label="Active"
visible={from !== null}
visible={sectionDividersEnabled || from !== null}
persistent={sectionDividersEnabled}
isDropTarget={dragTargetSection === "active"}
/>,
);
Expand Down
32 changes: 32 additions & 0 deletions apps/web/src/components/settings/SettingsPanels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,10 @@ export function useSettingsRestore(onRestored?: () => void) {
...(settings.sidebarWorkingShelfEnabled !== DEFAULT_UNIFIED_SETTINGS.sidebarWorkingShelfEnabled
? ["Working section"]
: []),
...(settings.sidebarSectionDividersEnabled !==
DEFAULT_UNIFIED_SETTINGS.sidebarSectionDividersEnabled
? ["Thread section dividers"]
: []),
...(settings.sidebarAutoSettleAfterDays !== DEFAULT_UNIFIED_SETTINGS.sidebarAutoSettleAfterDays
? ["Auto-settle inactive threads"]
: []),
Expand Down Expand Up @@ -631,6 +635,7 @@ export function useSettingsRestore(onRestored?: () => void) {
sidebarProjectGroupingMode: DEFAULT_UNIFIED_SETTINGS.sidebarProjectGroupingMode,
sidebarProjectSortOrder: DEFAULT_UNIFIED_SETTINGS.sidebarProjectSortOrder,
sidebarWorkingShelfEnabled: DEFAULT_UNIFIED_SETTINGS.sidebarWorkingShelfEnabled,
sidebarSectionDividersEnabled: DEFAULT_UNIFIED_SETTINGS.sidebarSectionDividersEnabled,
sidebarAutoSettleAfterDays: DEFAULT_UNIFIED_SETTINGS.sidebarAutoSettleAfterDays,
sidebarAutoSettleKeepPullRequests: DEFAULT_UNIFIED_SETTINGS.sidebarAutoSettleKeepPullRequests,
sidebarAutoSettleOnMerge: DEFAULT_UNIFIED_SETTINGS.sidebarAutoSettleOnMerge,
Expand Down Expand Up @@ -886,6 +891,33 @@ export function GeneralSettingsPanel() {
/>
}
/>
<SettingsRow
{...searchableSetting("sidebar-section-dividers")}
description="Keep the Pinned and Active labels and lines visible when you're not dragging a thread."
resetAction={
settings.sidebarSectionDividersEnabled !==
DEFAULT_UNIFIED_SETTINGS.sidebarSectionDividersEnabled ? (
<SettingResetButton
label="thread section dividers"
onClick={() =>
updateSettings({
sidebarSectionDividersEnabled:
DEFAULT_UNIFIED_SETTINGS.sidebarSectionDividersEnabled,
})
}
/>
) : null
}
control={
<Switch
checked={settings.sidebarSectionDividersEnabled}
onCheckedChange={(checked) =>
updateSettings({ sidebarSectionDividersEnabled: Boolean(checked) })
}
aria-label="Always show thread section dividers"
/>
}
/>
</SettingsSection>

<SettingsSection id="confirmations" title="Confirmations">
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/components/settings/settingsSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,12 @@ export const SETTINGS_SEARCH_ITEMS = [
to: "/settings/general",
searchTerms: ["hide fold running monitoring threads inbox sidebar shelf"],
},
{
id: "sidebar-section-dividers",
title: "Always show thread section dividers",
to: "/settings/general",
searchTerms: ["sidebar pinned active labels lines separators dragging"],
},
{
id: "boring-sidebar",
title: "Boring sidebar (group by project)",
Expand Down
8 changes: 6 additions & 2 deletions apps/web/src/components/sidebar/SidebarListMarkers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,20 @@ export function SidebarDragBoundary(props: {
marker: "pinned-header" | "pinned-divider";
label: string;
visible: boolean;
persistent: boolean;
isDropTarget: boolean;
}) {
return (
<SortableSidebarMarker
marker={props.marker}
data-testid={`sidebar-${props.marker}`}
className="pointer-events-none relative mx-0.5 -mb-px h-0"
className={cn("pointer-events-none relative mx-0.5 -mb-px", props.persistent ? "h-6" : "h-0")}
>
{props.visible ? (
<div className="sidebar-drag-boundary-label absolute inset-x-2 top-1 flex h-4 items-center gap-2">
<div
data-persistent={props.persistent || undefined}
className="sidebar-drag-boundary-label absolute inset-x-2 top-1 flex h-4 items-center gap-2"
>
<span
className={cn(
"shrink-0 text-xs font-medium",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -2385,7 +2385,7 @@ code {

/* Let the sortable rows open label clearance before painting the dividers.
This runs once per pickup; rows retain dnd-kit's normal 200ms transitions. */
.sidebar-drag-boundary-label {
.sidebar-drag-boundary-label:not([data-persistent]) {
animation: sidebar-drag-label-reveal 200ms step-end;
}

Expand Down
15 changes: 15 additions & 0 deletions packages/contracts/src/settings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,21 @@ describe("ClientSettings notifications", () => {
});
});

describe("ClientSettings sidebar section dividers", () => {
it("keeps existing clients opted out and preserves explicit choices", () => {
expect(decodeClientSettings({}).sidebarSectionDividersEnabled).toBe(false);
expect(decodeClientSettingsPatch({})).not.toHaveProperty("sidebarSectionDividersEnabled");
for (const enabled of [true, false]) {
const patch = { sidebarSectionDividersEnabled: enabled };
expect(decodeClientSettingsPatch(patch)).toEqual(patch);
expect(
decodeClientSettings(encodeClientSettings(decodeClientSettings(patch)))
.sidebarSectionDividersEnabled,
).toBe(enabled);
}
});
});

describe("ClientSettings quit confirmation", () => {
it.each([
[true, "hold"],
Expand Down
4 changes: 4 additions & 0 deletions packages/contracts/src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,9 @@ export const ClientSettingsSchema = Schema.Struct({
// the boring sidebar; the boring sidebar toggle clears it.
legacySidebarEnabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
sidebarWorkingShelfEnabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
sidebarSectionDividersEnabled: Schema.Boolean.pipe(
Schema.withDecodingDefault(Effect.succeed(false)),
),
sidebarProjectGroupingMode: SidebarProjectGroupingMode.pipe(
Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_PROJECT_GROUPING_MODE)),
),
Expand Down Expand Up @@ -1731,6 +1734,7 @@ export const ClientSettingsPatch = Schema.Struct({
threadDetailsItems: Schema.optionalKey(Schema.Record(Schema.String, Schema.Boolean)),
legacySidebarEnabled: Schema.optionalKey(Schema.Boolean),
sidebarWorkingShelfEnabled: Schema.optionalKey(Schema.Boolean),
sidebarSectionDividersEnabled: Schema.optionalKey(Schema.Boolean),
sidebarProjectGroupingMode: Schema.optionalKey(SidebarProjectGroupingMode),
sidebarProjectGroupingOverrides: Schema.optionalKey(
Schema.Record(TrimmedNonEmptyString, SidebarProjectGroupingMode),
Expand Down
Loading