import { Effect } from "effect";import * as Schema from "effect/Schema";import * as SchemaTransformation from "effect/SchemaTransformation";import { TrimmedNonEmptyString, TrimmedString } from "./baseSchemas.ts";import { DEFAULT_GIT_TEXT_GENERATION_MODEL_BY_PROVIDER, ProviderOptionSelections,} from "./model.ts";import { ModelSelection, ProviderKind } from "./orchestration.ts"; // ── Client Settings (local-only) ─────────────────────────────── export const TimestampFormat = Schema.Literals(["locale", "12-hour", "24-hour"]);export type TimestampFormat = typeof TimestampFormat.Type;export const DEFAULT_TIMESTAMP_FORMAT: TimestampFormat = "locale"; export const SidebarProjectSortOrder = Schema.Literals(["updated_at", "created_at", "manual"]);export type SidebarProjectSortOrder = typeof SidebarProjectSortOrder.Type;export const DEFAULT_SIDEBAR_PROJECT_SORT_ORDER: SidebarProjectSortOrder = "updated_at"; export const SidebarThreadSortOrder = Schema.Literals(["updated_at", "created_at"]);export type SidebarThreadSortOrder = typeof SidebarThreadSortOrder.Type;export const DEFAULT_SIDEBAR_THREAD_SORT_ORDER: SidebarThreadSortOrder = "updated_at"; export const SidebarProjectGroupingMode = Schema.Literals([ "repository", "repository_path", "separate",]);export type SidebarProjectGroupingMode = typeof SidebarProjectGroupingMode.Type;export const DEFAULT_SIDEBAR_PROJECT_GROUPING_MODE: SidebarProjectGroupingMode = "repository"; export const ClientSettingsSchema = Schema.Struct({ confirmThreadArchive: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), confirmThreadDelete: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))), diffWordWrap: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), favorites: Schema.Array( Schema.Struct({ provider: ProviderKind, model: TrimmedNonEmptyString, }), ).pipe(Schema.withDecodingDefault(Effect.succeed([]))), sidebarProjectGroupingMode: SidebarProjectGroupingMode.pipe( Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_PROJECT_GROUPING_MODE)), ), sidebarProjectGroupingOverrides: Schema.Record( TrimmedNonEmptyString, SidebarProjectGroupingMode, ).pipe(Schema.withDecodingDefault(Effect.succeed({}))), sidebarProjectSortOrder: SidebarProjectSortOrder.pipe( Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_PROJECT_SORT_ORDER)), ), sidebarThreadSortOrder: SidebarThreadSortOrder.pipe( Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_THREAD_SORT_ORDER)), ), timestampFormat: TimestampFormat.pipe( Schema.withDecodingDefault(Effect.succeed(DEFAULT_TIMESTAMP_FORMAT)), ),});export type ClientSettings = typeof ClientSettingsSchema.Type; export const DEFAULT_CLIENT_SETTINGS: ClientSettings = Schema.decodeSync(ClientSettingsSchema)({}); // ── Server Settings (server-authoritative) ──────────────────── export const ThreadEnvMode = Schema.Literals(["local", "worktree"]);export type ThreadEnvMode = typeof ThreadEnvMode.Type; const makeBinaryPathSetting = (fallback: string) => TrimmedString.pipe( Schema.decodeTo( Schema.String, SchemaTransformation.transformOrFail({ decode: (value) => Effect.succeed(value || fallback), encode: (value) => Effect.succeed(value), }), ), Schema.withDecodingDefault(Effect.succeed(fallback)), ); export const CodexSettings = Schema.Struct({ enabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))), binaryPath: makeBinaryPathSetting("codex"), homePath: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed(""))), customModels: Schema.Array(Schema.String).pipe(Schema.withDecodingDefault(Effect.succeed([]))),});export type CodexSettings = typeof CodexSettings.Type; export const ClaudeSettings = Schema.Struct({ enabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))), binaryPath: makeBinaryPathSetting("claude"), customModels: Schema.Array(Schema.String).pipe(Schema.withDecodingDefault(Effect.succeed([]))), launchArgs: Schema.String.pipe(Schema.withDecodingDefault(Effect.succeed(""))),});export type ClaudeSettings = typeof ClaudeSettings.Type; export const CursorSettings = Schema.Struct({ enabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), binaryPath: makeBinaryPathSetting("agent"), apiEndpoint: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed(""))), customModels: Schema.Array(Schema.String).pipe(Schema.withDecodingDefault(Effect.succeed([]))),});export type CursorSettings = typeof CursorSettings.Type;export const OpenCodeSettings = Schema.Struct({ enabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))), binaryPath: makeBinaryPathSetting("opencode"), serverUrl: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed(""))), serverPassword: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed(""))), customModels: Schema.Array(Schema.String).pipe(Schema.withDecodingDefault(Effect.succeed([]))),});export type OpenCodeSettings = typeof OpenCodeSettings.Type; export const ObservabilitySettings = Schema.Struct({ otlpTracesUrl: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed(""))), otlpMetricsUrl: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed(""))),});export type ObservabilitySettings = typeof ObservabilitySettings.Type; export const ServerSettings = Schema.Struct({ enableAssistantStreaming: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), defaultThreadEnvMode: ThreadEnvMode.pipe( Schema.withDecodingDefault(Effect.succeed("local" as const satisfies ThreadEnvMode)), ), addProjectBaseDirectory: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed(""))), textGenerationModelSelection: ModelSelection.pipe( Schema.withDecodingDefault( Effect.succeed({ provider: "codex" as const, model: DEFAULT_GIT_TEXT_GENERATION_MODEL_BY_PROVIDER.codex, }), ), ), // Provider specific settings providers: Schema.Struct({ codex: CodexSettings.pipe(Schema.withDecodingDefault(Effect.succeed({}))), claudeAgent: ClaudeSettings.pipe(Schema.withDecodingDefault(Effect.succeed({}))), cursor: CursorSettings.pipe(Schema.withDecodingDefault(Effect.succeed({}))), opencode: OpenCodeSettings.pipe(Schema.withDecodingDefault(Effect.succeed({}))), }).pipe(Schema.withDecodingDefault(Effect.succeed({}))), observability: ObservabilitySettings.pipe(Schema.withDecodingDefault(Effect.succeed({}))),});export type ServerSettings = typeof ServerSettings.Type; export const DEFAULT_SERVER_SETTINGS: ServerSettings = Schema.decodeSync(ServerSettings)({}); export class ServerSettingsError extends Schema.TaggedErrorClass<ServerSettingsError>()( "ServerSettingsError", { settingsPath: Schema.String, detail: Schema.String, cause: Schema.optional(Schema.Defect), },) { override get message(): string { return `Server settings error at ${this.settingsPath}: ${this.detail}`; }} // ── Unified type ───────────────────────────────────────────────────── export type UnifiedSettings = ServerSettings & ClientSettings;export const DEFAULT_UNIFIED_SETTINGS: UnifiedSettings = { ...DEFAULT_SERVER_SETTINGS, ...DEFAULT_CLIENT_SETTINGS,}; // ── Server Settings Patch (replace with a Schema.deepPartial if available) ────────────────────────────────────────── const ModelSelectionPatch = Schema.Union([ Schema.Struct({ provider: Schema.optionalKey(Schema.Literal("codex")), model: Schema.optionalKey(TrimmedNonEmptyString), options: Schema.optionalKey(ProviderOptionSelections), }), Schema.Struct({ provider: Schema.optionalKey(Schema.Literal("claudeAgent")), model: Schema.optionalKey(TrimmedNonEmptyString), options: Schema.optionalKey(ProviderOptionSelections), }), Schema.Struct({ provider: Schema.optionalKey(Schema.Literal("cursor")), model: Schema.optionalKey(TrimmedNonEmptyString), options: Schema.optionalKey(ProviderOptionSelections), }), Schema.Struct({ provider: Schema.optionalKey(Schema.Literal("opencode")), model: Schema.optionalKey(TrimmedNonEmptyString), options: Schema.optionalKey(ProviderOptionSelections), }),]); const CodexSettingsPatch = Schema.Struct({ enabled: Schema.optionalKey(Schema.Boolean), binaryPath: Schema.optionalKey(Schema.String), homePath: Schema.optionalKey(Schema.String), customModels: Schema.optionalKey(Schema.Array(Schema.String)),}); const ClaudeSettingsPatch = Schema.Struct({ enabled: Schema.optionalKey(Schema.Boolean), binaryPath: Schema.optionalKey(Schema.String), customModels: Schema.optionalKey(Schema.Array(Schema.String)), launchArgs: Schema.optionalKey(Schema.String),}); const CursorSettingsPatch = Schema.Struct({ enabled: Schema.optionalKey(Schema.Boolean), binaryPath: Schema.optionalKey(Schema.String), apiEndpoint: Schema.optionalKey(Schema.String), customModels: Schema.optionalKey(Schema.Array(Schema.String)),}); const OpenCodeSettingsPatch = Schema.Struct({ enabled: Schema.optionalKey(Schema.Boolean), binaryPath: Schema.optionalKey(Schema.String), serverUrl: Schema.optionalKey(Schema.String), serverPassword: Schema.optionalKey(Schema.String), customModels: Schema.optionalKey(Schema.Array(Schema.String)),}); export const ServerSettingsPatch = Schema.Struct({ // Server settings enableAssistantStreaming: Schema.optionalKey(Schema.Boolean), defaultThreadEnvMode: Schema.optionalKey(ThreadEnvMode), addProjectBaseDirectory: Schema.optionalKey(Schema.String), textGenerationModelSelection: Schema.optionalKey(ModelSelectionPatch), observability: Schema.optionalKey( Schema.Struct({ otlpTracesUrl: Schema.optionalKey(Schema.String), otlpMetricsUrl: Schema.optionalKey(Schema.String), }), ), providers: Schema.optionalKey( Schema.Struct({ codex: Schema.optionalKey(CodexSettingsPatch), claudeAgent: Schema.optionalKey(ClaudeSettingsPatch), cursor: Schema.optionalKey(CursorSettingsPatch), opencode: Schema.optionalKey(OpenCodeSettingsPatch), }), ),});export type ServerSettingsPatch = typeof ServerSettingsPatch.Type; export const ClientSettingsPatch = Schema.Struct({ confirmThreadArchive: Schema.optionalKey(Schema.Boolean), confirmThreadDelete: Schema.optionalKey(Schema.Boolean), diffWordWrap: Schema.optionalKey(Schema.Boolean), favorites: Schema.optionalKey( Schema.Array( Schema.Struct({ provider: ProviderKind, model: TrimmedNonEmptyString, }), ), ), sidebarProjectGroupingMode: Schema.optionalKey(SidebarProjectGroupingMode), sidebarProjectGroupingOverrides: Schema.optionalKey( Schema.Record(TrimmedNonEmptyString, SidebarProjectGroupingMode), ), sidebarProjectSortOrder: Schema.optionalKey(SidebarProjectSortOrder), sidebarThreadSortOrder: Schema.optionalKey(SidebarThreadSortOrder), timestampFormat: Schema.optionalKey(TimestampFormat),});export type ClientSettingsPatch = typeof ClientSettingsPatch.Type;
import { Effect } from "effect";
import * as Schema from "effect/Schema";
import * as SchemaTransformation from "effect/SchemaTransformation";
import { TrimmedNonEmptyString, TrimmedString } from "./baseSchemas.ts";
import {
DEFAULT_GIT_TEXT_GENERATION_MODEL_BY_PROVIDER,
ProviderOptionSelections,
} from "./model.ts";
import { ModelSelection, ProviderKind } from "./orchestration.ts";
// ── Client Settings (local-only) ───────────────────────────────
export const TimestampFormat = Schema.Literals(["locale", "12-hour", "24-hour"]);
export type TimestampFormat = typeof TimestampFormat.Type;
export const DEFAULT_TIMESTAMP_FORMAT: TimestampFormat = "locale";
export const SidebarProjectSortOrder = Schema.Literals(["updated_at", "created_at", "manual"]);
export type SidebarProjectSortOrder = typeof SidebarProjectSortOrder.Type;
export const DEFAULT_SIDEBAR_PROJECT_SORT_ORDER: SidebarProjectSortOrder = "updated_at";
export const SidebarThreadSortOrder = Schema.Literals(["updated_at", "created_at"]);
export type SidebarThreadSortOrder = typeof SidebarThreadSortOrder.Type;
export const DEFAULT_SIDEBAR_THREAD_SORT_ORDER: SidebarThreadSortOrder = "updated_at";
export const SidebarProjectGroupingMode = Schema.Literals([
"repository",
"repository_path",
"separate",
]);
export type SidebarProjectGroupingMode = typeof SidebarProjectGroupingMode.Type;
export const DEFAULT_SIDEBAR_PROJECT_GROUPING_MODE: SidebarProjectGroupingMode = "repository";
export const ClientSettingsSchema = Schema.Struct({
confirmThreadArchive: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
confirmThreadDelete: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
diffWordWrap: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
favorites: Schema.Array(
Schema.Struct({
provider: ProviderKind,
model: TrimmedNonEmptyString,
}),
).pipe(Schema.withDecodingDefault(Effect.succeed([]))),
sidebarProjectGroupingMode: SidebarProjectGroupingMode.pipe(
Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_PROJECT_GROUPING_MODE)),
),
sidebarProjectGroupingOverrides: Schema.Record(
TrimmedNonEmptyString,
SidebarProjectGroupingMode,
).pipe(Schema.withDecodingDefault(Effect.succeed({}))),
sidebarProjectSortOrder: SidebarProjectSortOrder.pipe(
Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_PROJECT_SORT_ORDER)),
),
sidebarThreadSortOrder: SidebarThreadSortOrder.pipe(
Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_THREAD_SORT_ORDER)),
),
timestampFormat: TimestampFormat.pipe(
Schema.withDecodingDefault(Effect.succeed(DEFAULT_TIMESTAMP_FORMAT)),
),
});
export type ClientSettings = typeof ClientSettingsSchema.Type;
export const DEFAULT_CLIENT_SETTINGS: ClientSettings = Schema.decodeSync(ClientSettingsSchema)({});
// ── Server Settings (server-authoritative) ────────────────────
export const ThreadEnvMode = Schema.Literals(["local", "worktree"]);
export type ThreadEnvMode = typeof ThreadEnvMode.Type;
const makeBinaryPathSetting = (fallback: string) =>
TrimmedString.pipe(
Schema.decodeTo(
Schema.String,
SchemaTransformation.transformOrFail({
decode: (value) => Effect.succeed(value || fallback),
encode: (value) => Effect.succeed(value),
}),
),
Schema.withDecodingDefault(Effect.succeed(fallback)),
);
export const CodexSettings = Schema.Struct({
enabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
binaryPath: makeBinaryPathSetting("codex"),
homePath: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed(""))),
customModels: Schema.Array(Schema.String).pipe(Schema.withDecodingDefault(Effect.succeed([]))),
});
export type CodexSettings = typeof CodexSettings.Type;
export const ClaudeSettings = Schema.Struct({
enabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
binaryPath: makeBinaryPathSetting("claude"),
customModels: Schema.Array(Schema.String).pipe(Schema.withDecodingDefault(Effect.succeed([]))),
launchArgs: Schema.String.pipe(Schema.withDecodingDefault(Effect.succeed(""))),
});
export type ClaudeSettings = typeof ClaudeSettings.Type;
export const CursorSettings = Schema.Struct({
enabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
binaryPath: makeBinaryPathSetting("agent"),
apiEndpoint: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed(""))),
customModels: Schema.Array(Schema.String).pipe(Schema.withDecodingDefault(Effect.succeed([]))),
});
export type CursorSettings = typeof CursorSettings.Type;
export const OpenCodeSettings = Schema.Struct({
enabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
binaryPath: makeBinaryPathSetting("opencode"),
serverUrl: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed(""))),
serverPassword: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed(""))),
customModels: Schema.Array(Schema.String).pipe(Schema.withDecodingDefault(Effect.succeed([]))),
});
export type OpenCodeSettings = typeof OpenCodeSettings.Type;
export const ObservabilitySettings = Schema.Struct({
otlpTracesUrl: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed(""))),
otlpMetricsUrl: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed(""))),
});
export type ObservabilitySettings = typeof ObservabilitySettings.Type;
export const ServerSettings = Schema.Struct({
enableAssistantStreaming: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
defaultThreadEnvMode: ThreadEnvMode.pipe(
Schema.withDecodingDefault(Effect.succeed("local" as const satisfies ThreadEnvMode)),
),
addProjectBaseDirectory: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed(""))),
textGenerationModelSelection: ModelSelection.pipe(
Schema.withDecodingDefault(
Effect.succeed({
provider: "codex" as const,
model: DEFAULT_GIT_TEXT_GENERATION_MODEL_BY_PROVIDER.codex,
}),
),
),
// Provider specific settings
providers: Schema.Struct({
codex: CodexSettings.pipe(Schema.withDecodingDefault(Effect.succeed({}))),
claudeAgent: ClaudeSettings.pipe(Schema.withDecodingDefault(Effect.succeed({}))),
cursor: CursorSettings.pipe(Schema.withDecodingDefault(Effect.succeed({}))),
opencode: OpenCodeSettings.pipe(Schema.withDecodingDefault(Effect.succeed({}))),
}).pipe(Schema.withDecodingDefault(Effect.succeed({}))),
observability: ObservabilitySettings.pipe(Schema.withDecodingDefault(Effect.succeed({}))),
});
export type ServerSettings = typeof ServerSettings.Type;
export const DEFAULT_SERVER_SETTINGS: ServerSettings = Schema.decodeSync(ServerSettings)({});
export class ServerSettingsError extends Schema.TaggedErrorClass<ServerSettingsError>()(
"ServerSettingsError",
{
settingsPath: Schema.String,
detail: Schema.String,
cause: Schema.optional(Schema.Defect),
},
) {
override get message(): string {
return `Server settings error at ${this.settingsPath}: ${this.detail}`;
}
}
// ── Unified type ─────────────────────────────────────────────────────
export type UnifiedSettings = ServerSettings & ClientSettings;
export const DEFAULT_UNIFIED_SETTINGS: UnifiedSettings = {
...DEFAULT_SERVER_SETTINGS,
...DEFAULT_CLIENT_SETTINGS,
};
// ── Server Settings Patch (replace with a Schema.deepPartial if available) ──────────────────────────────────────────
const ModelSelectionPatch = Schema.Union([
Schema.Struct({
provider: Schema.optionalKey(Schema.Literal("codex")),
model: Schema.optionalKey(TrimmedNonEmptyString),
options: Schema.optionalKey(ProviderOptionSelections),
}),
Schema.Struct({
provider: Schema.optionalKey(Schema.Literal("claudeAgent")),
model: Schema.optionalKey(TrimmedNonEmptyString),
options: Schema.optionalKey(ProviderOptionSelections),
}),
Schema.Struct({
provider: Schema.optionalKey(Schema.Literal("cursor")),
model: Schema.optionalKey(TrimmedNonEmptyString),
options: Schema.optionalKey(ProviderOptionSelections),
}),
Schema.Struct({
provider: Schema.optionalKey(Schema.Literal("opencode")),
model: Schema.optionalKey(TrimmedNonEmptyString),
options: Schema.optionalKey(ProviderOptionSelections),
}),
]);
const CodexSettingsPatch = Schema.Struct({
enabled: Schema.optionalKey(Schema.Boolean),
binaryPath: Schema.optionalKey(Schema.String),
homePath: Schema.optionalKey(Schema.String),
customModels: Schema.optionalKey(Schema.Array(Schema.String)),
});
const ClaudeSettingsPatch = Schema.Struct({
enabled: Schema.optionalKey(Schema.Boolean),
binaryPath: Schema.optionalKey(Schema.String),
customModels: Schema.optionalKey(Schema.Array(Schema.String)),
launchArgs: Schema.optionalKey(Schema.String),
});
const CursorSettingsPatch = Schema.Struct({
enabled: Schema.optionalKey(Schema.Boolean),
binaryPath: Schema.optionalKey(Schema.String),
apiEndpoint: Schema.optionalKey(Schema.String),
customModels: Schema.optionalKey(Schema.Array(Schema.String)),
});
const OpenCodeSettingsPatch = Schema.Struct({
enabled: Schema.optionalKey(Schema.Boolean),
binaryPath: Schema.optionalKey(Schema.String),
serverUrl: Schema.optionalKey(Schema.String),
serverPassword: Schema.optionalKey(Schema.String),
customModels: Schema.optionalKey(Schema.Array(Schema.String)),
});
export const ServerSettingsPatch = Schema.Struct({
// Server settings
enableAssistantStreaming: Schema.optionalKey(Schema.Boolean),
defaultThreadEnvMode: Schema.optionalKey(ThreadEnvMode),
addProjectBaseDirectory: Schema.optionalKey(Schema.String),
textGenerationModelSelection: Schema.optionalKey(ModelSelectionPatch),
observability: Schema.optionalKey(
Schema.Struct({
otlpTracesUrl: Schema.optionalKey(Schema.String),
otlpMetricsUrl: Schema.optionalKey(Schema.String),
}),
),
providers: Schema.optionalKey(
Schema.Struct({
codex: Schema.optionalKey(CodexSettingsPatch),
claudeAgent: Schema.optionalKey(ClaudeSettingsPatch),
cursor: Schema.optionalKey(CursorSettingsPatch),
opencode: Schema.optionalKey(OpenCodeSettingsPatch),
}),
),
});
export type ServerSettingsPatch = typeof ServerSettingsPatch.Type;
export const ClientSettingsPatch = Schema.Struct({
confirmThreadArchive: Schema.optionalKey(Schema.Boolean),
confirmThreadDelete: Schema.optionalKey(Schema.Boolean),
diffWordWrap: Schema.optionalKey(Schema.Boolean),
favorites: Schema.optionalKey(
Schema.Array(
Schema.Struct({
provider: ProviderKind,
model: TrimmedNonEmptyString,
}),
),
),
sidebarProjectGroupingMode: Schema.optionalKey(SidebarProjectGroupingMode),
sidebarProjectGroupingOverrides: Schema.optionalKey(
Schema.Record(TrimmedNonEmptyString, SidebarProjectGroupingMode),
),
sidebarProjectSortOrder: Schema.optionalKey(SidebarProjectSortOrder),
sidebarThreadSortOrder: Schema.optionalKey(SidebarThreadSortOrder),
timestampFormat: Schema.optionalKey(TimestampFormat),
});
export type ClientSettingsPatch = typeof ClientSettingsPatch.Type;