diff --git a/apps/desktop/src/settings/DesktopClientSettings.test.ts b/apps/desktop/src/settings/DesktopClientSettings.test.ts index 4867d8524093..611b4f795b3c 100644 --- a/apps/desktop/src/settings/DesktopClientSettings.test.ts +++ b/apps/desktop/src/settings/DesktopClientSettings.test.ts @@ -19,6 +19,7 @@ const clientSettings: ClientSettings = { browserDefaultAppearance: "dark", browserAutoShowFloatingPreview: false, compactLegacySidebarStatuses: false, + showThreadWorktreeIndicators: true, confirmQuit: true, confirmThreadArchive: true, confirmThreadDelete: false, diff --git a/apps/desktop/src/settings/LastCodeSettingsImport.test.ts b/apps/desktop/src/settings/LastCodeSettingsImport.test.ts index f2ff48a9ae4c..756323d221fa 100644 --- a/apps/desktop/src/settings/LastCodeSettingsImport.test.ts +++ b/apps/desktop/src/settings/LastCodeSettingsImport.test.ts @@ -92,6 +92,7 @@ describe("LastCodeSettingsImport", () => { const lastCodeCustom = ProviderInstanceId.make("lastcode_custom"); const sourceClient = { ...DEFAULT_CLIENT_SETTINGS, fontSizeInterface: 17 }; Reflect.deleteProperty(sourceClient, "compactLegacySidebarStatuses"); + Reflect.deleteProperty(sourceClient, "showThreadWorktreeIndicators"); Reflect.deleteProperty(sourceClient, "legacySidebarScale"); Reflect.deleteProperty(sourceClient, "roundedProjectIcons"); sourceClient.favorites = [ @@ -105,6 +106,7 @@ describe("LastCodeSettingsImport", () => { const destinationClient = { ...DEFAULT_CLIENT_SETTINGS, compactLegacySidebarStatuses: true, + showThreadWorktreeIndicators: false, legacySidebarScale: 75, roundedProjectIcons: true, environmentIconColors: { primary: "#2563eb", remote: "#7c3aed" }, @@ -218,6 +220,7 @@ describe("LastCodeSettingsImport", () => { ); assert.equal(importedClient.fontSizeInterface, 17); assert.equal(importedClient.compactLegacySidebarStatuses, true); + assert.equal(importedClient.showThreadWorktreeIndicators, false); assert.equal(importedClient.legacySidebarScale, 75); assert.equal(importedClient.roundedProjectIcons, true); assert.deepEqual(importedClient.environmentIconColors, { diff --git a/apps/desktop/src/settings/LastCodeSettingsImport.ts b/apps/desktop/src/settings/LastCodeSettingsImport.ts index 806b227f9564..0a1cf69d9b15 100644 --- a/apps/desktop/src/settings/LastCodeSettingsImport.ts +++ b/apps/desktop/src/settings/LastCodeSettingsImport.ts @@ -241,6 +241,7 @@ function mergeClientSettings(sourceRaw: string, destinationRaw: string | null): legacySidebarScale: destination.legacySidebarScale, roundedProjectIcons: destination.roundedProjectIcons, showLocalEnvironmentIcon: destination.showLocalEnvironmentIcon, + showThreadWorktreeIndicators: destination.showThreadWorktreeIndicators, })}\n`; } diff --git a/apps/web/src/components/LegacySidebar.tsx b/apps/web/src/components/LegacySidebar.tsx index 45172ea581c8..04315e7b61d4 100644 --- a/apps/web/src/components/LegacySidebar.tsx +++ b/apps/web/src/components/LegacySidebar.tsx @@ -341,6 +341,7 @@ function buildThreadJumpLabelMap(input: { interface SidebarThreadRowProps { thread: SidebarThreadSummary; compactStatusIndicators: boolean; + showWorktreeIndicators: boolean; showLocalEnvironmentIcon: boolean; configuredEnvironmentIconColor: EnvironmentIconColor | undefined; projectCwd: string | null; @@ -934,7 +935,7 @@ export const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThr )} - + {props.showWorktreeIndicators ? : null} {terminalStatus && ( >; environmentIconColors: Readonly>; @@ -1206,6 +1208,7 @@ const SidebarProjectThreadList = memo(function SidebarProjectThreadList( const { legacySidebarScale, compactStatusIndicators, + showWorktreeIndicators, scaleStyle, providerEntriesByEnvironmentId, environmentIconColors, @@ -1277,6 +1280,7 @@ const SidebarProjectThreadList = memo(function SidebarProjectThreadList( key={threadKey} thread={thread} compactStatusIndicators={compactStatusIndicators} + showWorktreeIndicators={showWorktreeIndicators} showLocalEnvironmentIcon={showLocalEnvironmentIcon} configuredEnvironmentIconColor={environmentIconColors[thread.environmentId]} projectCwd={projectCwd} @@ -1351,6 +1355,7 @@ const SidebarProjectThreadList = memo(function SidebarProjectThreadList( interface SidebarProjectItemProps { legacySidebarScale: LegacySidebarScale; compactStatusIndicators: boolean; + showWorktreeIndicators: boolean; scaleStyle: CSSProperties; providerEntriesByEnvironmentId: ReadonlyMap>; desktopLocalEnvironmentIds: ReadonlySet; @@ -1380,6 +1385,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec const { legacySidebarScale, compactStatusIndicators, + showWorktreeIndicators, scaleStyle, providerEntriesByEnvironmentId, project, @@ -2764,6 +2770,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec >; desktopLocalEnvironmentIds: ReadonlySet; @@ -3339,6 +3347,7 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( projectsLength, legacySidebarScale, compactStatusIndicators, + showWorktreeIndicators, projectTreeScaleStyle, providerEntriesByEnvironmentId, desktopLocalEnvironmentIds, @@ -3473,6 +3482,7 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( settings.compactLegacySidebarStatuses, ); + const showWorktreeIndicators = useClientSettings( + (settings) => settings.showThreadWorktreeIndicators, + ); const environmentIconColors = useClientSettings((s) => s.environmentIconColors); const showLocalEnvironmentIcon = useClientSettings((s) => s.showLocalEnvironmentIcon); const serverProviders = useAtomValue(primaryServerProvidersAtom); @@ -4264,6 +4278,7 @@ export default function LegacySidebar() { projectsLength={projects.length} legacySidebarScale={legacySidebarScale} compactStatusIndicators={compactStatusIndicators} + showWorktreeIndicators={showWorktreeIndicators} projectTreeScaleStyle={scaleStyle} providerEntriesByEnvironmentId={providerEntriesByEnvironmentId} desktopLocalEnvironmentIds={desktopLocalEnvironmentIds} diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index 48ec1c759c06..1f4dfb63757a 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -627,6 +627,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { environmentLabel: string | null; environmentKnown: boolean; showLocalEnvironmentIcon: boolean; + showWorktreeIndicators: boolean; configuredEnvironmentIconColor: EnvironmentIconColor | undefined; projectCwd: string | null; projectFaviconPath: string | null; @@ -1606,7 +1607,9 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { branch, so the row lost its most stable identifier. */} {thread.branch ? ( <> - + {props.showWorktreeIndicators ? ( + + ) : null} {thread.branch} ) : ( @@ -1808,6 +1811,7 @@ export default function Sidebar() { const projectGroupingSettings = useClientSettings(selectProjectGroupingSettings); const environmentIconColors = useClientSettings((s) => s.environmentIconColors); const showLocalEnvironmentIcon = useClientSettings((s) => s.showLocalEnvironmentIcon); + const showWorktreeIndicators = useClientSettings((s) => s.showThreadWorktreeIndicators); const { settleThread, unsettleThread, @@ -3859,6 +3863,7 @@ export default function Sidebar() { environmentLabel={environmentLabelById.get(thread.environmentId) ?? null} environmentKnown={environmentLabelById.has(thread.environmentId)} showLocalEnvironmentIcon={showLocalEnvironmentIcon} + showWorktreeIndicators={showWorktreeIndicators} configuredEnvironmentIconColor={environmentIconColors[thread.environmentId]} projectCwd={ projectCwdByKey.get(`${thread.environmentId}:${thread.projectId}`) ?? null diff --git a/apps/web/src/components/settings/LastCodeSettings.tsx b/apps/web/src/components/settings/LastCodeSettings.tsx index c083c65d5291..cb8e80beb58c 100644 --- a/apps/web/src/components/settings/LastCodeSettings.tsx +++ b/apps/web/src/components/settings/LastCodeSettings.tsx @@ -247,6 +247,19 @@ export function LastCodeSettingsPanel() { /> } /> + + updateClientSettings({ showThreadWorktreeIndicators: Boolean(checked) }) + } + aria-label="Show worktree indicators" + /> + } + /> { expect(searchSettings("compact status").map((item) => item.id)).toEqual([ "compact-status-indicators", ]); + expect(searchSettings("worktree indicators").map((item) => item.id)).toEqual([ + "show-worktree-indicators", + ]); expect(searchSettings("xyzzy")).toEqual([]); }); diff --git a/apps/web/src/components/settings/settingsSearch.ts b/apps/web/src/components/settings/settingsSearch.ts index 1d94858750c9..5e4cf898e4f0 100644 --- a/apps/web/src/components/settings/settingsSearch.ts +++ b/apps/web/src/components/settings/settingsSearch.ts @@ -265,6 +265,11 @@ export const SETTINGS_SEARCH_ITEMS = [ title: "Compact status indicators", to: "/settings/lastcode", }, + { + id: "show-worktree-indicators", + title: "Show worktree indicators", + to: "/settings/lastcode", + }, { id: "rounded-project-icons", title: "Rounded project icons", diff --git a/docs/user/thread-sidebar.md b/docs/user/thread-sidebar.md index 955387a97216..f68aad0dcefc 100644 --- a/docs/user/thread-sidebar.md +++ b/docs/user/thread-sidebar.md @@ -70,6 +70,9 @@ indicators** in **Settings -> LastCode -> Appearance**. Legacy thread rows then colored status dot; the full status remains available as a tooltip. This preference is off by default. +To hide the worktree icon beside threads that use a dedicated worktree, turn off **Show worktree +indicators** in **Settings -> LastCode -> Appearance**. The icon remains visible by default. + ## Environment icons in LastCode Open **Settings -> LastCode -> Environments** to choose the icon color for the primary machine and diff --git a/packages/contracts/src/settings.test.ts b/packages/contracts/src/settings.test.ts index a93d564b40ab..233148d87830 100644 --- a/packages/contracts/src/settings.test.ts +++ b/packages/contracts/src/settings.test.ts @@ -116,6 +116,7 @@ describe("ClientSettings sidebar", () => { it("defaults to the current sidebar with automatic merge and inactivity settling", () => { const settings = decodeClientSettings({}); expect(settings.compactLegacySidebarStatuses).toBe(false); + expect(settings.showThreadWorktreeIndicators).toBe(true); expect(settings.legacySidebarEnabled).toBe(false); expect(settings.legacySidebarScale).toBe(100); expect(settings.sidebarAutoSettleAfterDays).toBe(3); @@ -149,6 +150,16 @@ describe("ClientSettings sidebar", () => { ).toBe(true); }); + it("preserves an explicit worktree indicator preference", () => { + expect( + decodeClientSettings({ showThreadWorktreeIndicators: false }).showThreadWorktreeIndicators, + ).toBe(false); + expect( + decodeClientSettingsPatch({ showThreadWorktreeIndicators: false }) + .showThreadWorktreeIndicators, + ).toBe(false); + }); + it.each([50, 75, 100])("accepts a legacy sidebar scale within 50..100: %s", (value) => { expect(decodeClientSettings({ legacySidebarScale: value }).legacySidebarScale).toBe(value); expect(decodeClientSettingsPatch({ legacySidebarScale: value }).legacySidebarScale).toBe(value); diff --git a/packages/contracts/src/settings.ts b/packages/contracts/src/settings.ts index aa09a5e6eb0b..4e690b9ed89f 100644 --- a/packages/contracts/src/settings.ts +++ b/packages/contracts/src/settings.ts @@ -184,6 +184,9 @@ export const ClientSettingsSchema = Schema.Struct({ compactLegacySidebarStatuses: Schema.Boolean.pipe( Schema.withDecodingDefault(Effect.succeed(false)), ), + showThreadWorktreeIndicators: Schema.Boolean.pipe( + Schema.withDecodingDefault(Effect.succeed(true)), + ), dismissedProviderUpdateNotificationKeys: Schema.Array(TrimmedNonEmptyString).pipe( Schema.withDecodingDefault(Effect.succeed([])), ), @@ -906,6 +909,7 @@ export const ClientSettingsPatch = Schema.Struct({ confirmThreadArchive: Schema.optionalKey(Schema.Boolean), confirmThreadDelete: Schema.optionalKey(Schema.Boolean), compactLegacySidebarStatuses: Schema.optionalKey(Schema.Boolean), + showThreadWorktreeIndicators: Schema.optionalKey(Schema.Boolean), diffIgnoreWhitespace: Schema.optionalKey(Schema.Boolean), environmentIdentificationMode: Schema.optionalKey(EnvironmentIdentificationMode), environmentIconColors: Schema.optionalKey(Schema.Record(EnvironmentId, EnvironmentIconColor)),