mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
Desktop sessions need a fast local escape hatch that opens the same materialized cwd the agent is editing, without showing unavailable IDE choices or persisting detection state. This adds a local open-targets API with silent in-memory detection for common IDEs and platform file managers, then wires a compact Codex-style toolbar menu into the desktop TabBar for active session workdirs. Constraint: The first version is local IDE/editor and Finder/Explorer/file-manager only, no terminal targets or IDE plugin integration. Constraint: The opened path must come from the active session workDir so isolated worktrees open the actual agent editing surface. Rejected: Persisting detected applications | detection is cheap and temporary state avoids stale app inventory. Rejected: Rendering unavailable IDEs as disabled menu rows | the user asked to show only detected targets and fall back to Finder/Explorer when no IDE is available. Confidence: high Scope-risk: moderate Directive: Keep this path session-workdir based; do not switch it to repository root without proving isolated worktree behavior. Tested: bun test src/server/__tests__/open-target-service.test.ts src/server/__tests__/open-target-api.test.ts Tested: cd desktop && bun run test -- src/stores/openTargetStore.test.ts src/components/layout/OpenProjectMenu.test.tsx src/components/layout/TabBar.test.tsx Tested: cd desktop && bun run lint Tested: bun run check:desktop Tested: bun test src/server/__tests__/h5-access-auth.test.ts -t 'allows local desktop H5 access settings under explicit server auth with a valid bearer' Not-tested: bun run check:server full suite had one unrelated H5 auth integration timeout in the full concurrent run; the timed-out test passed when rerun alone.
53 lines
1.6 KiB
TypeScript
53 lines
1.6 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
const apiMocks = vi.hoisted(() => ({
|
|
list: vi.fn(),
|
|
open: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('../api/openTargets', () => ({
|
|
openTargetsApi: apiMocks,
|
|
}))
|
|
|
|
describe('openTargetStore', () => {
|
|
beforeEach(async () => {
|
|
vi.resetModules()
|
|
apiMocks.list.mockReset()
|
|
apiMocks.open.mockReset()
|
|
})
|
|
|
|
it('caches detected targets inside the TTL', async () => {
|
|
const { useOpenTargetStore } = await import('./openTargetStore')
|
|
apiMocks.list.mockResolvedValue({
|
|
platform: 'darwin',
|
|
targets: [{ id: 'finder', kind: 'file_manager', label: 'Finder', icon: 'finder', platform: 'darwin' }],
|
|
primaryTargetId: 'finder',
|
|
cachedAt: 1,
|
|
ttlMs: 60_000,
|
|
})
|
|
|
|
await useOpenTargetStore.getState().refreshTargets()
|
|
await useOpenTargetStore.getState().ensureTargets()
|
|
|
|
expect(apiMocks.list).toHaveBeenCalledTimes(1)
|
|
expect(useOpenTargetStore.getState().primaryTargetId).toBe('finder')
|
|
})
|
|
|
|
it('remembers the last successful target for this runtime', async () => {
|
|
const { useOpenTargetStore } = await import('./openTargetStore')
|
|
apiMocks.list.mockResolvedValue({
|
|
platform: 'darwin',
|
|
targets: [{ id: 'vscode', kind: 'ide', label: 'VS Code', icon: 'vscode', platform: 'darwin' }],
|
|
primaryTargetId: 'vscode',
|
|
cachedAt: 1,
|
|
ttlMs: 60_000,
|
|
})
|
|
apiMocks.open.mockResolvedValue({ ok: true, targetId: 'vscode', path: '/repo' })
|
|
|
|
await useOpenTargetStore.getState().refreshTargets()
|
|
await useOpenTargetStore.getState().openTarget('vscode', '/repo')
|
|
|
|
expect(useOpenTargetStore.getState().lastSuccessfulTargetId).toBe('vscode')
|
|
})
|
|
})
|