Add workspace title display preference
This commit is contained in:
@@ -52,7 +52,7 @@ interface WorkspaceStatusGroupEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function switchSidebarToStatusGrouping(page: import("@playwright/test").Page) {
|
async function switchSidebarToStatusGrouping(page: import("@playwright/test").Page) {
|
||||||
await page.getByTestId("sidebar-grouping-selector").click();
|
await page.getByTestId("sidebar-display-preferences-menu").click();
|
||||||
await page.getByTestId("sidebar-grouping-status").click();
|
await page.getByTestId("sidebar-grouping-status").click();
|
||||||
await expect(page.getByTestId("sidebar-status-group-done")).toBeVisible({ timeout: 30_000 });
|
await expect(page.getByTestId("sidebar-status-group-done")).toBeVisible({ timeout: 30_000 });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ test.describe("Model B sidebar shape", () => {
|
|||||||
await expect(workspaceRow(page, idleProject.workspaceId)).toBeVisible({ timeout: 30_000 });
|
await expect(workspaceRow(page, idleProject.workspaceId)).toBeVisible({ timeout: 30_000 });
|
||||||
|
|
||||||
// Switch to status grouping.
|
// Switch to status grouping.
|
||||||
await page.getByTestId("sidebar-grouping-selector").click();
|
await page.getByTestId("sidebar-display-preferences-menu").click();
|
||||||
await page.getByTestId("sidebar-grouping-status").click();
|
await page.getByTestId("sidebar-grouping-status").click();
|
||||||
|
|
||||||
const sidebar = page.getByTestId("sidebar-sessions").filter({ visible: true }).first();
|
const sidebar = page.getByTestId("sidebar-sessions").filter({ visible: true }).first();
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ async function fetchAgentStatus(seeded: SeededWorkspace, agentId: string): Promi
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function switchSidebarToStatusGrouping(page: import("@playwright/test").Page) {
|
async function switchSidebarToStatusGrouping(page: import("@playwright/test").Page) {
|
||||||
await page.getByTestId("sidebar-grouping-selector").click();
|
await page.getByTestId("sidebar-display-preferences-menu").click();
|
||||||
await page.getByTestId("sidebar-grouping-status").click();
|
await page.getByTestId("sidebar-grouping-status").click();
|
||||||
await expect(page.locator('[data-testid^="sidebar-status-group-"]').first()).toBeVisible({
|
await expect(page.locator('[data-testid^="sidebar-status-group-"]').first()).toBeVisible({
|
||||||
timeout: 30_000,
|
timeout: 30_000,
|
||||||
|
|||||||
@@ -390,7 +390,7 @@ async function expectWorkspaceRowInStatusBucket(
|
|||||||
page: Page,
|
page: Page,
|
||||||
input: { serverId: string; workspaceId: string; bucket: string },
|
input: { serverId: string; workspaceId: string; bucket: string },
|
||||||
) {
|
) {
|
||||||
await page.getByTestId("sidebar-grouping-selector").click();
|
await page.getByTestId("sidebar-display-preferences-menu").click();
|
||||||
await page.getByTestId("sidebar-grouping-status").click();
|
await page.getByTestId("sidebar-grouping-status").click();
|
||||||
await expect(
|
await expect(
|
||||||
page
|
page
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
import { TitlebarDragRegion } from "@/components/desktop/titlebar-drag-region";
|
import { TitlebarDragRegion } from "@/components/desktop/titlebar-drag-region";
|
||||||
import { SidebarHeaderRow } from "@/components/sidebar/sidebar-header-row";
|
import { SidebarHeaderRow } from "@/components/sidebar/sidebar-header-row";
|
||||||
import { SidebarGroupingSelector } from "@/components/sidebar/sidebar-grouping-selector";
|
import { SidebarDisplayPreferencesMenu } from "@/components/sidebar/sidebar-display-preferences-menu";
|
||||||
import { Combobox, ComboboxItem, type ComboboxOption } from "@/components/ui/combobox";
|
import { Combobox, ComboboxItem, type ComboboxOption } from "@/components/ui/combobox";
|
||||||
import { Shortcut } from "@/components/ui/shortcut";
|
import { Shortcut } from "@/components/ui/shortcut";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
@@ -1096,7 +1096,7 @@ function WorkspacesSectionHeader({ serverId }: { serverId: string | null }) {
|
|||||||
<Tooltip delayDuration={300}>
|
<Tooltip delayDuration={300}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<View>
|
<View>
|
||||||
<SidebarGroupingSelector serverId={serverId} />
|
<SidebarDisplayPreferencesMenu serverId={serverId} />
|
||||||
</View>
|
</View>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="bottom" align="center" offset={8}>
|
<TooltipContent side="bottom" align="center" offset={8}>
|
||||||
|
|||||||
+52
-10
@@ -8,9 +8,11 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
|
DropdownMenuSeparator,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { useSidebarViewStore, type SidebarGroupMode } from "@/stores/sidebar-view-store";
|
import { useSidebarViewStore, type SidebarGroupMode } from "@/stores/sidebar-view-store";
|
||||||
import { isWeb as platformIsWeb } from "@/constants/platform";
|
import { isWeb as platformIsWeb } from "@/constants/platform";
|
||||||
|
import { useAppSettings, type WorkspaceTitleSource } from "@/hooks/use-settings";
|
||||||
|
|
||||||
const ThemedSettings2 = withUnistyles(Settings2);
|
const ThemedSettings2 = withUnistyles(Settings2);
|
||||||
const filterColorMapping = (theme: Theme) => ({ color: theme.colors.foregroundMuted });
|
const filterColorMapping = (theme: Theme) => ({ color: theme.colors.foregroundMuted });
|
||||||
@@ -20,11 +22,25 @@ const GROUP_MODE_ITEMS: Array<{ value: SidebarGroupMode; label: string }> = [
|
|||||||
{ value: "status", label: "Status" },
|
{ value: "status", label: "Status" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function SidebarGroupingSelector({ serverId }: { serverId: string | null }) {
|
const WORKSPACE_TITLE_SOURCE_ITEMS: Array<{ value: WorkspaceTitleSource; label: string }> = [
|
||||||
|
{ value: "title", label: "Title" },
|
||||||
|
{ value: "branch", label: "Branch name" },
|
||||||
|
];
|
||||||
|
|
||||||
|
interface DisplayPreferenceOption<Value extends string> {
|
||||||
|
value: Value;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SidebarDisplayPreferencesMenu({ serverId }: { serverId: string | null }) {
|
||||||
const groupMode = useSidebarViewStore((state) =>
|
const groupMode = useSidebarViewStore((state) =>
|
||||||
serverId ? state.getGroupMode(serverId) : "project",
|
serverId ? state.getGroupMode(serverId) : "project",
|
||||||
);
|
);
|
||||||
const setGroupMode = useSidebarViewStore((state) => state.setGroupMode);
|
const setGroupMode = useSidebarViewStore((state) => state.setGroupMode);
|
||||||
|
const {
|
||||||
|
settings: { workspaceTitleSource },
|
||||||
|
updateSettings,
|
||||||
|
} = useAppSettings();
|
||||||
|
|
||||||
const handleSelect = useCallback(
|
const handleSelect = useCallback(
|
||||||
(mode: SidebarGroupMode) => {
|
(mode: SidebarGroupMode) => {
|
||||||
@@ -34,6 +50,13 @@ export function SidebarGroupingSelector({ serverId }: { serverId: string | null
|
|||||||
[serverId, setGroupMode],
|
[serverId, setGroupMode],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleWorkspaceTitleSourceSelect = useCallback(
|
||||||
|
(source: WorkspaceTitleSource) => {
|
||||||
|
void updateSettings({ workspaceTitleSource: source });
|
||||||
|
},
|
||||||
|
[updateSettings],
|
||||||
|
);
|
||||||
|
|
||||||
const triggerStyle = useCallback(
|
const triggerStyle = useCallback(
|
||||||
({ hovered = false }: PressableStateCallbackType & { hovered?: boolean }) => [
|
({ hovered = false }: PressableStateCallbackType & { hovered?: boolean }) => [
|
||||||
styles.trigger,
|
styles.trigger,
|
||||||
@@ -47,45 +70,61 @@ export function SidebarGroupingSelector({ serverId }: { serverId: string | null
|
|||||||
<DropdownMenuTrigger
|
<DropdownMenuTrigger
|
||||||
style={triggerStyle}
|
style={triggerStyle}
|
||||||
accessibilityRole={platformIsWeb ? undefined : "button"}
|
accessibilityRole={platformIsWeb ? undefined : "button"}
|
||||||
accessibilityLabel="Sidebar grouping"
|
accessibilityLabel="Display preferences"
|
||||||
testID="sidebar-grouping-selector"
|
testID="sidebar-display-preferences-menu"
|
||||||
>
|
>
|
||||||
<ThemedSettings2 size={14} uniProps={filterColorMapping} />
|
<ThemedSettings2 size={14} uniProps={filterColorMapping} />
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="start" width={180} testID="sidebar-grouping-menu">
|
<DropdownMenuContent align="end" width={180} testID="sidebar-display-preferences-content">
|
||||||
<View style={styles.menuHeader}>
|
<View style={styles.menuHeader}>
|
||||||
<Text style={styles.menuHeaderLabel}>Group by</Text>
|
<Text style={styles.menuHeaderLabel}>Group by</Text>
|
||||||
</View>
|
</View>
|
||||||
{GROUP_MODE_ITEMS.map((item) => (
|
{GROUP_MODE_ITEMS.map((item) => (
|
||||||
<GroupModeMenuItem
|
<DisplayPreferenceMenuItem
|
||||||
key={item.value}
|
key={item.value}
|
||||||
item={item}
|
item={item}
|
||||||
isSelected={groupMode === item.value}
|
isSelected={groupMode === item.value}
|
||||||
|
testIDPrefix="sidebar-grouping"
|
||||||
onSelect={handleSelect}
|
onSelect={handleSelect}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<View style={styles.menuHeader}>
|
||||||
|
<Text style={styles.menuHeaderLabel}>Workspace title</Text>
|
||||||
|
</View>
|
||||||
|
{WORKSPACE_TITLE_SOURCE_ITEMS.map((item) => (
|
||||||
|
<DisplayPreferenceMenuItem
|
||||||
|
key={item.value}
|
||||||
|
item={item}
|
||||||
|
isSelected={workspaceTitleSource === item.value}
|
||||||
|
testIDPrefix="sidebar-workspace-title-source"
|
||||||
|
onSelect={handleWorkspaceTitleSourceSelect}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function GroupModeMenuItem({
|
function DisplayPreferenceMenuItem<Value extends string>({
|
||||||
item,
|
item,
|
||||||
isSelected,
|
isSelected,
|
||||||
|
testIDPrefix,
|
||||||
onSelect,
|
onSelect,
|
||||||
}: {
|
}: {
|
||||||
item: { value: SidebarGroupMode; label: string };
|
item: DisplayPreferenceOption<Value>;
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
onSelect: (mode: SidebarGroupMode) => void;
|
testIDPrefix: string;
|
||||||
|
onSelect: (value: Value) => void;
|
||||||
}) {
|
}) {
|
||||||
const handleSelect = useCallback(() => onSelect(item.value), [item.value, onSelect]);
|
const handleSelect = useCallback(() => onSelect(item.value), [item.value, onSelect]);
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
testID={`sidebar-grouping-${item.value}`}
|
testID={`${testIDPrefix}-${item.value}`}
|
||||||
selected={isSelected}
|
selected={isSelected}
|
||||||
onSelect={handleSelect}
|
onSelect={handleSelect}
|
||||||
>
|
>
|
||||||
{item.label}
|
<Text style={styles.optionLabel}>{item.label}</Text>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -110,4 +149,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
fontWeight: theme.fontWeight.medium,
|
fontWeight: theme.fontWeight.medium,
|
||||||
color: theme.colors.foregroundMuted,
|
color: theme.colors.foregroundMuted,
|
||||||
},
|
},
|
||||||
|
optionLabel: {
|
||||||
|
fontSize: theme.fontSize.sm,
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
@@ -22,12 +22,14 @@ import { GitHubIcon } from "@/components/icons/github-icon";
|
|||||||
import { WorkspaceHoverCard } from "@/components/workspace-hover-card";
|
import { WorkspaceHoverCard } from "@/components/workspace-hover-card";
|
||||||
import { SyncedLoader } from "@/components/synced-loader";
|
import { SyncedLoader } from "@/components/synced-loader";
|
||||||
import type { SidebarWorkspaceEntry } from "@/hooks/use-sidebar-workspaces-list";
|
import type { SidebarWorkspaceEntry } from "@/hooks/use-sidebar-workspaces-list";
|
||||||
|
import { useAppSettings } from "@/hooks/use-settings";
|
||||||
import type { Theme } from "@/styles/theme";
|
import type { Theme } from "@/styles/theme";
|
||||||
import type { PrHint } from "@/git/use-pr-status-query";
|
import type { PrHint } from "@/git/use-pr-status-query";
|
||||||
import type { SidebarStateBucket } from "@/utils/sidebar-agent-state";
|
import type { SidebarStateBucket } from "@/utils/sidebar-agent-state";
|
||||||
import { isEmphasizedStatusDotBucket } from "@/utils/status-dot-color";
|
import { isEmphasizedStatusDotBucket } from "@/utils/status-dot-color";
|
||||||
import { shouldRenderSyncedStatusLoader } from "@/utils/status-loader";
|
import { shouldRenderSyncedStatusLoader } from "@/utils/status-loader";
|
||||||
import { openExternalUrl } from "@/utils/open-external-url";
|
import { openExternalUrl } from "@/utils/open-external-url";
|
||||||
|
import { resolveSidebarWorkspacePrimaryLabel } from "@/components/sidebar/sidebar-workspace-title";
|
||||||
|
|
||||||
const DEFAULT_STATUS_DOT_SIZE = 7;
|
const DEFAULT_STATUS_DOT_SIZE = 7;
|
||||||
const EMPHASIZED_STATUS_DOT_SIZE = 9;
|
const EMPHASIZED_STATUS_DOT_SIZE = 9;
|
||||||
@@ -110,6 +112,10 @@ export const SidebarWorkspaceRowContent = memo(function SidebarWorkspaceRowConte
|
|||||||
showShortcutBadge?: boolean;
|
showShortcutBadge?: boolean;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
}) {
|
}) {
|
||||||
|
const {
|
||||||
|
settings: { workspaceTitleSource },
|
||||||
|
} = useAppSettings();
|
||||||
|
const workspaceLabel = resolveSidebarWorkspacePrimaryLabel({ workspace, workspaceTitleSource });
|
||||||
const workspaceBranchTextStyle = useMemo(
|
const workspaceBranchTextStyle = useMemo(
|
||||||
() => [
|
() => [
|
||||||
styles.workspaceBranchText,
|
styles.workspaceBranchText,
|
||||||
@@ -132,7 +138,7 @@ export const SidebarWorkspaceRowContent = memo(function SidebarWorkspaceRowConte
|
|||||||
<View style={styles.workspaceTitleRow}>
|
<View style={styles.workspaceTitleRow}>
|
||||||
<View style={styles.workspaceTitleLeft}>
|
<View style={styles.workspaceTitleLeft}>
|
||||||
<Text style={workspaceBranchTextStyle} numberOfLines={1}>
|
<Text style={workspaceBranchTextStyle} numberOfLines={1}>
|
||||||
{workspace.name}
|
{workspaceLabel}
|
||||||
</Text>
|
</Text>
|
||||||
{scriptIconKind ? <WorkspaceScriptIcon kind={scriptIconKind} /> : null}
|
{scriptIconKind ? <WorkspaceScriptIcon kind={scriptIconKind} /> : null}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { resolveSidebarWorkspacePrimaryLabel } from "@/components/sidebar/sidebar-workspace-title";
|
||||||
|
|
||||||
|
describe("resolveSidebarWorkspacePrimaryLabel", () => {
|
||||||
|
it("uses the workspace name in title mode", () => {
|
||||||
|
const label = resolveSidebarWorkspacePrimaryLabel({
|
||||||
|
workspace: { name: "Investigate search", currentBranch: "fix/search" },
|
||||||
|
workspaceTitleSource: "title",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(label).toBe("Investigate search");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses the branch name in branch mode", () => {
|
||||||
|
const label = resolveSidebarWorkspacePrimaryLabel({
|
||||||
|
workspace: { name: "Investigate search", currentBranch: "fix/search" },
|
||||||
|
workspaceTitleSource: "branch",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(label).toBe("fix/search");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to the workspace name in branch mode without a branch", () => {
|
||||||
|
const label = resolveSidebarWorkspacePrimaryLabel({
|
||||||
|
workspace: { name: "Local folder", currentBranch: null },
|
||||||
|
workspaceTitleSource: "branch",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(label).toBe("Local folder");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import type { SidebarWorkspaceEntry } from "@/hooks/use-sidebar-workspaces-list";
|
||||||
|
import type { WorkspaceTitleSource } from "@/hooks/use-settings";
|
||||||
|
|
||||||
|
export function resolveSidebarWorkspacePrimaryLabel(input: {
|
||||||
|
workspace: Pick<SidebarWorkspaceEntry, "name" | "currentBranch">;
|
||||||
|
workspaceTitleSource: WorkspaceTitleSource;
|
||||||
|
}): string {
|
||||||
|
if (input.workspaceTitleSource === "branch") {
|
||||||
|
return input.workspace.currentBranch ?? input.workspace.name;
|
||||||
|
}
|
||||||
|
return input.workspace.name;
|
||||||
|
}
|
||||||
@@ -38,6 +38,7 @@ import {
|
|||||||
type ServiceUrlBehavior,
|
type ServiceUrlBehavior,
|
||||||
type Settings,
|
type Settings,
|
||||||
type SettingsDeps,
|
type SettingsDeps,
|
||||||
|
type WorkspaceTitleSource,
|
||||||
} from "./storage";
|
} from "./storage";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -67,6 +68,7 @@ export type {
|
|||||||
ServiceUrlBehavior,
|
ServiceUrlBehavior,
|
||||||
Settings,
|
Settings,
|
||||||
SettingsDeps,
|
SettingsDeps,
|
||||||
|
WorkspaceTitleSource,
|
||||||
};
|
};
|
||||||
|
|
||||||
const productionDeps: SettingsDeps = {
|
const productionDeps: SettingsDeps = {
|
||||||
@@ -172,6 +174,9 @@ export function useSettings(): UseSettingsReturn {
|
|||||||
if (updates.syntaxTheme !== undefined) {
|
if (updates.syntaxTheme !== undefined) {
|
||||||
appUpdates.syntaxTheme = updates.syntaxTheme;
|
appUpdates.syntaxTheme = updates.syntaxTheme;
|
||||||
}
|
}
|
||||||
|
if (updates.workspaceTitleSource !== undefined) {
|
||||||
|
appUpdates.workspaceTitleSource = updates.workspaceTitleSource;
|
||||||
|
}
|
||||||
const promises: Promise<void>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
if (Object.keys(appUpdates).length > 0) {
|
if (Object.keys(appUpdates).length > 0) {
|
||||||
promises.push(appSettings.updateSettings(appUpdates));
|
promises.push(appSettings.updateSettings(appUpdates));
|
||||||
|
|||||||
@@ -61,6 +61,14 @@ describe("loadAppSettingsFromStorage", () => {
|
|||||||
expect(result.language).toBe("system");
|
expect(result.language).toBe("system");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("defaults workspace title source to title when storage is empty", async () => {
|
||||||
|
const deps = makeDeps();
|
||||||
|
|
||||||
|
const result = await loadAppSettingsFromStorage(deps);
|
||||||
|
|
||||||
|
expect(result.workspaceTitleSource).toBe("title");
|
||||||
|
});
|
||||||
|
|
||||||
it("loads configured terminal scrollback lines from app settings", async () => {
|
it("loads configured terminal scrollback lines from app settings", async () => {
|
||||||
const deps = makeDeps({
|
const deps = makeDeps({
|
||||||
storage: createInMemoryKeyValueStorage({
|
storage: createInMemoryKeyValueStorage({
|
||||||
@@ -73,6 +81,30 @@ describe("loadAppSettingsFromStorage", () => {
|
|||||||
expect(result.terminalScrollbackLines).toBe(42_000);
|
expect(result.terminalScrollbackLines).toBe(42_000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("loads configured workspace title source from app settings", async () => {
|
||||||
|
const deps = makeDeps({
|
||||||
|
storage: createInMemoryKeyValueStorage({
|
||||||
|
[APP_SETTINGS_KEY]: JSON.stringify({ workspaceTitleSource: "branch" }),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await loadAppSettingsFromStorage(deps);
|
||||||
|
|
||||||
|
expect(result.workspaceTitleSource).toBe("branch");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("drops an unknown workspace title source back to title", async () => {
|
||||||
|
const deps = makeDeps({
|
||||||
|
storage: createInMemoryKeyValueStorage({
|
||||||
|
[APP_SETTINGS_KEY]: JSON.stringify({ workspaceTitleSource: "directory" }),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await loadAppSettingsFromStorage(deps);
|
||||||
|
|
||||||
|
expect(result.workspaceTitleSource).toBe("title");
|
||||||
|
});
|
||||||
|
|
||||||
it("normalizes terminal scrollback lines from storage", async () => {
|
it("normalizes terminal scrollback lines from storage", async () => {
|
||||||
const deps = makeDeps({
|
const deps = makeDeps({
|
||||||
storage: createInMemoryKeyValueStorage({
|
storage: createInMemoryKeyValueStorage({
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ const LEGACY_SETTINGS_KEY = "@paseo:settings";
|
|||||||
export type SendBehavior = "interrupt" | "queue";
|
export type SendBehavior = "interrupt" | "queue";
|
||||||
export type ReleaseChannel = "stable" | "beta";
|
export type ReleaseChannel = "stable" | "beta";
|
||||||
export type ServiceUrlBehavior = "ask" | "in-app" | "external";
|
export type ServiceUrlBehavior = "ask" | "in-app" | "external";
|
||||||
|
export type WorkspaceTitleSource = "title" | "branch";
|
||||||
|
|
||||||
const VALID_THEMES = new Set<string>([...Object.keys(THEME_TO_UNISTYLES), "auto"]);
|
const VALID_THEMES = new Set<string>([...Object.keys(THEME_TO_UNISTYLES), "auto"]);
|
||||||
const VALID_SERVICE_URL_BEHAVIORS = new Set<ServiceUrlBehavior>(["ask", "in-app", "external"]);
|
const VALID_SERVICE_URL_BEHAVIORS = new Set<ServiceUrlBehavior>(["ask", "in-app", "external"]);
|
||||||
|
const VALID_WORKSPACE_TITLE_SOURCES = new Set<WorkspaceTitleSource>(["title", "branch"]);
|
||||||
export const DEFAULT_TERMINAL_SCROLLBACK_LINES = 10_000;
|
export const DEFAULT_TERMINAL_SCROLLBACK_LINES = 10_000;
|
||||||
export const MIN_TERMINAL_SCROLLBACK_LINES = 0;
|
export const MIN_TERMINAL_SCROLLBACK_LINES = 0;
|
||||||
export const MAX_TERMINAL_SCROLLBACK_LINES = 1_000_000;
|
export const MAX_TERMINAL_SCROLLBACK_LINES = 1_000_000;
|
||||||
@@ -36,6 +38,7 @@ export interface AppSettings {
|
|||||||
uiFontSize: number; // clamped px, default 16
|
uiFontSize: number; // clamped px, default 16
|
||||||
codeFontSize: number; // clamped px, default 12
|
codeFontSize: number; // clamped px, default 12
|
||||||
syntaxTheme: SyntaxThemeId; // default "one"
|
syntaxTheme: SyntaxThemeId; // default "one"
|
||||||
|
workspaceTitleSource: WorkspaceTitleSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Settings extends AppSettings {
|
export interface Settings extends AppSettings {
|
||||||
@@ -54,6 +57,7 @@ export const DEFAULT_CLIENT_SETTINGS: AppSettings = {
|
|||||||
uiFontSize: DEFAULT_UI_FONT_SIZE,
|
uiFontSize: DEFAULT_UI_FONT_SIZE,
|
||||||
codeFontSize: DEFAULT_CODE_FONT_SIZE,
|
codeFontSize: DEFAULT_CODE_FONT_SIZE,
|
||||||
syntaxTheme: "one",
|
syntaxTheme: "one",
|
||||||
|
workspaceTitleSource: "title",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_APP_SETTINGS: Settings = {
|
export const DEFAULT_APP_SETTINGS: Settings = {
|
||||||
@@ -194,6 +198,12 @@ function pickAppSettings(stored: Partial<AppSettings>): Partial<AppSettings> {
|
|||||||
if (typeof stored.syntaxTheme === "string" && isSyntaxThemeId(stored.syntaxTheme)) {
|
if (typeof stored.syntaxTheme === "string" && isSyntaxThemeId(stored.syntaxTheme)) {
|
||||||
result.syntaxTheme = stored.syntaxTheme;
|
result.syntaxTheme = stored.syntaxTheme;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
typeof stored.workspaceTitleSource === "string" &&
|
||||||
|
VALID_WORKSPACE_TITLE_SOURCES.has(stored.workspaceTitleSource)
|
||||||
|
) {
|
||||||
|
result.workspaceTitleSource = stored.workspaceTitleSource;
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user