import { describe, expect, it } from "vite-plus/test";import * as Schema from "effect/Schema"; import { ProviderDriverKind, ProviderInstanceId } from "./providerInstance.ts";import { ClientSettingsSchema, ClientSettingsPatch, ClaudeSettings, DEFAULT_SERVER_SETTINGS, resolveProviderInstanceEnabled, ServerSettings, ServerSettingsPatch,} from "./settings.ts"; const decodeClientSettings = Schema.decodeUnknownSync(ClientSettingsSchema);const decodeClientSettingsPatch = Schema.decodeUnknownSync(ClientSettingsPatch);const encodeClientSettings = Schema.encodeSync(ClientSettingsSchema);const decodeServerSettings = Schema.decodeUnknownSync(ServerSettings);const decodeServerSettingsPatch = Schema.decodeUnknownSync(ServerSettingsPatch);const encodeServerSettings = Schema.encodeSync(ServerSettings);const decodeClaudeSettings = Schema.decodeUnknownSync(ClaudeSettings); describe("ServerSettings usage price overrides", () => { const prices = { inputCostPerMillionTokens: 2, outputCostPerMillionTokens: 8 }; it("defaults to automatic pricing and round-trips arbitrary model IDs", () => { expect(decodeServerSettings({}).usagePriceOverrides).toEqual({}); const settings = decodeServerSettings({ usagePriceOverrides: { " vendor/example-model ": prices }, }); expect(encodeServerSettings(settings).usagePriceOverrides).toEqual({ "vendor/example-model": prices, }); }); it("accepts zero rates, optional cache rates, and per-model deletion", () => { const overrides = { "example-model": { inputCostPerMillionTokens: 0, outputCostPerMillionTokens: 0, cacheReadCostPerMillionTokens: 0.5, cacheWriteCostPerMillionTokens: 3, }, "removed-model": null, }; expect( decodeServerSettingsPatch({ usagePriceOverrides: overrides }).usagePriceOverrides, ).toEqual(overrides); }); it.each([ "inputCostPerMillionTokens", "outputCostPerMillionTokens", "cacheReadCostPerMillionTokens", "cacheWriteCostPerMillionTokens", ])("rejects invalid %s rates at the settings boundary", (field) => { for (const value of [-1, Number.NaN, Number.POSITIVE_INFINITY, "2"]) { const usagePriceOverrides = { "example-model": { ...prices, [field]: value } }; expect(() => decodeServerSettings({ usagePriceOverrides })).toThrow(); expect(() => decodeServerSettingsPatch({ usagePriceOverrides })).toThrow(); } }); it("rejects empty model IDs and incomplete input/output pricing", () => { for (const usagePriceOverrides of [ { " ": prices }, { "example-model": { inputCostPerMillionTokens: 2 } }, { "example-model": { outputCostPerMillionTokens: 8 } }, ]) { expect(() => decodeServerSettingsPatch({ usagePriceOverrides })).toThrow(); } });}); describe("custom model settings", () => { const capabilities = { optionDescriptors: [ { id: "effort", label: "Reasoning", type: "select", options: [{ id: "high", label: "High", isDefault: true }], }, ], }; it("accepts legacy bare slugs alongside full entries", () => { const decoded = decodeClaudeSettings({ customModels: ["bare-slug", { slug: "named", name: "Named", capabilities }], }); expect(decoded.customModels).toEqual([ "bare-slug", { slug: "named", name: "Named", capabilities }, ]); }); it("accepts entries at the settings patch boundary", () => { expect( decodeServerSettingsPatch({ providers: { codex: { customModels: [{ slug: "x", capabilities }] } }, }).providers?.codex?.customModels, ).toEqual([{ slug: "x", capabilities }]); expect(() => decodeServerSettingsPatch({ providers: { codex: { customModels: [{ name: "no slug" }] } } }), ).toThrow(); });}); describe("ClaudeSettings auto-compaction", () => { it("uses Claude's default threshold when no override is configured", () => { expect(decodeClaudeSettings({}).autoCompactWindow).toBe(""); }); it.each(["100000", "300000", "1000000"])( "accepts a supported auto-compaction threshold: %s", (value) => { expect(decodeClaudeSettings({ autoCompactWindow: value }).autoCompactWindow).toBe(value); }, ); it.each(["99999", "1000001", "300k", "invalid"])( "rejects an unsupported auto-compaction threshold: %s", (value) => { expect(() => decodeClaudeSettings({ autoCompactWindow: value })).toThrow(); }, ); it("rejects an unsupported threshold at the settings patch boundary", () => { expect(() => decodeServerSettingsPatch({ providers: { claudeAgent: { autoCompactWindow: "300k" } } }), ).toThrow(); expect( decodeServerSettingsPatch({ providers: { claudeAgent: { autoCompactWindow: "300000" } } }), ).toBeDefined(); });}); describe("ClientSettings word wrap", () => { it("defaults word wrap on", () => { expect(decodeClientSettings({}).wordWrap).toBe(true); }); it("ignores obsolete wrapping preferences", () => { const decoded = decodeClientSettings({ chatWordWrap: false, diffWordWrap: false, }); expect(decoded.wordWrap).toBe(true); expect(decoded).not.toHaveProperty("chatWordWrap"); expect(decoded).not.toHaveProperty("diffWordWrap"); });}); describe("ClientSettings proactive panels", () => { it("is opt-in and accepts client-local updates", () => { expect(decodeClientSettings({}).proactivePanelsEnabled).toBe(false); expect(decodeClientSettingsPatch({ proactivePanelsEnabled: true }).proactivePanelsEnabled).toBe( true, ); });}); describe("ClientSettings quit confirmation", () => { it("defaults to hold", () => { expect(decodeClientSettings({}).confirmQuit).toBe("hold"); }); it.each(["direct", "hold", "double-click"] as const)("accepts the %s mode", (mode) => { expect(decodeClientSettings({ confirmQuit: mode }).confirmQuit).toBe(mode); expect(decodeClientSettingsPatch({ confirmQuit: mode }).confirmQuit).toBe(mode); }); it.each([ [true, "hold"], [false, "direct"], ] as const)("migrates the legacy %s value to %s", (legacyValue, mode) => { const settings = decodeClientSettings({ confirmQuit: legacyValue }); expect(settings.confirmQuit).toBe(mode); expect(encodeClientSettings(settings).confirmQuit).toBe(mode); }); it("rejects legacy booleans at the patch boundary", () => { expect(() => decodeClientSettingsPatch({ confirmQuit: true })).toThrow(); });}); describe("ClientSettings browser recording frame rate", () => { it("defaults to 30 fps", () => { expect(decodeClientSettings({}).browserRecordingFrameRate).toBe(30); }); it.each([30, 60])("accepts a supported frame rate: %s", (frameRate) => { expect( decodeClientSettings({ browserRecordingFrameRate: frameRate }).browserRecordingFrameRate, ).toBe(frameRate); expect( decodeClientSettingsPatch({ browserRecordingFrameRate: frameRate }).browserRecordingFrameRate, ).toBe(frameRate); }); it.each([24, 59, 120])("rejects an unsupported frame rate: %s", (frameRate) => { expect(() => decodeClientSettings({ browserRecordingFrameRate: frameRate })).toThrow(); expect(() => decodeClientSettingsPatch({ browserRecordingFrameRate: frameRate })).toThrow(); });}); describe("ClientSettings glass opacity", () => { it("defaults to a readable translucent surface", () => { expect(decodeClientSettings({}).glassOpacity).toBe(80); }); it.each([39, 101, 72.5])("rejects an invalid glass opacity: %s", (value) => { expect(() => decodeClientSettings({ glassOpacity: value })).toThrow(); expect(() => decodeClientSettingsPatch({ glassOpacity: value })).toThrow(); }); it.each([40, 75, 100])("accepts a glass opacity within the supported range: %s", (value) => { expect(decodeClientSettings({ glassOpacity: value }).glassOpacity).toBe(value); expect(decodeClientSettingsPatch({ glassOpacity: value }).glassOpacity).toBe(value); });}); describe("ClientSettings appearance contrast", () => { it("defaults to the theme's original contrast", () => { expect(decodeClientSettings({}).appearanceContrast).toBe(100); }); it.each([49, 201, 92.5])("rejects an invalid appearance contrast: %s", (value) => { expect(() => decodeClientSettings({ appearanceContrast: value })).toThrow(); expect(() => decodeClientSettingsPatch({ appearanceContrast: value })).toThrow(); }); it.each([50, 100, 150, 200])("accepts an appearance contrast in range: %s", (value) => { expect(decodeClientSettings({ appearanceContrast: value }).appearanceContrast).toBe(value); expect(decodeClientSettingsPatch({ appearanceContrast: value }).appearanceContrast).toBe(value); });}); describe("ClientSettings panel animations", () => { it("defaults to instant changes", () => { expect(decodeClientSettings({}).panelAnimationDurationMs).toBe(0); }); it.each([0, 400])("accepts a panel animation duration: %s", (value) => { expect(decodeClientSettingsPatch({ panelAnimationDurationMs: value })).toEqual({ panelAnimationDurationMs: value, }); }); it.each([-1, 401, 150.5])("rejects an invalid panel animation duration: %s", (value) => { expect(() => decodeClientSettingsPatch({ panelAnimationDurationMs: value })).toThrow(); });}); describe("ClientSettings environment identification", () => { it("defaults to artwork and accepts each presentation mode", () => { expect(decodeClientSettings({}).environmentIdentificationMode).toBe("artwork"); for (const mode of ["artwork", "pill", "none"] as const) { expect( decodeClientSettingsPatch({ environmentIdentificationMode: mode }) .environmentIdentificationMode, ).toBe(mode); } }); it("rejects unsupported presentation modes", () => { expect(() => decodeClientSettings({ environmentIdentificationMode: "badge" })).toThrow(); expect(() => decodeClientSettingsPatch({ environmentIdentificationMode: "badge" })).toThrow(); });}); describe("ClientSettings sidebar", () => { it("defaults to the current sidebar", () => { expect(decodeClientSettings({}).legacySidebarEnabled).toBe(false); }); it("drops the retired sidebar v2 beta keys, resetting everyone to the default", () => { const decoded = decodeClientSettings({ sidebarV2Enabled: false, sidebarV2ConfiguredByUser: true, }); expect(decoded.legacySidebarEnabled).toBe(false); expect(decoded).not.toHaveProperty("sidebarV2Enabled"); expect(decoded).not.toHaveProperty("sidebarV2ConfiguredByUser"); }); it("preserves an explicit legacy sidebar opt-in", () => { expect(decodeClientSettings({ legacySidebarEnabled: true }).legacySidebarEnabled).toBe(true); expect(decodeClientSettingsPatch({ legacySidebarEnabled: true }).legacySidebarEnabled).toBe( true, ); }); it("keeps unpin confirmation opt-in and patchable", () => { expect(decodeClientSettings({}).confirmThreadUnpin).toBe(false); expect(decodeClientSettingsPatch({ confirmThreadUnpin: true }).confirmThreadUnpin).toBe(true); expect(() => decodeClientSettingsPatch({ confirmThreadUnpin: "yes" })).toThrow(); });}); describe("ClientSettings context window meter", () => { it("defaults off and preserves an explicit legacy opt-in", () => { expect(decodeClientSettings({}).contextWindowMeterEnabled).toBe(false); expect( decodeClientSettings({ contextWindowMeterEnabled: true }).contextWindowMeterEnabled, ).toBe(true); expect( decodeClientSettingsPatch({ contextWindowMeterEnabled: true }).contextWindowMeterEnabled, ).toBe(true); });}); describe("ClientSettings composer collapse", () => { it("collapses on blur and scroll by default and accepts opting out of each", () => { const defaults = decodeClientSettings({}); expect(defaults.composerCollapseOnBlur).toBe(true); expect(defaults.composerCollapseOnScroll).toBe(true); const blurOff = decodeClientSettings({ composerCollapseOnBlur: false }); expect(blurOff.composerCollapseOnBlur).toBe(false); expect(blurOff.composerCollapseOnScroll).toBe(true); expect( decodeClientSettingsPatch({ composerCollapseOnScroll: false }).composerCollapseOnScroll, ).toBe(false); });}); describe("ServerSettings thread settlement", () => { it("defaults merge settlement on and inactivity settlement to three days", () => { const settings = decodeServerSettings({}); expect(settings.sidebarAutoSettleAfterDays).toBe(3); expect(settings.sidebarAutoSettleOnMerge).toBe(true); }); it("allows both automatic rules to be disabled", () => { expect( decodeServerSettings({ sidebarAutoSettleAfterDays: null, sidebarAutoSettleOnMerge: false, }), ).toMatchObject({ sidebarAutoSettleAfterDays: null, sidebarAutoSettleOnMerge: false }); expect( decodeServerSettingsPatch({ sidebarAutoSettleAfterDays: null, sidebarAutoSettleOnMerge: false, }), ).toMatchObject({ sidebarAutoSettleAfterDays: null, sidebarAutoSettleOnMerge: false }); }); it.each([-1, 0, 91])("rejects an auto-settle threshold outside 1..90: %s", (value) => { expect(() => decodeServerSettings({ sidebarAutoSettleAfterDays: value })).toThrow(); expect(() => decodeServerSettingsPatch({ sidebarAutoSettleAfterDays: value })).toThrow(); });}); describe("ServerSettings.providerInstances (slice-2 invariant)", () => { it("defaults text generation to Luna at low reasoning effort", () => { expect(DEFAULT_SERVER_SETTINGS.textGenerationModelSelection).toEqual({ instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.6-luna", options: [{ id: "reasoningEffort", value: "low" }], }); }); it("defaults to an empty record so legacy configs without the key still decode", () => { expect(DEFAULT_SERVER_SETTINGS.providerInstances).toEqual({}); }); it("decodes a fully empty config (legacy on-disk shape) without complaint", () => { const decoded = decodeServerSettings({}); expect(decoded.providerInstances).toEqual({}); // Legacy `providers` struct is still hydrated with its per-driver defaults // so existing call sites keep working through the migration. expect(decoded.providers.codex.enabled).toBe(true); }); it("decodes a multi-instance map mixing first-party and fork drivers", () => { const decoded = decodeServerSettings({ providerInstances: { codex_personal: { driver: "codex", displayName: "Codex (personal)", config: { homePath: "~/.codex_personal" }, }, codex_work: { driver: "codex", config: { homePath: "~/.codex_work" }, }, ollama_local: { driver: "ollama", displayName: "Ollama (local)", config: { endpoint: "http://localhost:11434" }, }, }, }); const personalId = ProviderInstanceId.make("codex_personal"); const workId = ProviderInstanceId.make("codex_work"); const ollamaId = ProviderInstanceId.make("ollama_local"); expect(decoded.providerInstances[personalId]?.driver).toBe("codex"); expect(decoded.providerInstances[workId]?.config).toEqual({ homePath: "~/.codex_work" }); // Critical: a config naming a driver this build does not know about // (`ollama` is not in `ProviderDriverKind`) must round-trip without loss. // The runtime handles "driver not installed" — the schema must not. expect(decoded.providerInstances[ollamaId]?.driver).toBe("ollama"); expect(decoded.providerInstances[ollamaId]?.config).toEqual({ endpoint: "http://localhost:11434", }); }); it("rejects instance keys that violate the slug pattern", () => { expect(() => decodeServerSettings({ providerInstances: { "1bad": { driver: "codex" } }, }), ).toThrow(); });}); describe("provider enabled defaults", () => { it("enables only the stable bindings by default", () => { const decoded = decodeServerSettings({}); expect(decoded.providers.codex.enabled).toBe(true); expect(decoded.providers.claudeAgent.enabled).toBe(true); expect(decoded.providers.cursor.enabled).toBe(false); expect(decoded.providers.grok.enabled).toBe(false); expect(decoded.providers.opencode.enabled).toBe(false); }); it("keeps Cursor enabled when an existing user explicitly opted in", () => { const cursor = ProviderDriverKind.make("cursor"); const cursorId = ProviderInstanceId.make("cursor"); const decoded = decodeServerSettings({ providers: { cursor: { enabled: true } }, providerInstances: { [cursorId]: { driver: cursor, enabled: true, config: {} }, }, }); expect(decoded.providers.cursor.enabled).toBe(true); expect(resolveProviderInstanceEnabled(decoded.providerInstances[cursorId]!)).toBe(true); }); it("resolves instance enabled state with explicit false winning", () => { const grok = ProviderDriverKind.make("grok"); const codex = ProviderDriverKind.make("codex"); // No flags anywhere: driver default applies. expect(resolveProviderInstanceEnabled({ driver: grok, config: {} })).toBe(false); expect(resolveProviderInstanceEnabled({ driver: codex, config: {} })).toBe(true); // Unknown fork drivers stay enabled. expect( resolveProviderInstanceEnabled({ driver: ProviderDriverKind.make("ollama"), config: {} }), ).toBe(true); // Envelope flag wins over the driver default. expect(resolveProviderInstanceEnabled({ driver: grok, enabled: true, config: {} })).toBe(true); expect(resolveProviderInstanceEnabled({ driver: codex, enabled: false, config: {} })).toBe( false, ); // Legacy in-config flag fills in when the envelope is silent. expect(resolveProviderInstanceEnabled({ driver: grok, config: { enabled: true } })).toBe(true); // Conflicting flags: the explicit false wins, whichever side it is on. expect( resolveProviderInstanceEnabled({ driver: grok, enabled: true, config: { enabled: false } }), ).toBe(false); expect( resolveProviderInstanceEnabled({ driver: codex, enabled: false, config: { enabled: true } }), ).toBe(false); });}); describe("ServerSettings worktree defaults", () => { it("defaults start-from-origin on for legacy configs", () => { expect(decodeServerSettings({}).newWorktreesStartFromOrigin).toBe(true); }); it("accepts start-from-origin updates", () => { expect( decodeServerSettingsPatch({ newWorktreesStartFromOrigin: false }).newWorktreesStartFromOrigin, ).toBe(false); });}); describe("ServerSettings.sourceControlWritingStyle", () => { it("defaults all style settings for legacy configs", () => { const settings = decodeServerSettings({}); expect(settings.sourceControlWritingStyle).toEqual({ mode: "repo_conventions", customInstructions: "", followChangeRequestTemplates: true, }); expect(settings.sourceControlWriterModelSelection).toBeNull(); }); it("trims partial style updates", () => { const patch = decodeServerSettingsPatch({Lines 1–500 of 623. Load more
import { describe, expect, it } from "vite-plus/test"; import * as Schema from "effect/Schema"; import { ProviderDriverKind, ProviderInstanceId } from "./providerInstance.ts"; import { ClientSettingsSchema, ClientSettingsPatch, ClaudeSettings, DEFAULT_SERVER_SETTINGS, resolveProviderInstanceEnabled, ServerSettings, ServerSettingsPatch, } from "./settings.ts"; const decodeClientSettings = Schema.decodeUnknownSync(ClientSettingsSchema); const decodeClientSettingsPatch = Schema.decodeUnknownSync(ClientSettingsPatch); const encodeClientSettings = Schema.encodeSync(ClientSettingsSchema); const decodeServerSettings = Schema.decodeUnknownSync(ServerSettings); const decodeServerSettingsPatch = Schema.decodeUnknownSync(ServerSettingsPatch); const encodeServerSettings = Schema.encodeSync(ServerSettings); const decodeClaudeSettings = Schema.decodeUnknownSync(ClaudeSettings); describe("ServerSettings usage price overrides", () => { const prices = { inputCostPerMillionTokens: 2, outputCostPerMillionTokens: 8 }; it("defaults to automatic pricing and round-trips arbitrary model IDs", () => { expect(decodeServerSettings({}).usagePriceOverrides).toEqual({}); const settings = decodeServerSettings({ usagePriceOverrides: { " vendor/example-model ": prices }, }); expect(encodeServerSettings(settings).usagePriceOverrides).toEqual({ "vendor/example-model": prices, }); }); it("accepts zero rates, optional cache rates, and per-model deletion", () => { const overrides = { "example-model": { inputCostPerMillionTokens: 0, outputCostPerMillionTokens: 0, cacheReadCostPerMillionTokens: 0.5, cacheWriteCostPerMillionTokens: 3, }, "removed-model": null, }; expect( decodeServerSettingsPatch({ usagePriceOverrides: overrides }).usagePriceOverrides, ).toEqual(overrides); }); it.each([ "inputCostPerMillionTokens", "outputCostPerMillionTokens", "cacheReadCostPerMillionTokens", "cacheWriteCostPerMillionTokens", ])("rejects invalid %s rates at the settings boundary", (field) => { for (const value of [-1, Number.NaN, Number.POSITIVE_INFINITY, "2"]) { const usagePriceOverrides = { "example-model": { ...prices, [field]: value } }; expect(() => decodeServerSettings({ usagePriceOverrides })).toThrow(); expect(() => decodeServerSettingsPatch({ usagePriceOverrides })).toThrow(); } }); it("rejects empty model IDs and incomplete input/output pricing", () => { for (const usagePriceOverrides of [ { " ": prices }, { "example-model": { inputCostPerMillionTokens: 2 } }, { "example-model": { outputCostPerMillionTokens: 8 } }, ]) { expect(() => decodeServerSettingsPatch({ usagePriceOverrides })).toThrow(); } }); }); describe("custom model settings", () => { const capabilities = { optionDescriptors: [ { id: "effort", label: "Reasoning", type: "select", options: [{ id: "high", label: "High", isDefault: true }], }, ], }; it("accepts legacy bare slugs alongside full entries", () => { const decoded = decodeClaudeSettings({ customModels: ["bare-slug", { slug: "named", name: "Named", capabilities }], }); expect(decoded.customModels).toEqual([ "bare-slug", { slug: "named", name: "Named", capabilities }, ]); }); it("accepts entries at the settings patch boundary", () => { expect( decodeServerSettingsPatch({ providers: { codex: { customModels: [{ slug: "x", capabilities }] } }, }).providers?.codex?.customModels, ).toEqual([{ slug: "x", capabilities }]); expect(() => decodeServerSettingsPatch({ providers: { codex: { customModels: [{ name: "no slug" }] } } }), ).toThrow(); }); }); describe("ClaudeSettings auto-compaction", () => { it("uses Claude's default threshold when no override is configured", () => { expect(decodeClaudeSettings({}).autoCompactWindow).toBe(""); }); it.each(["100000", "300000", "1000000"])( "accepts a supported auto-compaction threshold: %s", (value) => { expect(decodeClaudeSettings({ autoCompactWindow: value }).autoCompactWindow).toBe(value); }, ); it.each(["99999", "1000001", "300k", "invalid"])( "rejects an unsupported auto-compaction threshold: %s", (value) => { expect(() => decodeClaudeSettings({ autoCompactWindow: value })).toThrow(); }, ); it("rejects an unsupported threshold at the settings patch boundary", () => { expect(() => decodeServerSettingsPatch({ providers: { claudeAgent: { autoCompactWindow: "300k" } } }), ).toThrow(); expect( decodeServerSettingsPatch({ providers: { claudeAgent: { autoCompactWindow: "300000" } } }), ).toBeDefined(); }); }); describe("ClientSettings word wrap", () => { it("defaults word wrap on", () => { expect(decodeClientSettings({}).wordWrap).toBe(true); }); it("ignores obsolete wrapping preferences", () => { const decoded = decodeClientSettings({ chatWordWrap: false, diffWordWrap: false, }); expect(decoded.wordWrap).toBe(true); expect(decoded).not.toHaveProperty("chatWordWrap"); expect(decoded).not.toHaveProperty("diffWordWrap"); }); }); describe("ClientSettings proactive panels", () => { it("is opt-in and accepts client-local updates", () => { expect(decodeClientSettings({}).proactivePanelsEnabled).toBe(false); expect(decodeClientSettingsPatch({ proactivePanelsEnabled: true }).proactivePanelsEnabled).toBe( true, ); }); }); describe("ClientSettings quit confirmation", () => { it("defaults to hold", () => { expect(decodeClientSettings({}).confirmQuit).toBe("hold"); }); it.each(["direct", "hold", "double-click"] as const)("accepts the %s mode", (mode) => { expect(decodeClientSettings({ confirmQuit: mode }).confirmQuit).toBe(mode); expect(decodeClientSettingsPatch({ confirmQuit: mode }).confirmQuit).toBe(mode); }); it.each([ [true, "hold"], [false, "direct"], ] as const)("migrates the legacy %s value to %s", (legacyValue, mode) => { const settings = decodeClientSettings({ confirmQuit: legacyValue }); expect(settings.confirmQuit).toBe(mode); expect(encodeClientSettings(settings).confirmQuit).toBe(mode); }); it("rejects legacy booleans at the patch boundary", () => { expect(() => decodeClientSettingsPatch({ confirmQuit: true })).toThrow(); }); }); describe("ClientSettings browser recording frame rate", () => { it("defaults to 30 fps", () => { expect(decodeClientSettings({}).browserRecordingFrameRate).toBe(30); }); it.each([30, 60])("accepts a supported frame rate: %s", (frameRate) => { expect( decodeClientSettings({ browserRecordingFrameRate: frameRate }).browserRecordingFrameRate, ).toBe(frameRate); expect( decodeClientSettingsPatch({ browserRecordingFrameRate: frameRate }).browserRecordingFrameRate, ).toBe(frameRate); }); it.each([24, 59, 120])("rejects an unsupported frame rate: %s", (frameRate) => { expect(() => decodeClientSettings({ browserRecordingFrameRate: frameRate })).toThrow(); expect(() => decodeClientSettingsPatch({ browserRecordingFrameRate: frameRate })).toThrow(); }); }); describe("ClientSettings glass opacity", () => { it("defaults to a readable translucent surface", () => { expect(decodeClientSettings({}).glassOpacity).toBe(80); }); it.each([39, 101, 72.5])("rejects an invalid glass opacity: %s", (value) => { expect(() => decodeClientSettings({ glassOpacity: value })).toThrow(); expect(() => decodeClientSettingsPatch({ glassOpacity: value })).toThrow(); }); it.each([40, 75, 100])("accepts a glass opacity within the supported range: %s", (value) => { expect(decodeClientSettings({ glassOpacity: value }).glassOpacity).toBe(value); expect(decodeClientSettingsPatch({ glassOpacity: value }).glassOpacity).toBe(value); }); }); describe("ClientSettings appearance contrast", () => { it("defaults to the theme's original contrast", () => { expect(decodeClientSettings({}).appearanceContrast).toBe(100); }); it.each([49, 201, 92.5])("rejects an invalid appearance contrast: %s", (value) => { expect(() => decodeClientSettings({ appearanceContrast: value })).toThrow(); expect(() => decodeClientSettingsPatch({ appearanceContrast: value })).toThrow(); }); it.each([50, 100, 150, 200])("accepts an appearance contrast in range: %s", (value) => { expect(decodeClientSettings({ appearanceContrast: value }).appearanceContrast).toBe(value); expect(decodeClientSettingsPatch({ appearanceContrast: value }).appearanceContrast).toBe(value); }); }); describe("ClientSettings panel animations", () => { it("defaults to instant changes", () => { expect(decodeClientSettings({}).panelAnimationDurationMs).toBe(0); }); it.each([0, 400])("accepts a panel animation duration: %s", (value) => { expect(decodeClientSettingsPatch({ panelAnimationDurationMs: value })).toEqual({ panelAnimationDurationMs: value, }); }); it.each([-1, 401, 150.5])("rejects an invalid panel animation duration: %s", (value) => { expect(() => decodeClientSettingsPatch({ panelAnimationDurationMs: value })).toThrow(); }); }); describe("ClientSettings environment identification", () => { it("defaults to artwork and accepts each presentation mode", () => { expect(decodeClientSettings({}).environmentIdentificationMode).toBe("artwork"); for (const mode of ["artwork", "pill", "none"] as const) { expect( decodeClientSettingsPatch({ environmentIdentificationMode: mode }) .environmentIdentificationMode, ).toBe(mode); } }); it("rejects unsupported presentation modes", () => { expect(() => decodeClientSettings({ environmentIdentificationMode: "badge" })).toThrow(); expect(() => decodeClientSettingsPatch({ environmentIdentificationMode: "badge" })).toThrow(); }); }); describe("ClientSettings sidebar", () => { it("defaults to the current sidebar", () => { expect(decodeClientSettings({}).legacySidebarEnabled).toBe(false); }); it("drops the retired sidebar v2 beta keys, resetting everyone to the default", () => { const decoded = decodeClientSettings({ sidebarV2Enabled: false, sidebarV2ConfiguredByUser: true, }); expect(decoded.legacySidebarEnabled).toBe(false); expect(decoded).not.toHaveProperty("sidebarV2Enabled"); expect(decoded).not.toHaveProperty("sidebarV2ConfiguredByUser"); }); it("preserves an explicit legacy sidebar opt-in", () => { expect(decodeClientSettings({ legacySidebarEnabled: true }).legacySidebarEnabled).toBe(true); expect(decodeClientSettingsPatch({ legacySidebarEnabled: true }).legacySidebarEnabled).toBe( true, ); }); it("keeps unpin confirmation opt-in and patchable", () => { expect(decodeClientSettings({}).confirmThreadUnpin).toBe(false); expect(decodeClientSettingsPatch({ confirmThreadUnpin: true }).confirmThreadUnpin).toBe(true); expect(() => decodeClientSettingsPatch({ confirmThreadUnpin: "yes" })).toThrow(); }); }); describe("ClientSettings context window meter", () => { it("defaults off and preserves an explicit legacy opt-in", () => { expect(decodeClientSettings({}).contextWindowMeterEnabled).toBe(false); expect( decodeClientSettings({ contextWindowMeterEnabled: true }).contextWindowMeterEnabled, ).toBe(true); expect( decodeClientSettingsPatch({ contextWindowMeterEnabled: true }).contextWindowMeterEnabled, ).toBe(true); }); }); describe("ClientSettings composer collapse", () => { it("collapses on blur and scroll by default and accepts opting out of each", () => { const defaults = decodeClientSettings({}); expect(defaults.composerCollapseOnBlur).toBe(true); expect(defaults.composerCollapseOnScroll).toBe(true); const blurOff = decodeClientSettings({ composerCollapseOnBlur: false }); expect(blurOff.composerCollapseOnBlur).toBe(false); expect(blurOff.composerCollapseOnScroll).toBe(true); expect( decodeClientSettingsPatch({ composerCollapseOnScroll: false }).composerCollapseOnScroll, ).toBe(false); }); }); describe("ServerSettings thread settlement", () => { it("defaults merge settlement on and inactivity settlement to three days", () => { const settings = decodeServerSettings({}); expect(settings.sidebarAutoSettleAfterDays).toBe(3); expect(settings.sidebarAutoSettleOnMerge).toBe(true); }); it("allows both automatic rules to be disabled", () => { expect( decodeServerSettings({ sidebarAutoSettleAfterDays: null, sidebarAutoSettleOnMerge: false, }), ).toMatchObject({ sidebarAutoSettleAfterDays: null, sidebarAutoSettleOnMerge: false }); expect( decodeServerSettingsPatch({ sidebarAutoSettleAfterDays: null, sidebarAutoSettleOnMerge: false, }), ).toMatchObject({ sidebarAutoSettleAfterDays: null, sidebarAutoSettleOnMerge: false }); }); it.each([-1, 0, 91])("rejects an auto-settle threshold outside 1..90: %s", (value) => { expect(() => decodeServerSettings({ sidebarAutoSettleAfterDays: value })).toThrow(); expect(() => decodeServerSettingsPatch({ sidebarAutoSettleAfterDays: value })).toThrow(); }); }); describe("ServerSettings.providerInstances (slice-2 invariant)", () => { it("defaults text generation to Luna at low reasoning effort", () => { expect(DEFAULT_SERVER_SETTINGS.textGenerationModelSelection).toEqual({ instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.6-luna", options: [{ id: "reasoningEffort", value: "low" }], }); }); it("defaults to an empty record so legacy configs without the key still decode", () => { expect(DEFAULT_SERVER_SETTINGS.providerInstances).toEqual({}); }); it("decodes a fully empty config (legacy on-disk shape) without complaint", () => { const decoded = decodeServerSettings({}); expect(decoded.providerInstances).toEqual({}); // Legacy `providers` struct is still hydrated with its per-driver defaults // so existing call sites keep working through the migration. expect(decoded.providers.codex.enabled).toBe(true); }); it("decodes a multi-instance map mixing first-party and fork drivers", () => { const decoded = decodeServerSettings({ providerInstances: { codex_personal: { driver: "codex", displayName: "Codex (personal)", config: { homePath: "~/.codex_personal" }, }, codex_work: { driver: "codex", config: { homePath: "~/.codex_work" }, }, ollama_local: { driver: "ollama", displayName: "Ollama (local)", config: { endpoint: "http://localhost:11434" }, }, }, }); const personalId = ProviderInstanceId.make("codex_personal"); const workId = ProviderInstanceId.make("codex_work"); const ollamaId = ProviderInstanceId.make("ollama_local"); expect(decoded.providerInstances[personalId]?.driver).toBe("codex"); expect(decoded.providerInstances[workId]?.config).toEqual({ homePath: "~/.codex_work" }); // Critical: a config naming a driver this build does not know about // (`ollama` is not in `ProviderDriverKind`) must round-trip without loss. // The runtime handles "driver not installed" — the schema must not. expect(decoded.providerInstances[ollamaId]?.driver).toBe("ollama"); expect(decoded.providerInstances[ollamaId]?.config).toEqual({ endpoint: "http://localhost:11434", }); }); it("rejects instance keys that violate the slug pattern", () => { expect(() => decodeServerSettings({ providerInstances: { "1bad": { driver: "codex" } }, }), ).toThrow(); }); }); describe("provider enabled defaults", () => { it("enables only the stable bindings by default", () => { const decoded = decodeServerSettings({}); expect(decoded.providers.codex.enabled).toBe(true); expect(decoded.providers.claudeAgent.enabled).toBe(true); expect(decoded.providers.cursor.enabled).toBe(false); expect(decoded.providers.grok.enabled).toBe(false); expect(decoded.providers.opencode.enabled).toBe(false); }); it("keeps Cursor enabled when an existing user explicitly opted in", () => { const cursor = ProviderDriverKind.make("cursor"); const cursorId = ProviderInstanceId.make("cursor"); const decoded = decodeServerSettings({ providers: { cursor: { enabled: true } }, providerInstances: { [cursorId]: { driver: cursor, enabled: true, config: {} }, }, }); expect(decoded.providers.cursor.enabled).toBe(true); expect(resolveProviderInstanceEnabled(decoded.providerInstances[cursorId]!)).toBe(true); }); it("resolves instance enabled state with explicit false winning", () => { const grok = ProviderDriverKind.make("grok"); const codex = ProviderDriverKind.make("codex"); // No flags anywhere: driver default applies. expect(resolveProviderInstanceEnabled({ driver: grok, config: {} })).toBe(false); expect(resolveProviderInstanceEnabled({ driver: codex, config: {} })).toBe(true); // Unknown fork drivers stay enabled. expect( resolveProviderInstanceEnabled({ driver: ProviderDriverKind.make("ollama"), config: {} }), ).toBe(true); // Envelope flag wins over the driver default. expect(resolveProviderInstanceEnabled({ driver: grok, enabled: true, config: {} })).toBe(true); expect(resolveProviderInstanceEnabled({ driver: codex, enabled: false, config: {} })).toBe( false, ); // Legacy in-config flag fills in when the envelope is silent. expect(resolveProviderInstanceEnabled({ driver: grok, config: { enabled: true } })).toBe(true); // Conflicting flags: the explicit false wins, whichever side it is on. expect( resolveProviderInstanceEnabled({ driver: grok, enabled: true, config: { enabled: false } }), ).toBe(false); expect( resolveProviderInstanceEnabled({ driver: codex, enabled: false, config: { enabled: true } }), ).toBe(false); }); }); describe("ServerSettings worktree defaults", () => { it("defaults start-from-origin on for legacy configs", () => { expect(decodeServerSettings({}).newWorktreesStartFromOrigin).toBe(true); }); it("accepts start-from-origin updates", () => { expect( decodeServerSettingsPatch({ newWorktreesStartFromOrigin: false }).newWorktreesStartFromOrigin, ).toBe(false); }); }); describe("ServerSettings.sourceControlWritingStyle", () => { it("defaults all style settings for legacy configs", () => { const settings = decodeServerSettings({}); expect(settings.sourceControlWritingStyle).toEqual({ mode: "repo_conventions", customInstructions: "", followChangeRequestTemplates: true, }); expect(settings.sourceControlWriterModelSelection).toBeNull(); }); it("trims partial style updates", () => { const patch = decodeServerSettingsPatch({