| author | |
| committer | |
| log | 7609495bf70a51dd4d8608e3f71da636a7348aa6 |
| tree | e734167ddb9dbac57fe0dc59ade5032142475765 |
| parent | b41e89eba9cd232cc3257b400fc30972a9b53438 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: codex <codex@users.noreply.github.com>4 files changed, 102 insertions(+), 49 deletions(-)
apps/web/src/components/Sidebar.tsx+1-49| ... | @@ -145,15 +145,7 @@ import { | ... | @@ -145,15 +145,7 @@ import { |
| 145 | DialogTitle, | 145 | DialogTitle, |
| 146 | } from "./ui/dialog"; | 146 | } from "./ui/dialog"; |
| 147 | import { Input } from "./ui/input"; | 147 | import { Input } from "./ui/input"; |
| 148 | import { | 148 | import { Menu, MenuGroup, MenuPopup, MenuRadioGroup, MenuRadioItem, MenuTrigger } from "./ui/menu"; |
| 149 | Menu, | ||
| 150 | MenuGroup, | ||
| 151 | MenuPopup, | ||
| 152 | MenuRadioGroup, | ||
| 153 | MenuRadioItem, | ||
| 154 | MenuSeparator, | ||
| 155 | MenuTrigger, | ||
| 156 | } from "./ui/menu"; | ||
| 157 | import { | 149 | import { |
| 158 | NumberField, | 150 | NumberField, |
| 159 | NumberFieldDecrement, | 151 | NumberFieldDecrement, |
| ... | @@ -2601,20 +2593,16 @@ type SortableProjectHandleProps = Pick< | ... | @@ -2601,20 +2593,16 @@ type SortableProjectHandleProps = Pick< |
| 2601 | function ProjectSortMenu({ | 2593 | function ProjectSortMenu({ |
| 2602 | projectSortOrder, | 2594 | projectSortOrder, |
| 2603 | threadSortOrder, | 2595 | threadSortOrder, |
| 2604 | projectGroupingMode, | ||
| 2605 | threadPreviewCount, | 2596 | threadPreviewCount, |
| 2606 | onProjectSortOrderChange, | 2597 | onProjectSortOrderChange, |
| 2607 | onThreadSortOrderChange, | 2598 | onThreadSortOrderChange, |
| 2608 | onProjectGroupingModeChange, | ||
| 2609 | onThreadPreviewCountChange, | 2599 | onThreadPreviewCountChange, |
| 2610 | }: { | 2600 | }: { |
| 2611 | projectSortOrder: SidebarProjectSortOrder; | 2601 | projectSortOrder: SidebarProjectSortOrder; |
| 2612 | threadSortOrder: SidebarThreadSortOrder; | 2602 | threadSortOrder: SidebarThreadSortOrder; |
| 2613 | projectGroupingMode: SidebarProjectGroupingMode; | ||
| 2614 | threadPreviewCount: SidebarThreadPreviewCount; | 2603 | threadPreviewCount: SidebarThreadPreviewCount; |
| 2615 | onProjectSortOrderChange: (sortOrder: SidebarProjectSortOrder) => void; | 2604 | onProjectSortOrderChange: (sortOrder: SidebarProjectSortOrder) => void; |
| 2616 | onThreadSortOrderChange: (sortOrder: SidebarThreadSortOrder) => void; | 2605 | onThreadSortOrderChange: (sortOrder: SidebarThreadSortOrder) => void; |
| 2617 | onProjectGroupingModeChange: (mode: SidebarProjectGroupingMode) => void; | ||
| 2618 | onThreadPreviewCountChange: (count: SidebarThreadPreviewCount) => void; | 2606 | onThreadPreviewCountChange: (count: SidebarThreadPreviewCount) => void; |
| 2619 | }) { | 2607 | }) { |
| 2620 | const handleThreadPreviewCountChange = useCallback( | 2608 | const handleThreadPreviewCountChange = useCallback( |
| ... | @@ -2718,30 +2706,6 @@ function ProjectSortMenu({ | ... | @@ -2718,30 +2706,6 @@ function ProjectSortMenu({ |
| 2718 | </NumberField> | 2706 | </NumberField> |
| 2719 | </div> | 2707 | </div> |
| 2720 | </MenuGroup> | 2708 | </MenuGroup> |
| 2721 | <MenuSeparator /> | ||
| 2722 | <MenuGroup> | ||
| 2723 | <div className="px-2 pt-2 pb-1 font-medium text-muted-foreground sm:text-xs"> | ||
| 2724 | Group projects | ||
| 2725 | </div> | ||
| 2726 | <MenuRadioGroup | ||
| 2727 | value={projectGroupingMode} | ||
| 2728 | onValueChange={(value) => { | ||
| 2729 | if (value === "repository" || value === "repository_path" || value === "separate") { | ||
| 2730 | onProjectGroupingModeChange(value); | ||
| 2731 | } | ||
| 2732 | }} | ||
| 2733 | > | ||
| 2734 | {( | ||
| 2735 | Object.entries(PROJECT_GROUPING_MODE_LABELS) as Array< | ||
| 2736 | [SidebarProjectGroupingMode, string] | ||
| 2737 | > | ||
| 2738 | ).map(([value, label]) => ( | ||
| 2739 | <MenuRadioItem key={value} value={value} className="min-h-7 py-1 sm:text-xs"> | ||
| 2740 | {label} | ||
| 2741 | </MenuRadioItem> | ||
| 2742 | ))} | ||
| 2743 | </MenuRadioGroup> | ||
| 2744 | </MenuGroup> | ||
| 2745 | </MenuPopup> | 2709 | </MenuPopup> |
| 2746 | </Menu> | 2710 | </Menu> |
| 2747 | ); | 2711 | ); |
| ... | @@ -2792,7 +2756,6 @@ interface SidebarProjectsContentProps { | ... | @@ -2792,7 +2756,6 @@ interface SidebarProjectsContentProps { |
| 2792 | handleDesktopUpdateButtonClick: () => void; | 2756 | handleDesktopUpdateButtonClick: () => void; |
| 2793 | projectSortOrder: SidebarProjectSortOrder; | 2757 | projectSortOrder: SidebarProjectSortOrder; |
| 2794 | threadSortOrder: SidebarThreadSortOrder; | 2758 | threadSortOrder: SidebarThreadSortOrder; |
| 2795 | projectGroupingMode: SidebarProjectGroupingMode; | ||
| 2796 | threadPreviewCount: SidebarThreadPreviewCount; | 2759 | threadPreviewCount: SidebarThreadPreviewCount; |
| 2797 | updateSettings: ReturnType<typeof useUpdateClientSettings>; | 2760 | updateSettings: ReturnType<typeof useUpdateClientSettings>; |
| 2798 | openAddProject: () => void; | 2761 | openAddProject: () => void; |
| ... | @@ -2833,7 +2796,6 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( | ... | @@ -2833,7 +2796,6 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( |
| 2833 | handleDesktopUpdateButtonClick, | 2796 | handleDesktopUpdateButtonClick, |
| 2834 | projectSortOrder, | 2797 | projectSortOrder, |
| 2835 | threadSortOrder, | 2798 | threadSortOrder, |
| 2836 | projectGroupingMode, | ||
| 2837 | threadPreviewCount, | 2799 | threadPreviewCount, |
| 2838 | updateSettings, | 2800 | updateSettings, |
| 2839 | openAddProject, | 2801 | openAddProject, |
| ... | @@ -2875,12 +2837,6 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( | ... | @@ -2875,12 +2837,6 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( |
| 2875 | }, | 2837 | }, |
| 2876 | [updateSettings], | 2838 | [updateSettings], |
| 2877 | ); | 2839 | ); |
| 2878 | const handleProjectGroupingModeChange = useCallback( | ||
| 2879 | (groupingMode: SidebarProjectGroupingMode) => { | ||
| 2880 | updateSettings({ sidebarProjectGroupingMode: groupingMode }); | ||
| 2881 | }, | ||
| 2882 | [updateSettings], | ||
| 2883 | ); | ||
| 2884 | const handleThreadPreviewCountChange = useCallback( | 2840 | const handleThreadPreviewCountChange = useCallback( |
| 2885 | (count: SidebarThreadPreviewCount) => { | 2841 | (count: SidebarThreadPreviewCount) => { |
| 2886 | updateSettings({ sidebarThreadPreviewCount: count }); | 2842 | updateSettings({ sidebarThreadPreviewCount: count }); |
| ... | @@ -2944,11 +2900,9 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( | ... | @@ -2944,11 +2900,9 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( |
| 2944 | <ProjectSortMenu | 2900 | <ProjectSortMenu |
| 2945 | projectSortOrder={projectSortOrder} | 2901 | projectSortOrder={projectSortOrder} |
| 2946 | threadSortOrder={threadSortOrder} | 2902 | threadSortOrder={threadSortOrder} |
| 2947 | projectGroupingMode={projectGroupingMode} | ||
| 2948 | threadPreviewCount={threadPreviewCount} | 2903 | threadPreviewCount={threadPreviewCount} |
| 2949 | onProjectSortOrderChange={handleProjectSortOrderChange} | 2904 | onProjectSortOrderChange={handleProjectSortOrderChange} |
| 2950 | onThreadSortOrderChange={handleThreadSortOrderChange} | 2905 | onThreadSortOrderChange={handleThreadSortOrderChange} |
| 2951 | onProjectGroupingModeChange={handleProjectGroupingModeChange} | ||
| 2952 | onThreadPreviewCountChange={handleThreadPreviewCountChange} | 2906 | onThreadPreviewCountChange={handleThreadPreviewCountChange} |
| 2953 | /> | 2907 | /> |
| 2954 | <Tooltip> | 2908 | <Tooltip> |
| ... | @@ -3064,7 +3018,6 @@ export default function Sidebar() { | ... | @@ -3064,7 +3018,6 @@ export default function Sidebar() { |
| 3064 | const isOnSettings = pathname.startsWith("/settings"); | 3018 | const isOnSettings = pathname.startsWith("/settings"); |
| 3065 | const sidebarThreadSortOrder = useClientSettings((s) => s.sidebarThreadSortOrder); | 3019 | const sidebarThreadSortOrder = useClientSettings((s) => s.sidebarThreadSortOrder); |
| 3066 | const sidebarProjectSortOrder = useClientSettings((s) => s.sidebarProjectSortOrder); | 3020 | const sidebarProjectSortOrder = useClientSettings((s) => s.sidebarProjectSortOrder); |
| 3067 | const sidebarProjectGroupingMode = useClientSettings((s) => s.sidebarProjectGroupingMode); | ||
| 3068 | const projectGroupingSettings = useClientSettings(selectProjectGroupingSettings); | 3021 | const projectGroupingSettings = useClientSettings(selectProjectGroupingSettings); |
| 3069 | const sidebarThreadPreviewCount = useClientSettings((s) => s.sidebarThreadPreviewCount); | 3022 | const sidebarThreadPreviewCount = useClientSettings((s) => s.sidebarThreadPreviewCount); |
| 3070 | const updateSettings = useUpdateClientSettings(); | 3023 | const updateSettings = useUpdateClientSettings(); |
| ... | @@ -3674,7 +3627,6 @@ export default function Sidebar() { | ... | @@ -3674,7 +3627,6 @@ export default function Sidebar() { |
| 3674 | handleDesktopUpdateButtonClick={handleDesktopUpdateButtonClick} | 3627 | handleDesktopUpdateButtonClick={handleDesktopUpdateButtonClick} |
| 3675 | projectSortOrder={sidebarProjectSortOrder} | 3628 | projectSortOrder={sidebarProjectSortOrder} |
| 3676 | threadSortOrder={sidebarThreadSortOrder} | 3629 | threadSortOrder={sidebarThreadSortOrder} |
| 3677 | projectGroupingMode={sidebarProjectGroupingMode} | ||
| 3678 | threadPreviewCount={sidebarThreadPreviewCount} | 3630 | threadPreviewCount={sidebarThreadPreviewCount} |
| 3679 | updateSettings={updateSettings} | 3631 | updateSettings={updateSettings} |
| 3680 | openAddProject={openAddProjectCommandPalette} | 3632 | openAddProject={openAddProjectCommandPalette} |
apps/web/src/components/settings/SettingsPanels.logic.test.ts+17| ... | @@ -8,8 +8,25 @@ import { describe, expect, it } from "vite-plus/test"; | ... | @@ -8,8 +8,25 @@ import { describe, expect, it } from "vite-plus/test"; |
| 8 | import { | 8 | import { |
| 9 | buildProviderInstanceUpdatePatch, | 9 | buildProviderInstanceUpdatePatch, |
| 10 | formatDiagnosticsDescription, | 10 | formatDiagnosticsDescription, |
| 11 | isProjectGroupingEnabled, | ||
| 12 | projectGroupingModeFromToggle, | ||
| 11 | } from "./SettingsPanels.logic"; | 13 | } from "./SettingsPanels.logic"; |
| 12 | 14 | ||
| 15 | describe("project grouping toggle", () => { | ||
| 16 | it("enables repository grouping and disables into separate projects", () => { | ||
| 17 | expect(isProjectGroupingEnabled("repository")).toBe(true); | ||
| 18 | expect(isProjectGroupingEnabled("repository_path")).toBe(true); | ||
| 19 | expect(isProjectGroupingEnabled("separate")).toBe(false); | ||
| 20 | expect(projectGroupingModeFromToggle(true)).toBe("repository"); | ||
| 21 | expect(projectGroupingModeFromToggle(false)).toBe("separate"); | ||
| 22 | }); | ||
| 23 | |||
| 24 | it("restores repository path grouping when the toggle is cycled", () => { | ||
| 25 | expect(projectGroupingModeFromToggle(false, "repository_path")).toBe("separate"); | ||
| 26 | expect(projectGroupingModeFromToggle(true, "repository_path")).toBe("repository_path"); | ||
| 27 | }); | ||
| 28 | }); | ||
| 29 | |||
| 13 | describe("formatDiagnosticsDescription", () => { | 30 | describe("formatDiagnosticsDescription", () => { |
| 14 | it("collapses trace and metric URLs that share the same OTEL base path", () => { | 31 | it("collapses trace and metric URLs that share the same OTEL base path", () => { |
| 15 | expect( | 32 | expect( |
apps/web/src/components/settings/SettingsPanels.logic.ts+34| ... | @@ -3,10 +3,44 @@ import type { | ... | @@ -3,10 +3,44 @@ import type { |
| 3 | ProviderInstanceConfig, | 3 | ProviderInstanceConfig, |
| 4 | ProviderInstanceId, | 4 | ProviderInstanceId, |
| 5 | ServerSettings, | 5 | ServerSettings, |
| 6 | SidebarProjectGroupingMode, | ||
| 6 | UnifiedSettings, | 7 | UnifiedSettings, |
| 7 | } from "@t3tools/contracts"; | 8 | } from "@t3tools/contracts"; |
| 8 | import { DEFAULT_UNIFIED_SETTINGS } from "@t3tools/contracts/settings"; | 9 | import { DEFAULT_UNIFIED_SETTINGS } from "@t3tools/contracts/settings"; |
| 9 | 10 | ||
| 11 | export function isProjectGroupingEnabled(mode: SidebarProjectGroupingMode): boolean { | ||
| 12 | return mode !== "separate"; | ||
| 13 | } | ||
| 14 | |||
| 15 | export function projectGroupingModeFromToggle( | ||
| 16 | enabled: boolean, | ||
| 17 | lastEnabledMode: SidebarProjectGroupingMode = "repository", | ||
| 18 | ): SidebarProjectGroupingMode { | ||
| 19 | if (!enabled) return "separate"; | ||
| 20 | return lastEnabledMode === "repository_path" ? "repository_path" : "repository"; | ||
| 21 | } | ||
| 22 | |||
| 23 | const LAST_ENABLED_PROJECT_GROUPING_MODE_KEY = "t3code:last-enabled-project-grouping-mode"; | ||
| 24 | |||
| 25 | export function readLastEnabledProjectGroupingMode(): SidebarProjectGroupingMode { | ||
| 26 | try { | ||
| 27 | return localStorage.getItem(LAST_ENABLED_PROJECT_GROUPING_MODE_KEY) === "repository_path" | ||
| 28 | ? "repository_path" | ||
| 29 | : "repository"; | ||
| 30 | } catch { | ||
| 31 | return "repository"; | ||
| 32 | } | ||
| 33 | } | ||
| 34 | |||
| 35 | export function rememberEnabledProjectGroupingMode(mode: SidebarProjectGroupingMode): void { | ||
| 36 | if (mode === "separate") return; | ||
| 37 | try { | ||
| 38 | localStorage.setItem(LAST_ENABLED_PROJECT_GROUPING_MODE_KEY, mode); | ||
| 39 | } catch { | ||
| 40 | // Storage can be unavailable in restricted browser contexts. | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 10 | function collapseOtelSignalsUrl(input: { | 44 | function collapseOtelSignalsUrl(input: { |
| 11 | readonly tracesUrl: string; | 45 | readonly tracesUrl: string; |
| 12 | readonly metricsUrl: string; | 46 | readonly metricsUrl: string; |
apps/web/src/components/settings/SettingsPanels.tsx+50| ... | @@ -11,6 +11,7 @@ import { | ... | @@ -11,6 +11,7 @@ import { |
| 11 | type ProviderInstanceConfig, | 11 | type ProviderInstanceConfig, |
| 12 | type ProviderInstanceId, | 12 | type ProviderInstanceId, |
| 13 | type ScopedThreadRef, | 13 | type ScopedThreadRef, |
| 14 | type SidebarProjectGroupingMode, | ||
| 14 | } from "@t3tools/contracts"; | 15 | } from "@t3tools/contracts"; |
| 15 | import { scopeThreadRef } from "@t3tools/client-runtime/environment"; | 16 | import { scopeThreadRef } from "@t3tools/client-runtime/environment"; |
| 16 | import { safeErrorLogAttributes } from "@t3tools/client-runtime/errors"; | 17 | import { safeErrorLogAttributes } from "@t3tools/client-runtime/errors"; |
| ... | @@ -83,6 +84,10 @@ import { DRIVER_OPTIONS, getDriverOption } from "./providerDriverMeta"; | ... | @@ -83,6 +84,10 @@ import { DRIVER_OPTIONS, getDriverOption } from "./providerDriverMeta"; |
| 83 | import { | 84 | import { |
| 84 | buildProviderInstanceUpdatePatch, | 85 | buildProviderInstanceUpdatePatch, |
| 85 | formatDiagnosticsDescription, | 86 | formatDiagnosticsDescription, |
| 87 | isProjectGroupingEnabled, | ||
| 88 | projectGroupingModeFromToggle, | ||
| 89 | readLastEnabledProjectGroupingMode, | ||
| 90 | rememberEnabledProjectGroupingMode, | ||
| 86 | } from "./SettingsPanels.logic"; | 91 | } from "./SettingsPanels.logic"; |
| 87 | import { | 92 | import { |
| 88 | SettingResetButton, | 93 | SettingResetButton, |
| ... | @@ -402,6 +407,10 @@ export function useSettingsRestore(onRestored?: () => void) { | ... | @@ -402,6 +407,10 @@ export function useSettingsRestore(onRestored?: () => void) { |
| 402 | ...(settings.sidebarThreadPreviewCount !== DEFAULT_UNIFIED_SETTINGS.sidebarThreadPreviewCount | 407 | ...(settings.sidebarThreadPreviewCount !== DEFAULT_UNIFIED_SETTINGS.sidebarThreadPreviewCount |
| 403 | ? ["Visible threads"] | 408 | ? ["Visible threads"] |
| 404 | : []), | 409 | : []), |
| 410 | ...(settings.sidebarProjectGroupingMode !== | ||
| 411 | DEFAULT_UNIFIED_SETTINGS.sidebarProjectGroupingMode | ||
| 412 | ? ["Project Grouping"] | ||
| 413 | : []), | ||
| 405 | ...(settings.wordWrap !== DEFAULT_UNIFIED_SETTINGS.wordWrap ? ["Word wrap"] : []), | 414 | ...(settings.wordWrap !== DEFAULT_UNIFIED_SETTINGS.wordWrap ? ["Word wrap"] : []), |
| 406 | ...(settings.diffIgnoreWhitespace !== DEFAULT_UNIFIED_SETTINGS.diffIgnoreWhitespace | 415 | ...(settings.diffIgnoreWhitespace !== DEFAULT_UNIFIED_SETTINGS.diffIgnoreWhitespace |
| 407 | ? ["Diff whitespace changes"] | 416 | ? ["Diff whitespace changes"] |
| ... | @@ -451,6 +460,7 @@ export function useSettingsRestore(onRestored?: () => void) { | ... | @@ -451,6 +460,7 @@ export function useSettingsRestore(onRestored?: () => void) { |
| 451 | settings.automaticGitFetchInterval, | 460 | settings.automaticGitFetchInterval, |
| 452 | settings.enableAssistantStreaming, | 461 | settings.enableAssistantStreaming, |
| 453 | settings.enableProviderUpdateChecks, | 462 | settings.enableProviderUpdateChecks, |
| 463 | settings.sidebarProjectGroupingMode, | ||
| 454 | settings.sidebarThreadPreviewCount, | 464 | settings.sidebarThreadPreviewCount, |
| 455 | settings.timestampFormat, | 465 | settings.timestampFormat, |
| 456 | settings.wordWrap, | 466 | settings.wordWrap, |
| ... | @@ -475,6 +485,7 @@ export function useSettingsRestore(onRestored?: () => void) { | ... | @@ -475,6 +485,7 @@ export function useSettingsRestore(onRestored?: () => void) { |
| 475 | diffIgnoreWhitespace: DEFAULT_UNIFIED_SETTINGS.diffIgnoreWhitespace, | 485 | diffIgnoreWhitespace: DEFAULT_UNIFIED_SETTINGS.diffIgnoreWhitespace, |
| 476 | glassOpacity: DEFAULT_UNIFIED_SETTINGS.glassOpacity, | 486 | glassOpacity: DEFAULT_UNIFIED_SETTINGS.glassOpacity, |
| 477 | sidebarThreadPreviewCount: DEFAULT_UNIFIED_SETTINGS.sidebarThreadPreviewCount, | 487 | sidebarThreadPreviewCount: DEFAULT_UNIFIED_SETTINGS.sidebarThreadPreviewCount, |
| 488 | sidebarProjectGroupingMode: DEFAULT_UNIFIED_SETTINGS.sidebarProjectGroupingMode, | ||
| 478 | autoOpenPlanSidebar: DEFAULT_UNIFIED_SETTINGS.autoOpenPlanSidebar, | 489 | autoOpenPlanSidebar: DEFAULT_UNIFIED_SETTINGS.autoOpenPlanSidebar, |
| 479 | enableAssistantStreaming: DEFAULT_UNIFIED_SETTINGS.enableAssistantStreaming, | 490 | enableAssistantStreaming: DEFAULT_UNIFIED_SETTINGS.enableAssistantStreaming, |
| 480 | enableProviderUpdateChecks: DEFAULT_UNIFIED_SETTINGS.enableProviderUpdateChecks, | 491 | enableProviderUpdateChecks: DEFAULT_UNIFIED_SETTINGS.enableProviderUpdateChecks, |
| ... | @@ -499,6 +510,9 @@ export function GeneralSettingsPanel() { | ... | @@ -499,6 +510,9 @@ export function GeneralSettingsPanel() { |
| 499 | const { theme, setTheme } = useTheme(); | 510 | const { theme, setTheme } = useTheme(); |
| 500 | const settings = usePrimarySettings(); | 511 | const settings = usePrimarySettings(); |
| 501 | const updateSettings = useUpdatePrimarySettings(); | 512 | const updateSettings = useUpdatePrimarySettings(); |
| 513 | const lastEnabledProjectGroupingMode = useRef<SidebarProjectGroupingMode>( | ||
| 514 | readLastEnabledProjectGroupingMode(), | ||
| 515 | ); | ||
| 502 | const observability = useAtomValue(primaryServerObservabilityAtom); | 516 | const observability = useAtomValue(primaryServerObservabilityAtom); |
| 503 | const serverProviders = useAtomValue(primaryServerProvidersAtom); | 517 | const serverProviders = useAtomValue(primaryServerProvidersAtom); |
| 504 | const glassOpacityRatio = | 518 | const glassOpacityRatio = |
| ... | @@ -620,6 +634,42 @@ export function GeneralSettingsPanel() { | ... | @@ -620,6 +634,42 @@ export function GeneralSettingsPanel() { |
| 620 | } | 634 | } |
| 621 | /> | 635 | /> |
| 622 | 636 | ||
| 637 | <SettingsRow | ||
| 638 | title="Project Grouping" | ||
| 639 | description="Combine matching repositories across environments." | ||
| 640 | resetAction={ | ||
| 641 | settings.sidebarProjectGroupingMode !== | ||
| 642 | DEFAULT_UNIFIED_SETTINGS.sidebarProjectGroupingMode ? ( | ||
| 643 | <SettingResetButton | ||
| 644 | label="project grouping" | ||
| 645 | onClick={() => | ||
| 646 | updateSettings({ | ||
| 647 | sidebarProjectGroupingMode: DEFAULT_UNIFIED_SETTINGS.sidebarProjectGroupingMode, | ||
| 648 | }) | ||
| 649 | } | ||
| 650 | /> | ||
| 651 | ) : null | ||
| 652 | } | ||
| 653 | control={ | ||
| 654 | <Switch | ||
| 655 | checked={isProjectGroupingEnabled(settings.sidebarProjectGroupingMode)} | ||
| 656 | onCheckedChange={(checked) => { | ||
| 657 | if (!checked && settings.sidebarProjectGroupingMode !== "separate") { | ||
| 658 | lastEnabledProjectGroupingMode.current = settings.sidebarProjectGroupingMode; | ||
| 659 | rememberEnabledProjectGroupingMode(settings.sidebarProjectGroupingMode); | ||
| 660 | } | ||
| 661 | updateSettings({ | ||
| 662 | sidebarProjectGroupingMode: projectGroupingModeFromToggle( | ||
| 663 | checked, | ||
| 664 | lastEnabledProjectGroupingMode.current, | ||
| 665 | ), | ||
| 666 | }); | ||
| 667 | }} | ||
| 668 | aria-label="Project Grouping" | ||
| 669 | /> | ||
| 670 | } | ||
| 671 | /> | ||
| 672 | |||
| 623 | <SettingsRow | 673 | <SettingsRow |
| 624 | title="Time format" | 674 | title="Time format" |
| 625 | description="System default follows your browser or OS clock preference." | 675 | description="System default follows your browser or OS clock preference." |