fix(ui): open saved plans against their owning project
Saved Project knowledge plans opened as an empty editor whenever the
viewer could not resolve the owning project from the current directory:
managed chats (openchamber:chats is not a registered project), worktrees
outside the repo path, and plan tabs restored after a reload. Titles
still rendered because the list reads the manifest through the correct
owner.
- Thread the owner explicitly (savedProjectPlan = { projectRef, planId })
from the panel, mobile surfaces, and persisted context tabs; PlanView
no longer guesses the project.
- An unrecognized directory resolves to no owner instead of borrowing
the active project's knowledge.
- Serialize plan writes per document (planSaveQueue) so close/switch
within the autosave debounce no longer drops the last edits, saves
cannot land out of order, and a recovered save clears the error banner.
- Send saved-plan contents inline in Improve/Implement prompts (they
have no file path); disable those actions for managed-chat plans,
which have no project directory to create a session in.
- Drop persisted plan tabs that carry an id without an owner rather than
reopening them against a guessed project.
This commit is contained in:
@@ -45,4 +45,46 @@ describe('resolveProjectContextOwner', () => {
|
||||
|
||||
expect(owner).toEqual({ id: 'openchamber', path: '/workspace/openchamber' });
|
||||
});
|
||||
|
||||
test('returns null for a recognized directory that owns nothing, instead of borrowing the active project', () => {
|
||||
const owner = resolveProjectContextOwner({
|
||||
projects,
|
||||
worktreesByProject: new Map(),
|
||||
directory: '/some/other/project',
|
||||
activeProjectId: 'openchamber',
|
||||
chatDraftOpen: false,
|
||||
chatDraftTarget: 'project',
|
||||
homeDirectory: '/Users/test',
|
||||
});
|
||||
|
||||
expect(owner).toBeNull();
|
||||
});
|
||||
|
||||
test('falls back to the active project only when there is no directory at all', () => {
|
||||
const owner = resolveProjectContextOwner({
|
||||
projects,
|
||||
worktreesByProject: new Map(),
|
||||
directory: null,
|
||||
activeProjectId: 'openchamber',
|
||||
chatDraftOpen: false,
|
||||
chatDraftTarget: 'project',
|
||||
homeDirectory: '/Users/test',
|
||||
});
|
||||
|
||||
expect(owner).toEqual({ id: 'openchamber', path: '/workspace/openchamber' });
|
||||
});
|
||||
|
||||
test('never falls back to the first project when the active project is unknown', () => {
|
||||
const owner = resolveProjectContextOwner({
|
||||
projects,
|
||||
worktreesByProject: new Map(),
|
||||
directory: null,
|
||||
activeProjectId: 'missing-project',
|
||||
chatDraftOpen: false,
|
||||
chatDraftTarget: 'project',
|
||||
homeDirectory: '/Users/test',
|
||||
});
|
||||
|
||||
expect(owner).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,7 +47,16 @@ export const resolveProjectContextOwner = ({
|
||||
return { id: sessionProject.id, path: sessionProject.path };
|
||||
}
|
||||
|
||||
const activeProject = projects.find((project) => project.id === activeProjectId) ?? projects[0] ?? null;
|
||||
// A concrete directory that resolves to nothing owns nothing. Falling back
|
||||
// to the active project here showed one project's knowledge under another
|
||||
// project's name (the "plans open empty" bug), so the panel stays empty
|
||||
// instead of lying. The active-project fallback is only for states with no
|
||||
// directory at all, such as a new-session draft that has not landed yet.
|
||||
if (normalizedDirectory) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const activeProject = projects.find((project) => project.id === activeProjectId) ?? null;
|
||||
return activeProject ? { id: activeProject.id, path: activeProject.path } : null;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user