Make terminal scrollback configurable (#1021)
* Make terminal scrollback configurable * Fix terminal scrollback lint violation * Fix terminal runtime test environment split * Fix terminal scrollback updates --------- Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { QueryClient } from "@tanstack/react-query";
|
||||
|
||||
const asyncStorageMock = vi.hoisted(() => ({
|
||||
getItem: vi.fn<(_: string) => Promise<string | null>>(),
|
||||
@@ -87,6 +88,7 @@ describe("use-settings", () => {
|
||||
manageBuiltInDaemon: true,
|
||||
sendBehavior: "interrupt",
|
||||
serviceUrlBehavior: "ask",
|
||||
terminalScrollbackLines: 10_000,
|
||||
releaseChannel: "stable",
|
||||
});
|
||||
});
|
||||
@@ -130,6 +132,7 @@ describe("use-settings", () => {
|
||||
theme: "dark",
|
||||
sendBehavior: "interrupt",
|
||||
serviceUrlBehavior: "ask",
|
||||
terminalScrollbackLines: 10_000,
|
||||
});
|
||||
expect(asyncStorageMock.setItem).toHaveBeenCalledWith(
|
||||
mod.APP_SETTINGS_KEY,
|
||||
@@ -169,6 +172,7 @@ describe("use-settings", () => {
|
||||
theme: "light",
|
||||
sendBehavior: "interrupt",
|
||||
serviceUrlBehavior: "ask",
|
||||
terminalScrollbackLines: 10_000,
|
||||
manageBuiltInDaemon: false,
|
||||
releaseChannel: "beta",
|
||||
});
|
||||
@@ -188,8 +192,58 @@ describe("use-settings", () => {
|
||||
theme: "light",
|
||||
sendBehavior: "interrupt",
|
||||
serviceUrlBehavior: "ask",
|
||||
terminalScrollbackLines: 10_000,
|
||||
manageBuiltInDaemon: true,
|
||||
releaseChannel: "stable",
|
||||
});
|
||||
});
|
||||
|
||||
it("loads configured terminal scrollback lines from app settings", async () => {
|
||||
asyncStorageMock.getItem.mockResolvedValue(
|
||||
JSON.stringify({
|
||||
terminalScrollbackLines: 42_000,
|
||||
}),
|
||||
);
|
||||
|
||||
const mod = await import("./use-settings");
|
||||
const result = await mod.loadAppSettingsFromStorage();
|
||||
|
||||
expect(result.terminalScrollbackLines).toBe(42_000);
|
||||
});
|
||||
|
||||
it("saves terminal scrollback through app settings persistence", async () => {
|
||||
asyncStorageMock.setItem.mockResolvedValue();
|
||||
|
||||
const mod = await import("./use-settings");
|
||||
asyncStorageMock.getItem.mockResolvedValue(JSON.stringify(mod.DEFAULT_CLIENT_SETTINGS));
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
await mod.saveAppSettings({
|
||||
queryClient,
|
||||
updates: { terminalScrollbackLines: 42_000 },
|
||||
});
|
||||
|
||||
expect(asyncStorageMock.setItem).toHaveBeenLastCalledWith(
|
||||
mod.APP_SETTINGS_KEY,
|
||||
JSON.stringify({
|
||||
...mod.DEFAULT_CLIENT_SETTINGS,
|
||||
terminalScrollbackLines: 42_000,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("normalizes terminal scrollback lines from storage", async () => {
|
||||
asyncStorageMock.getItem.mockResolvedValue(
|
||||
JSON.stringify({
|
||||
terminalScrollbackLines: 1_000_000.9,
|
||||
}),
|
||||
);
|
||||
|
||||
const mod = await import("./use-settings");
|
||||
const result = await mod.loadAppSettingsFromStorage();
|
||||
|
||||
expect(result.terminalScrollbackLines).toBe(1_000_000);
|
||||
expect(mod.parseTerminalScrollbackLines("-10")).toBe(0);
|
||||
expect(mod.parseTerminalScrollbackLines("abc")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback } from "react";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useQuery, useQueryClient, type QueryClient } from "@tanstack/react-query";
|
||||
import { queryClient as appQueryClient } from "@/query/query-client";
|
||||
import {
|
||||
DEFAULT_DESKTOP_SETTINGS,
|
||||
@@ -21,11 +21,15 @@ export type ServiceUrlBehavior = "ask" | "in-app" | "external";
|
||||
|
||||
const VALID_THEMES = new Set<string>([...Object.keys(THEME_TO_UNISTYLES), "auto"]);
|
||||
const VALID_SERVICE_URL_BEHAVIORS = new Set<ServiceUrlBehavior>(["ask", "in-app", "external"]);
|
||||
export const DEFAULT_TERMINAL_SCROLLBACK_LINES = 10_000;
|
||||
export const MIN_TERMINAL_SCROLLBACK_LINES = 0;
|
||||
export const MAX_TERMINAL_SCROLLBACK_LINES = 1_000_000;
|
||||
|
||||
export interface AppSettings {
|
||||
theme: ThemeName | "auto";
|
||||
sendBehavior: SendBehavior;
|
||||
serviceUrlBehavior: ServiceUrlBehavior;
|
||||
terminalScrollbackLines: number;
|
||||
}
|
||||
|
||||
export interface Settings extends AppSettings {
|
||||
@@ -37,6 +41,7 @@ export const DEFAULT_CLIENT_SETTINGS: AppSettings = {
|
||||
theme: "auto",
|
||||
sendBehavior: "interrupt",
|
||||
serviceUrlBehavior: "ask",
|
||||
terminalScrollbackLines: DEFAULT_TERMINAL_SCROLLBACK_LINES,
|
||||
};
|
||||
|
||||
export const DEFAULT_APP_SETTINGS: Settings = {
|
||||
@@ -73,11 +78,7 @@ export function useAppSettings(): UseAppSettingsReturn {
|
||||
const updateSettings = useCallback(
|
||||
async (updates: Partial<AppSettings>) => {
|
||||
try {
|
||||
const prev =
|
||||
queryClient.getQueryData<AppSettings>(APP_SETTINGS_QUERY_KEY) ?? DEFAULT_CLIENT_SETTINGS;
|
||||
const next = { ...prev, ...updates };
|
||||
queryClient.setQueryData<AppSettings>(APP_SETTINGS_QUERY_KEY, next);
|
||||
await AsyncStorage.setItem(APP_SETTINGS_KEY, JSON.stringify(next));
|
||||
await saveAppSettings({ queryClient, updates });
|
||||
} catch (err) {
|
||||
console.error("[AppSettings] Failed to save settings:", err);
|
||||
throw err;
|
||||
@@ -122,7 +123,9 @@ export function useSettings(): UseSettingsReturn {
|
||||
if (updates.serviceUrlBehavior !== undefined) {
|
||||
appUpdates.serviceUrlBehavior = updates.serviceUrlBehavior;
|
||||
}
|
||||
|
||||
if (updates.terminalScrollbackLines !== undefined) {
|
||||
appUpdates.terminalScrollbackLines = updates.terminalScrollbackLines;
|
||||
}
|
||||
const promises: Promise<void>[] = [];
|
||||
if (Object.keys(appUpdates).length > 0) {
|
||||
promises.push(appSettings.updateSettings(appUpdates));
|
||||
@@ -171,11 +174,18 @@ export function useSettings(): UseSettingsReturn {
|
||||
}
|
||||
|
||||
export async function persistAppSettings(updates: Partial<AppSettings>): Promise<void> {
|
||||
await saveAppSettings({ queryClient: appQueryClient, updates });
|
||||
}
|
||||
|
||||
export async function saveAppSettings(input: {
|
||||
queryClient: QueryClient;
|
||||
updates: Partial<AppSettings>;
|
||||
}): Promise<void> {
|
||||
const current =
|
||||
appQueryClient.getQueryData<AppSettings>(APP_SETTINGS_QUERY_KEY) ??
|
||||
input.queryClient.getQueryData<AppSettings>(APP_SETTINGS_QUERY_KEY) ??
|
||||
(await loadAppSettingsFromStorage());
|
||||
const next = { ...current, ...updates };
|
||||
appQueryClient.setQueryData<AppSettings>(APP_SETTINGS_QUERY_KEY, next);
|
||||
const next = { ...current, ...input.updates };
|
||||
input.queryClient.setQueryData<AppSettings>(APP_SETTINGS_QUERY_KEY, next);
|
||||
await AsyncStorage.setItem(APP_SETTINGS_KEY, JSON.stringify(next));
|
||||
}
|
||||
|
||||
@@ -246,6 +256,10 @@ function pickAppSettings(stored: Partial<AppSettings>): Partial<AppSettings> {
|
||||
) {
|
||||
result.serviceUrlBehavior = stored.serviceUrlBehavior;
|
||||
}
|
||||
const terminalScrollbackLines = parseTerminalScrollbackLines(stored.terminalScrollbackLines);
|
||||
if (terminalScrollbackLines !== null) {
|
||||
result.terminalScrollbackLines = terminalScrollbackLines;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -257,6 +271,22 @@ function pickAppSettingsFromLegacy(legacy: Record<string, unknown>): Partial<App
|
||||
return result;
|
||||
}
|
||||
|
||||
export function parseTerminalScrollbackLines(value: unknown): number | null {
|
||||
let numericValue = NaN;
|
||||
if (typeof value === "number") {
|
||||
numericValue = value;
|
||||
} else if (typeof value === "string" && value.trim().length > 0) {
|
||||
numericValue = Number(value);
|
||||
}
|
||||
if (!Number.isFinite(numericValue)) {
|
||||
return null;
|
||||
}
|
||||
return Math.min(
|
||||
MAX_TERMINAL_SCROLLBACK_LINES,
|
||||
Math.max(MIN_TERMINAL_SCROLLBACK_LINES, Math.floor(numericValue)),
|
||||
);
|
||||
}
|
||||
|
||||
async function loadLegacyDesktopSettingsFromStorage(): Promise<{
|
||||
manageBuiltInDaemon?: boolean;
|
||||
releaseChannel?: ReleaseChannel;
|
||||
|
||||
Reference in New Issue
Block a user