Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 187 additions & 0 deletions docs/plans/2026-07-08-remove-freshagent-fontscale-spec.md

Large diffs are not rendered by default.

37 changes: 1 addition & 36 deletions shared/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,6 @@ const PANE_SNAP_THRESHOLD_MIN = 0
const PANE_SNAP_THRESHOLD_MAX = 8
const SIDEBAR_WIDTH_MIN = 200
const SIDEBAR_WIDTH_MAX = 500
const FRESH_AGENT_FONT_SCALE_MIN = 1
const FRESH_AGENT_FONT_SCALE_MAX = 2
// Fresh-agent panes render 50% larger than the base UI by default.
export const FRESH_AGENT_FONT_SCALE_DEFAULT = 1.5
export const FRESH_AGENT_FONT_SCALE_OPTIONS = [1, 1.25, 1.5, 1.75, 2] as const
export const FRESH_AGENT_STYLE_VALUES = ['sans', 'serif', 'mono'] as const
export type FreshAgentStyle = (typeof FRESH_AGENT_STYLE_VALUES)[number]
export const DEFAULT_FRESH_AGENT_STYLE: FreshAgentStyle = 'sans'
Expand Down Expand Up @@ -92,7 +87,6 @@ const FRESH_AGENT_LOCAL_KEYS = [
'showThinking',
'showTools',
'showTimecodes',
'fontScale',
] as const
Comment on lines 87 to 90

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Drop fontScale when stripping freshAgent-only patches

With fontScale removed from this local-key list, stripLocalSettings({ freshAgent: { fontScale: 1.25 } }, { migrateLegacyFreshAgentAlias: false }) no longer enters the migratedFreshAgent branch because the sanitizer returns undefined, so the shallow-copied freshAgent object remains in next. That lets stale/deprecated patches sent through normalizeServerSettingsPatchForApi reach /api/settings as freshAgent.fontScale, which the strict server schema rejects instead of silently dropping as intended; delete/overwrite next.freshAgent whenever a raw freshAgent block exists but sanitizes to empty.

Useful? React with 👍 / 👎.


export type ThemeMode = (typeof THEME_VALUES)[number]
Expand Down Expand Up @@ -221,7 +215,6 @@ export type LocalSettings = {
showThinking: boolean
showTools: boolean
showTimecodes: boolean
fontScale: number
}
notifications: {
soundEnabled: boolean
Expand Down Expand Up @@ -467,13 +460,6 @@ function normalizeRoundedClampedNumber(value: unknown, min: number, max: number)
return Math.round(normalized)
}

function normalizeFreshAgentFontScale(value: unknown): number {
return (
normalizeClampedNumber(value, FRESH_AGENT_FONT_SCALE_MIN, FRESH_AGENT_FONT_SCALE_MAX)
?? FRESH_AGENT_FONT_SCALE_DEFAULT
)
}

