diff --git a/packages/ui/src/sync/session-ui-store.test.js b/packages/ui/src/sync/session-ui-store.test.js index e4488f40..b97afbfe 100644 --- a/packages/ui/src/sync/session-ui-store.test.js +++ b/packages/ui/src/sync/session-ui-store.test.js @@ -1,5 +1,7 @@ import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; import { opencodeClient } from '@/lib/opencode/client'; +import { useProjectsStore } from '@/stores/useProjectsStore'; +import { useDirectoryStore } from '@/stores/useDirectoryStore'; import { useSessionWorktreeStore } from './session-worktree-store'; import { routeMessage, useSessionUIStore } from './session-ui-store'; import { setActionRefs, setOptimisticRefs } from './session-actions'; @@ -226,6 +228,50 @@ describe('routeMessage directory scoping', () => { }); }); +describe('openNewSessionDraft project binding', () => { + const projectA = { id: 'proj-a', path: '/projects/alpha', label: 'Alpha' }; + const projectB = { id: 'proj-b', path: '/projects/beta', label: 'Beta' }; + + beforeEach(() => { + useSessionUIStore.setState({ + currentSessionId: null, + currentSessionDirectory: null, + newSessionDraft: { open: false, directoryOverride: null, parentID: null }, + availableWorktreesByProject: new Map(), + }); + useProjectsStore.setState({ + projects: [projectA, projectB], + activeProjectId: projectA.id, + }); + useDirectoryStore.getState().setDirectory(projectB.path, { showOverlay: false }); + }); + + test('binds draft to active project when current directory differs', () => { + useSessionUIStore.getState().openNewSessionDraft(); + const draft = useSessionUIStore.getState().newSessionDraft; + + expect(draft.open).toBe(true); + expect(draft.selectedProjectId).toBe(projectA.id); + expect(draft.directoryOverride).toBe(projectA.path); + }); + + test('respects explicit directoryOverride over active project', () => { + useSessionUIStore.getState().openNewSessionDraft({ directoryOverride: '/projects/beta/src' }); + const draft = useSessionUIStore.getState().newSessionDraft; + + expect(draft.open).toBe(true); + expect(draft.directoryOverride).toBe('/projects/beta/src'); + }); + + test('respects explicit selectedProjectId over active project', () => { + useSessionUIStore.getState().openNewSessionDraft({ selectedProjectId: projectB.id }); + const draft = useSessionUIStore.getState().newSessionDraft; + + expect(draft.open).toBe(true); + expect(draft.selectedProjectId).toBe(projectB.id); + }); +}); + describe('routeMessage skill invocation', () => { // OpenCode registers every skill as a command (source: "skill"), so a skill // selected from the slash menu must be dispatched via session.command so its diff --git a/packages/ui/src/sync/session-ui-store.ts b/packages/ui/src/sync/session-ui-store.ts index e2f3a248..6ae058d9 100644 --- a/packages/ui/src/sync/session-ui-store.ts +++ b/packages/ui/src/sync/session-ui-store.ts @@ -626,6 +626,7 @@ export const useSessionUIStore = create()((set, get) => ({ if (explicitProject || explicitDirectory !== null) { return explicitProject ?? inferredProjectFromDir ?? fallbackProject } + if (activeProject) return activeProject if (currentDirectory) return currentDirProject ?? fallbackProject return persistedProjectByDir ?? persistedProjectById ?? fallbackProject })() @@ -633,6 +634,8 @@ export const useSessionUIStore = create()((set, get) => ({ const directory = (() => { if (explicitDirectory !== null) return explicitDirectory if (explicitProject) return normalizePath(explicitProject.path ?? null) + const selectedProjectPath = normalizePath(selectedProject?.path ?? null) + if (selectedProjectPath && selectedProjectPath !== currentDirectory) return selectedProjectPath if (currentDirectory) return currentDirectory if (persistedTarget?.directory) return persistedTarget.directory return normalizePath(selectedProject?.path ?? null)