diff --git a/apps/desktop/src/settings/DesktopClientSettings.test.ts b/apps/desktop/src/settings/DesktopClientSettings.test.ts index cb63ba62605a..4867d8524093 100644 --- a/apps/desktop/src/settings/DesktopClientSettings.test.ts +++ b/apps/desktop/src/settings/DesktopClientSettings.test.ts @@ -18,6 +18,7 @@ const clientSettings: ClientSettings = { browserDefaultZoomFactor: 1.25, browserDefaultAppearance: "dark", browserAutoShowFloatingPreview: false, + compactLegacySidebarStatuses: false, 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 0d4a992a97de..f2ff48a9ae4c 100644 --- a/apps/desktop/src/settings/LastCodeSettingsImport.test.ts +++ b/apps/desktop/src/settings/LastCodeSettingsImport.test.ts @@ -91,6 +91,7 @@ describe("LastCodeSettingsImport", () => { const sourceCustom = ProviderInstanceId.make("source_custom"); const lastCodeCustom = ProviderInstanceId.make("lastcode_custom"); const sourceClient = { ...DEFAULT_CLIENT_SETTINGS, fontSizeInterface: 17 }; + Reflect.deleteProperty(sourceClient, "compactLegacySidebarStatuses"); Reflect.deleteProperty(sourceClient, "legacySidebarScale"); Reflect.deleteProperty(sourceClient, "roundedProjectIcons"); sourceClient.favorites = [ @@ -103,6 +104,7 @@ describe("LastCodeSettingsImport", () => { }; const destinationClient = { ...DEFAULT_CLIENT_SETTINGS, + compactLegacySidebarStatuses: true, legacySidebarScale: 75, roundedProjectIcons: true, environmentIconColors: { primary: "#2563eb", remote: "#7c3aed" }, @@ -215,6 +217,7 @@ describe("LastCodeSettingsImport", () => { ) as unknown, ); assert.equal(importedClient.fontSizeInterface, 17); + assert.equal(importedClient.compactLegacySidebarStatuses, true); 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 d21cfdfbd12f..806b227f9564 100644 --- a/apps/desktop/src/settings/LastCodeSettingsImport.ts +++ b/apps/desktop/src/settings/LastCodeSettingsImport.ts @@ -236,6 +236,7 @@ function mergeClientSettings(sourceRaw: string, destinationRaw: string | null): ...source, favorites, providerModelPreferences, + compactLegacySidebarStatuses: destination.compactLegacySidebarStatuses, environmentIconColors: destination.environmentIconColors, legacySidebarScale: destination.legacySidebarScale, roundedProjectIcons: destination.roundedProjectIcons, diff --git a/apps/web/src/components/LegacySidebar.tsx b/apps/web/src/components/LegacySidebar.tsx index 1c632a4d48f7..45172ea581c8 100644 --- a/apps/web/src/components/LegacySidebar.tsx +++ b/apps/web/src/components/LegacySidebar.tsx @@ -340,6 +340,7 @@ function buildThreadJumpLabelMap(input: { interface SidebarThreadRowProps { thread: SidebarThreadSummary; + compactStatusIndicators: boolean; showLocalEnvironmentIcon: boolean; configuredEnvironmentIconColor: EnvironmentIconColor | undefined; projectCwd: string | null; @@ -889,7 +890,9 @@ export const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThr )} - {threadStatus && } + {threadStatus && ( + + )} {renamingThreadKey === threadKey ? ( >; environmentIconColors: Readonly>; @@ -1201,6 +1205,7 @@ const SidebarProjectThreadList = memo(function SidebarProjectThreadList( ) { const { legacySidebarScale, + compactStatusIndicators, scaleStyle, providerEntriesByEnvironmentId, environmentIconColors, @@ -1271,6 +1276,7 @@ const SidebarProjectThreadList = memo(function SidebarProjectThreadList( >; desktopLocalEnvironmentIds: ReadonlySet; @@ -1372,6 +1379,7 @@ interface SidebarProjectItemProps { const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjectItemProps) { const { legacySidebarScale, + compactStatusIndicators, scaleStyle, providerEntriesByEnvironmentId, project, @@ -2755,6 +2763,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec void; projectsLength: number; legacySidebarScale: LegacySidebarScale; + compactStatusIndicators: boolean; projectTreeScaleStyle: CSSProperties; providerEntriesByEnvironmentId: ReadonlyMap>; desktopLocalEnvironmentIds: ReadonlySet; @@ -3328,6 +3338,7 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( attachProjectListAutoAnimateRef, projectsLength, legacySidebarScale, + compactStatusIndicators, projectTreeScaleStyle, providerEntriesByEnvironmentId, desktopLocalEnvironmentIds, @@ -3461,6 +3472,7 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( {(dragHandleProps) => ( s.sidebarThreadPreviewCount); const legacySidebarScale = useClientSettings((s) => s.legacySidebarScale); + const compactStatusIndicators = useClientSettings( + (settings) => settings.compactLegacySidebarStatuses, + ); const environmentIconColors = useClientSettings((s) => s.environmentIconColors); const showLocalEnvironmentIcon = useClientSettings((s) => s.showLocalEnvironmentIcon); const serverProviders = useAtomValue(primaryServerProvidersAtom); @@ -4247,6 +4263,7 @@ export default function LegacySidebar() { attachProjectListAutoAnimateRef={attachProjectListAutoAnimateRef} projectsLength={projects.length} legacySidebarScale={legacySidebarScale} + compactStatusIndicators={compactStatusIndicators} projectTreeScaleStyle={scaleStyle} providerEntriesByEnvironmentId={providerEntriesByEnvironmentId} desktopLocalEnvironmentIds={desktopLocalEnvironmentIds} diff --git a/apps/web/src/components/ThreadStatusIndicators.tsx b/apps/web/src/components/ThreadStatusIndicators.tsx index b2a8dcb7046e..a94fc5e6e6b0 100644 --- a/apps/web/src/components/ThreadStatusIndicators.tsx +++ b/apps/web/src/components/ThreadStatusIndicators.tsx @@ -361,6 +361,7 @@ export function ThreadStatusLabel({ diff --git a/apps/web/src/components/settings/LastCodeSettings.tsx b/apps/web/src/components/settings/LastCodeSettings.tsx index 537b02ffd020..c083c65d5291 100644 --- a/apps/web/src/components/settings/LastCodeSettings.tsx +++ b/apps/web/src/components/settings/LastCodeSettings.tsx @@ -234,6 +234,19 @@ export function LastCodeSettingsPanel() { /> } /> + + updateClientSettings({ compactLegacySidebarStatuses: Boolean(checked) }) + } + aria-label="Compact status indicators" + /> + } + /> { it("matches normalized title substrings", () => { expect(searchSettings(" WORD WRAP ", ITEMS).map((item) => item.id)).toEqual(["word-wrap"]); expect(searchSettings("glass").map((item) => item.id)).toEqual(["setting-glass-opacity"]); + expect(searchSettings("compact status").map((item) => item.id)).toEqual([ + "compact-status-indicators", + ]); expect(searchSettings("xyzzy")).toEqual([]); }); diff --git a/apps/web/src/components/settings/settingsSearch.ts b/apps/web/src/components/settings/settingsSearch.ts index f59bb20b8cdd..1d94858750c9 100644 --- a/apps/web/src/components/settings/settingsSearch.ts +++ b/apps/web/src/components/settings/settingsSearch.ts @@ -260,6 +260,11 @@ export const SETTINGS_SEARCH_ITEMS = [ title: "Scale legacy sidebar", to: "/settings/lastcode", }, + { + id: "compact-status-indicators", + title: "Compact status 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 4a86d647afda..955387a97216 100644 --- a/docs/user/thread-sidebar.md +++ b/docs/user/thread-sidebar.md @@ -65,6 +65,11 @@ LastCode header, Search field, Projects heading, drafts, status notices, and foo their standard size. On desktop, **View -> Actual Size**, **Zoom In**, and **Zoom Out** continue to zoom the whole application and compose with the legacy sidebar scale. +To keep the same status colors while using less horizontal space, enable **Compact status +indicators** in **Settings -> LastCode -> Appearance**. Legacy thread rows then show only the +colored status dot; the full status remains available as a tooltip. This preference is off 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 23b585778464..a93d564b40ab 100644 --- a/packages/contracts/src/settings.test.ts +++ b/packages/contracts/src/settings.test.ts @@ -115,6 +115,7 @@ describe("ClientSettings environment icons", () => { 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.legacySidebarEnabled).toBe(false); expect(settings.legacySidebarScale).toBe(100); expect(settings.sidebarAutoSettleAfterDays).toBe(3); @@ -138,6 +139,16 @@ describe("ClientSettings sidebar", () => { ); }); + it("preserves compact legacy sidebar status indicators", () => { + expect( + decodeClientSettings({ compactLegacySidebarStatuses: true }).compactLegacySidebarStatuses, + ).toBe(true); + expect( + decodeClientSettingsPatch({ compactLegacySidebarStatuses: true }) + .compactLegacySidebarStatuses, + ).toBe(true); + }); + 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 d65f63759974..aa09a5e6eb0b 100644 --- a/packages/contracts/src/settings.ts +++ b/packages/contracts/src/settings.ts @@ -181,6 +181,9 @@ export const ClientSettingsSchema = Schema.Struct({ confirmQuit: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))), confirmThreadArchive: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), confirmThreadDelete: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))), + compactLegacySidebarStatuses: Schema.Boolean.pipe( + Schema.withDecodingDefault(Effect.succeed(false)), + ), dismissedProviderUpdateNotificationKeys: Schema.Array(TrimmedNonEmptyString).pipe( Schema.withDecodingDefault(Effect.succeed([])), ), @@ -902,6 +905,7 @@ export const ClientSettingsPatch = Schema.Struct({ confirmQuit: Schema.optionalKey(Schema.Boolean), confirmThreadArchive: Schema.optionalKey(Schema.Boolean), confirmThreadDelete: Schema.optionalKey(Schema.Boolean), + compactLegacySidebarStatuses: Schema.optionalKey(Schema.Boolean), diffIgnoreWhitespace: Schema.optionalKey(Schema.Boolean), environmentIdentificationMode: Schema.optionalKey(EnvironmentIdentificationMode), environmentIconColors: Schema.optionalKey(Schema.Record(EnvironmentId, EnvironmentIconColor)),