export function normalizeFreshAgentStyleOverride(value: unknown): FreshAgentStyle | undefined {
return FreshAgentStyleSchema.safeParse(value).success
? value as FreshAgentStyle
Expand All @@ -484,10 +470,6 @@ export function normalizeFreshAgentStyle(value: unknown): FreshAgentStyle {
return normalizeFreshAgentStyleOverride(value) ?? DEFAULT_FRESH_AGENT_STYLE
}

function normalizeLocalFreshAgent(value: LocalSettings['freshAgent']): LocalSettings['freshAgent'] {
return { ...value, fontScale: normalizeFreshAgentFontScale(value.fontScale) }
}

function normalizeExtractedLocalSeed(patch: Record<string, unknown>): LocalSettingsPatch | undefined {
const normalized: LocalSettingsPatch = {}

Expand Down Expand Up @@ -620,14 +602,6 @@ function normalizeExtractedLocalSeed(patch: Record<string, unknown>): LocalSetti
if (typeof patch.freshAgent.showTimecodes === 'boolean') {
freshAgent.showTimecodes = patch.freshAgent.showTimecodes as boolean
}
const normalizedFontScale = normalizeClampedNumber(
patch.freshAgent.fontScale,
FRESH_AGENT_FONT_SCALE_MIN,
FRESH_AGENT_FONT_SCALE_MAX,
)
if (normalizedFontScale !== undefined) {
freshAgent.fontScale = normalizedFontScale
}
if (Object.keys(freshAgent).length > 0) {
normalized.freshAgent = freshAgent
}
Expand Down Expand Up @@ -883,7 +857,6 @@ export const defaultLocalSettings: LocalSettings = {
showThinking: false,
showTools: false,
showTimecodes: false,
fontScale: FRESH_AGENT_FONT_SCALE_DEFAULT,
},
notifications: {
soundEnabled: true,
Expand All @@ -907,14 +880,6 @@ function sanitizeFreshAgentLocalSettingsPatchInput(
if (typeof rawFreshAgent.showTimecodes === 'boolean') {
freshAgent.showTimecodes = rawFreshAgent.showTimecodes
}
const normalizedFontScale = normalizeClampedNumber(
rawFreshAgent.fontScale,
FRESH_AGENT_FONT_SCALE_MIN,
FRESH_AGENT_FONT_SCALE_MAX,
)
if (normalizedFontScale !== undefined) {
freshAgent.fontScale = normalizedFontScale
}
return Object.keys(freshAgent).length > 0 ? freshAgent : undefined
}

Expand Down Expand Up @@ -1291,7 +1256,7 @@ export function resolveLocalSettings(patch?: LocalSettingsPatch): LocalSettings
sortMode: normalizeLocalSortMode(patch?.sidebar?.sortMode),
worktreeGrouping: normalizeWorktreeGrouping(patch?.sidebar?.worktreeGrouping),
},
freshAgent: normalizeLocalFreshAgent(mergeDefined(defaultLocalSettings.freshAgent, freshAgentPatch)),
freshAgent: mergeDefined(defaultLocalSettings.freshAgent, freshAgentPatch),
notifications: mergeDefined(defaultLocalSettings.notifications, patch?.notifications),
}
}
Expand Down
4 changes: 2 additions & 2 deletions test/e2e-browser/specs/fresh-agent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -876,8 +876,8 @@ test.describe('Fresh Agent', () => {
const transcript = paneRoot.locator('[data-context="fresh-agent-transcript"]')
await expect.poll(async () => transcript.evaluate((el) => el.scrollWidth - el.clientWidth)).toBeLessThanOrEqual(1)

// Changing the terminal font size updates the transcript size. The legacy
// freshAgent.fontScale setting no longer drives Fresh Agent body text.
// Changing the terminal font size updates the transcript size. The removed
// freshAgent.fontScale setting is ignored when present in a dispatched patch.
await page.evaluate(() => {
window.__FRESHELL_TEST_HARNESS__?.dispatch({
type: 'settings/updateSettingsLocal',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,11 @@ describe('browser preferences fresh-agent settings compatibility', () => {
freshAgent: {
showTools: true,
showThinking: true,
fontScale: 1.25,
},
})
expect(resolved.freshAgent.showTools).toBe(true)
expect(resolved.freshAgent.showThinking).toBe(true)
expect(resolved.freshAgent.fontScale).toBe(1.25)
expect('fontScale' in resolved.freshAgent).toBe(false)
expect('agentChat' in (record.settings ?? {})).toBe(false)
expect('agentChat' in resolved).toBe(false)
})
Expand All @@ -55,7 +54,6 @@ describe('browser preferences fresh-agent settings compatibility', () => {
expect(raw.settings).toEqual({
freshAgent: {
showTools: true,
fontScale: 1.25,
},
})
expect(raw.settings.agentChat).toBeUndefined()
Expand Down
11 changes: 6 additions & 5 deletions test/unit/client/store/browserPreferencesPersistence.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,12 +226,12 @@ describe('browserPreferencesPersistence', () => {
expect(bp.settings?.agentChat).toBeUndefined()
})

it('does not persist deprecated freshAgent.fontScale values', () => {
it('ignores removed freshAgent.fontScale values', () => {
const store = createStore()

store.dispatch(updateSettingsLocal({
freshAgent: { fontScale: 1.75 },
}))
} as never))

vi.advanceTimersByTime(BROWSER_PREFERENCES_PERSIST_DEBOUNCE_MS)

Expand All @@ -240,7 +240,7 @@ describe('browserPreferencesPersistence', () => {
expect(bp.settings?.agentChat).toBeUndefined()
})

it('still accepts legacy freshAgent.fontScale records when rehydrating old preferences', () => {
it('drops legacy freshAgent.fontScale records when rehydrating old preferences', () => {
const store = createStore()

store.dispatch(updateSettingsLocal({
Expand All @@ -253,8 +253,9 @@ describe('browserPreferencesPersistence', () => {
const rehydrated = resolveLocalSettings({
...bp.settings,
freshAgent: { ...bp.settings.freshAgent, fontScale: 1.75 },
})
expect(rehydrated.freshAgent.fontScale).toBe(1.75)
} as never)
expect(rehydrated.freshAgent.showTools).toBe(true)
expect('fontScale' in rehydrated.freshAgent).toBe(false)
expect('agentChat' in rehydrated).toBe(false)
})
})
58 changes: 28 additions & 30 deletions test/unit/shared/settings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ describe('shared settings contract', () => {

expect(resolved.freshAgent.showTools).toBe(true)
expect(resolved.freshAgent.showThinking).toBe(true)
expect(resolved.freshAgent.fontScale).toBe(1.25)
expect('fontScale' in resolved.freshAgent).toBe(false)
expect('agentChat' in resolved).toBe(false)
})

Expand Down Expand Up @@ -604,49 +604,47 @@ describe('shared settings contract', () => {
expect(schema.safeParse({ panes: { multirowTabs: true } }).success).toBe(false)
})

describe('legacy fresh-agent font scale settings', () => {
it('keeps the legacy fresh-agent font scale default for old stored settings', () => {
const resolved = resolveLocalSettings(undefined)
expect(resolved.freshAgent.fontScale).toBe(1.5)
describe('deprecated fresh-agent font scale is dropped', () => {
it('resolves the default fresh-agent settings without a fontScale key', () => {
expect(resolveLocalSettings(undefined).freshAgent).toEqual({
showThinking: false,
showTools: false,
showTimecodes: false,
})
})

it('resolves a configured fresh-agent font scale without mirroring agentChat', () => {
const resolved = resolveLocalSettings({ freshAgent: { fontScale: 1.75 } })
expect(resolved.freshAgent.fontScale).toBe(1.75)
expect('agentChat' in resolved).toBe(false)
it('drops a canonical freshAgent.fontScale regardless of value', () => {
for (const value of [1.75, 5, 'big']) {
expect(resolveLocalSettings({ freshAgent: { fontScale: value } } as never).freshAgent).toEqual({
showThinking: false,
showTools: false,
showTimecodes: false,
})
}
})

it('accepts the legacy fresh-agent font scale through the agentChat alias', () => {
const resolved = resolveLocalSettings({ agentChat: { fontScale: 1.25 } } as never)
expect(resolved.freshAgent.fontScale).toBe(1.25)
it('drops the legacy agentChat alias fontScale while keeping its siblings', () => {
const resolved = resolveLocalSettings({ agentChat: { showTools: true, fontScale: 1.25 } } as never)
expect(resolved.freshAgent.showTools).toBe(true)
expect('fontScale' in resolved.freshAgent).toBe(false)
expect('agentChat' in resolved).toBe(false)
})

it('clamps an out-of-range legacy fresh-agent font scale into the supported range', () => {
expect(resolveLocalSettings({ freshAgent: { fontScale: 5 } }).freshAgent.fontScale).toBe(2)
expect(resolveLocalSettings({ freshAgent: { fontScale: 0.1 } }).freshAgent.fontScale).toBe(1)
})

it('falls back to the default when the legacy fresh-agent font scale is not a finite number', () => {
expect(
resolveLocalSettings({
freshAgent: { fontScale: 'big' as unknown as number },
}).freshAgent.fontScale,
).toBe(1.5)
})

it('keeps the resolved legacy fresh-agent font scale in composed settings', () => {
it('carries no fontScale into composed settings', () => {
const resolved = composeResolvedSettings(
createDefaultServerSettings({ loggingDebug: false }),
resolveLocalSettings({ freshAgent: { fontScale: 2 } }),
resolveLocalSettings({ freshAgent: { fontScale: 2 } } as never),
)
expect(resolved.freshAgent.fontScale).toBe(2)
expect('fontScale' in resolved.freshAgent).toBe(false)
})

it('clamps the legacy fresh-agent font scale when extracting a local seed', () => {
it('drops fontScale when extracting a legacy local seed', () => {
expect(
extractLegacyLocalSettingsSeed({ agentChat: { fontScale: 9 } } as Record<string, unknown>),
).toEqual({ freshAgent: { fontScale: 2 } })
).toEqual(undefined)
expect(
extractLegacyLocalSettingsSeed({ agentChat: { showTools: true, fontScale: 9 } } as Record<string, unknown>),
).toEqual({ freshAgent: { showTools: true } })
})
})
})
Loading