diff --git a/apps/desktop/electron/translucency.test.ts b/apps/desktop/electron/translucency.test.ts index ed7216c47c..59cc15828e 100644 --- a/apps/desktop/electron/translucency.test.ts +++ b/apps/desktop/electron/translucency.test.ts @@ -668,6 +668,7 @@ describe('resolving the book for the painted appearance', () => { const book = setTranslucencyValues(normalizeBook({ material: 'popover', mode: 'glass' }, true), 'dark', { intensity: 33 }) + const resolved = resolveTranslucency(book, 'dark', false) expect(resolved.intensity).toBe(33) diff --git a/apps/desktop/src/plugins/accent/picker.tsx b/apps/desktop/src/plugins/accent/picker.tsx index 144b0706e1..82d610632a 100644 --- a/apps/desktop/src/plugins/accent/picker.tsx +++ b/apps/desktop/src/plugins/accent/picker.tsx @@ -99,7 +99,15 @@ function useDrag(onMove: (x: number, y: number) => void) { } /** Lightness (y, 1→0) × chroma (x, 0→CHROMA_MAX) at one hue. */ -function ChromaLightnessField({ hue, lch, onPick }: { hue: number; lch: Oklch; onPick: (l: number, c: number) => void }) { +function ChromaLightnessField({ + hue, + lch, + onPick +}: { + hue: number + lch: Oklch + onPick: (l: number, c: number) => void +}) { const canvas = useRef(null) const drag = useDrag((x, y) => onPick(1 - y, x * CHROMA_MAX)) diff --git a/apps/desktop/src/store/projects.test.ts b/apps/desktop/src/store/projects.test.ts index 5e0e447343..e80b645882 100644 --- a/apps/desktop/src/store/projects.test.ts +++ b/apps/desktop/src/store/projects.test.ts @@ -579,13 +579,17 @@ describe('repository discovery policy', () => { isDesktopFsRemoteMode.mockReturnValue(true) const scanRepos = vi.fn() desktopGit.mockReturnValue({ scanRepos } as never) + const request = vi.fn(async (method: string) => method === 'projects.tree' ? { active_id: null, projects: [], scoped_session_ids: [] } : { accepted: false, repos: [] } ) + gatewayWith(request) - $projectTree.set([{ id: 'seed', label: 'seed', path: null, repos: [], sessionCount: 0 } satisfies SidebarProjectTree]) + $projectTree.set([ + { id: 'seed', label: 'seed', path: null, repos: [], sessionCount: 0 } satisfies SidebarProjectTree + ]) await scanAndRecordRepos(true) @@ -613,17 +617,23 @@ describe('repository discovery policy', () => { // keep its last known list and flag the failure, not go silently blank. isDesktopFsRemoteMode.mockReturnValue(true) desktopGit.mockReturnValue({ scanRepos: vi.fn() } as never) + const request = vi.fn(async (method: string) => { if (method === 'projects.discover_repos') { throw new Error('discover_repos failed') } + if (method === 'projects.tree') { return { active_id: null, projects: [], scoped_session_ids: [] } } + return { accepted: false, repos: [] } }) + gatewayWith(request) - $projectTree.set([{ id: 'seed', label: 'seed', path: null, repos: [], sessionCount: 0 } satisfies SidebarProjectTree]) + $projectTree.set([ + { id: 'seed', label: 'seed', path: null, repos: [], sessionCount: 0 } satisfies SidebarProjectTree + ]) await scanAndRecordRepos(true) @@ -633,9 +643,7 @@ describe('repository discovery policy', () => { expect.objectContaining({ preview_limit: expect.any(Number) }) ) // ... the cached tree is preserved ... - expect($projectTree.get()).toEqual([ - { id: 'seed', label: 'seed', path: null, repos: [], sessionCount: 0 } - ]) + expect($projectTree.get()).toEqual([{ id: 'seed', label: 'seed', path: null, repos: [], sessionCount: 0 }]) }) it('does not treat an error-shaped discover_repos response as a successful refresh', async () => { @@ -644,13 +652,15 @@ describe('repository discovery policy', () => { // the silent, empty sidebar of #81723. isDesktopFsRemoteMode.mockReturnValue(true) desktopGit.mockReturnValue({ scanRepos: vi.fn() } as never) + const request = vi.fn(async (method: string) => - method === 'projects.tree' - ? { active_id: null, projects: [], scoped_session_ids: [] } - : { accepted: false } + method === 'projects.tree' ? { active_id: null, projects: [], scoped_session_ids: [] } : { accepted: false } ) + gatewayWith(request) - $projectTree.set([{ id: 'seed', label: 'seed', path: null, repos: [], sessionCount: 0 } satisfies SidebarProjectTree]) + $projectTree.set([ + { id: 'seed', label: 'seed', path: null, repos: [], sessionCount: 0 } satisfies SidebarProjectTree + ]) await scanAndRecordRepos(true) @@ -658,9 +668,7 @@ describe('repository discovery policy', () => { 'projects.tree', expect.objectContaining({ preview_limit: expect.any(Number) }) ) - expect($projectTree.get()).toEqual([ - { id: 'seed', label: 'seed', path: null, repos: [], sessionCount: 0 } - ]) + expect($projectTree.get()).toEqual([{ id: 'seed', label: 'seed', path: null, repos: [], sessionCount: 0 }]) }) it('records repos under the profile the scan started with, not one focused mid-scan', async () => { @@ -678,10 +686,13 @@ describe('repository discovery policy', () => { ) gatewayWith(request) + const scanRepos = vi.fn(() => { markScanStarted() + return scanResult }) + desktopGit.mockReturnValue({ scanRepos } as never) getHermesConfig.mockResolvedValue({ desktop: { @@ -756,6 +767,7 @@ describe('project tree profile isolation', () => { it('does not publish a late projects.list response from the previous profile', async () => { const { promise: defaultResponse, resolve: resolveDefault } = deferred() + const request = vi.fn((_method: string, params: Record) => params.profile === 'default' ? defaultResponse @@ -764,6 +776,7 @@ describe('project tree profile isolation', () => { projects: [{ id: 'profile-b', label: 'Profile B' }] }) ) + const gateway = { connectionState: 'open', request } activeGateway.mockReturnValue(gateway as never) gatewayAtom.set(gateway as never) @@ -782,6 +795,7 @@ describe('project tree profile isolation', () => { it('does not publish a late projects.tree response from the previous profile', async () => { const { promise: defaultResponse, resolve: resolveDefault } = deferred() + const request = vi.fn((_method: string, params: Record) => params.profile === 'default' ? defaultResponse @@ -791,6 +805,7 @@ describe('project tree profile isolation', () => { scoped_session_ids: [] }) ) + const gateway = { connectionState: 'open', request } activeGateway.mockReturnValue(gateway as never) gatewayAtom.set(gateway as never) @@ -810,6 +825,7 @@ describe('project tree profile isolation', () => { it('drops a late hydrated-project response from the previous profile', async () => { const { promise: defaultResponse, resolve: resolveDefault } = deferred() + const request = vi.fn((_method: string, params: Record) => params.profile === 'default' ? defaultResponse @@ -817,6 +833,7 @@ describe('project tree profile isolation', () => { project: { id: 'profile-b', label: 'Profile B', path: null, repos: [], sessionCount: 0 } }) ) + const gateway = { connectionState: 'open', request } activeGateway.mockReturnValue(gateway as never) gatewayAtom.set(gateway as never) diff --git a/apps/desktop/src/store/projects.ts b/apps/desktop/src/store/projects.ts index eced3fe82b..c6f9df8470 100644 --- a/apps/desktop/src/store/projects.ts +++ b/apps/desktop/src/store/projects.ts @@ -416,6 +416,7 @@ export async function refreshProjects(): Promise { try { context = await activeProjectsContext() + const payload = await gatewayRequestOn( context.gateway, 'projects.list', @@ -559,6 +560,7 @@ export async function fetchProjectSessions(projectId: string): Promise( context.gateway, 'projects.project_sessions', @@ -672,6 +674,7 @@ export async function scanAndRecordRepos(force = false): Promise { // the merged session-derived + scanned list. try { const context = await activeProjectsContext() + const discovered = await gatewayRequestOn<{ repos?: unknown discovery_policy?: unknown @@ -684,6 +687,7 @@ export async function scanAndRecordRepos(force = false): Promise { // being blanked back to the silent, unpopulated state of #81723. if (discovered?.repos === undefined) { markProjectsRpcFailure(new Error('projects.discover_repos returned no repo list')) + return } @@ -700,6 +704,7 @@ export async function scanAndRecordRepos(force = false): Promise { // let the sidebar show the error/absent state. markProjectsRpcFailure(err) } + return } @@ -763,6 +768,7 @@ export async function scanAndRecordRepos(force = false): Promise { } state.completedSignature = signature + // Completion refresh only when the focused profile still matches the one // the scan was captured under. refreshProjectTree() re-derives the current // context, so skipping on mismatch keeps a stale scan from publishing into diff --git a/apps/desktop/src/themes/retint.test.ts b/apps/desktop/src/themes/retint.test.ts index a5f6eb6a42..9422b222f9 100644 --- a/apps/desktop/src/themes/retint.test.ts +++ b/apps/desktop/src/themes/retint.test.ts @@ -87,7 +87,15 @@ describe('retintTheme', () => { const before = nousTheme[mode]! const after = rose[mode]! - for (const key of ['primary', 'ring', 'midground', 'composerRing', 'accent', 'secondary', 'userBubble'] as const) { + for (const key of [ + 'primary', + 'ring', + 'midground', + 'composerRing', + 'accent', + 'secondary', + 'userBubble' + ] as const) { expect(after[key], `${mode}.${key}`).not.toBe(before[key]) } } diff --git a/apps/desktop/src/themes/retint.ts b/apps/desktop/src/themes/retint.ts index a3d03e9816..2f9c3b5b03 100644 --- a/apps/desktop/src/themes/retint.ts +++ b/apps/desktop/src/themes/retint.ts @@ -179,6 +179,7 @@ export function retintTheme(theme: DesktopTheme, color: string): DesktopTheme { } const light = seedFor(theme.colors, seed) + const retinted: DesktopTheme = { ...theme, colors: theme.colors.primary === light ? theme.colors : retintColors(theme.colors, light, false) diff --git a/ui-tui/packages/hermes-ink/src/ink/components/App.tsx b/ui-tui/packages/hermes-ink/src/ink/components/App.tsx index b432e525e6..136f08903d 100644 --- a/ui-tui/packages/hermes-ink/src/ink/components/App.tsx +++ b/ui-tui/packages/hermes-ink/src/ink/components/App.tsx @@ -341,6 +341,7 @@ export default class App extends PureComponent { if (!skipKittyKeyboardProtocol()) { this.props.stdout.write(ENABLE_KITTY_KEYBOARD) } + this.props.stdout.write(ENABLE_MODIFY_OTHER_KEYS) } diff --git a/ui-tui/packages/hermes-ink/src/ink/ink.tsx b/ui-tui/packages/hermes-ink/src/ink/ink.tsx index 0254a81e69..ca4f239b70 100644 --- a/ui-tui/packages/hermes-ink/src/ink/ink.tsx +++ b/ui-tui/packages/hermes-ink/src/ink/ink.tsx @@ -1478,9 +1478,7 @@ export default class Ink { // on each call. if (supportsExtendedKeys()) { this.options.stdout.write( - DISABLE_KITTY_KEYBOARD + - (skipKittyKeyboardProtocol() ? '' : ENABLE_KITTY_KEYBOARD) + - ENABLE_MODIFY_OTHER_KEYS + DISABLE_KITTY_KEYBOARD + (skipKittyKeyboardProtocol() ? '' : ENABLE_KITTY_KEYBOARD) + ENABLE_MODIFY_OTHER_KEYS ) } diff --git a/ui-tui/packages/hermes-ink/src/ink/terminal.test.ts b/ui-tui/packages/hermes-ink/src/ink/terminal.test.ts index d41902e486..b615790c78 100644 --- a/ui-tui/packages/hermes-ink/src/ink/terminal.test.ts +++ b/ui-tui/packages/hermes-ink/src/ink/terminal.test.ts @@ -84,6 +84,7 @@ describe('skipKittyKeyboardProtocol', () => { const { env } = await import('../utils/env.js') const { skipKittyKeyboardProtocol } = await import('./terminal.js') const saved = env.terminal + try { env.terminal = 'ghostty' expect(skipKittyKeyboardProtocol()).toBe(true) @@ -98,6 +99,7 @@ describe('skipKittyKeyboardProtocol', () => { const { env } = await import('../utils/env.js') const { skipKittyKeyboardProtocol } = await import('./terminal.js') const saved = env.terminal + try { env.terminal = terminal expect(skipKittyKeyboardProtocol()).toBe(false)