fix(projects): le viewer de conversation persiste au changement de layout/projet (#172)

Le viewer (CLI custom) se vidait au changement de layout/projet car l'etat viewerConversationId etait reset sur le changement de projet actif. Remplace par un etat viewerConversation {projectId, conversationId} lie a la conversation elle-meme, rendu contre le projectId de la conversation: la conversation persiste tant que la session headless existe, a l'instar de la TUI native.
This commit is contained in:
2026-08-11 09:42:23 +02:00
parent fa6136745d
commit a943f0cf8b
2 changed files with 122 additions and 30 deletions

View File

@ -117,6 +117,11 @@ interface PendingConversationOpen {
conversationId: string;
}
interface OpenConversationViewer {
projectId: string;
conversationId: string;
}
interface PluginMenuRenderBoundaryProps {
resetKey: string;
fallbackMenus: MenuBarMenu[];
@ -234,9 +239,8 @@ export function ProjectsView() {
// When set, the main area swaps the terminal grid for the read-only
// conversation viewer (LS7) — pure local UI state, same mechanic as the
// terminal↔gitGraph swap; **not** a backend layout kind.
const [viewerConversationId, setViewerConversationId] = useState<
string | null
>(null);
const [viewerConversation, setViewerConversation] =
useState<OpenConversationViewer | null>(null);
const [pendingConversationOpen, setPendingConversationOpen] =
useState<PendingConversationOpen | null>(null);
const [taskToasts, setTaskToasts] = useState<BackgroundTaskToast[]>([]);
@ -295,13 +299,12 @@ export function ProjectsView() {
// loading it against the new project's store fails with "not found: layout X".
useEffect(() => {
setActiveLayout(null);
setViewerConversationId(null);
}, [active?.id]);
useEffect(() => {
if (!active || pendingConversationOpen?.projectId !== active.id) return;
setSettingsSection(null);
setViewerConversationId(pendingConversationOpen.conversationId);
setViewerConversation(pendingConversationOpen);
dismissFloating();
setPendingConversationOpen(null);
}, [active?.id, pendingConversationOpen]);
@ -499,10 +502,12 @@ export function ProjectsView() {
};
}, [windowGateway]);
// Opening a conversation viewer takes over the main area — dismiss floating
// windows so they don't obscure the viewer (LS7). Docked views stay beside it.
// Opening a conversation viewer takes over the project content area — dismiss
// floating windows so they don't obscure the viewer (LS7). Docked views and
// the layout tabs stay available beside/above it.
function openConversation(conversationId: string) {
setViewerConversationId(conversationId);
if (!active) return;
setViewerConversation({ projectId: active.id, conversationId });
dismissFloating();
}
@ -525,7 +530,6 @@ export function ProjectsView() {
if (projectOpen) vm.activateTab(toast.projectId);
setSettingsSection(null);
setViewerConversationId(null);
setPlacement("work", "floating");
}
@ -901,20 +905,20 @@ export function ProjectsView() {
onSectionChange={setSettingsSection}
onClose={() => setSettingsSection(null)}
/>
) : active && viewerConversationId ? (
<ConversationViewer
key={`${active.id}-${viewerConversationId}`}
projectId={active.id}
conversationId={viewerConversationId}
onClose={() => setViewerConversationId(null)}
/>
) : active ? (
<>
<LayoutTabs
projectId={active.id}
onActiveLayoutChange={setActiveLayout}
/>
{activeLayoutKind === "gitGraph" ? (
{viewerConversation ? (
<ConversationViewer
key={`${viewerConversation.projectId}-${viewerConversation.conversationId}`}
projectId={viewerConversation.projectId}
conversationId={viewerConversation.conversationId}
onClose={() => setViewerConversation(null)}
/>
) : activeLayoutKind === "gitGraph" ? (
<GitGraphView
key={`${active.id}-${activeLayout?.id ?? "default"}-graph`}
projectId={active.id}
@ -933,6 +937,13 @@ export function ProjectsView() {
/>
)}
</>
) : viewerConversation ? (
<ConversationViewer
key={`${viewerConversation.projectId}-${viewerConversation.conversationId}`}
projectId={viewerConversation.projectId}
conversationId={viewerConversation.conversationId}
onClose={() => setViewerConversation(null)}
/>
) : (
<div className="flex flex-1 justify-center overflow-y-auto p-6">
<div className="w-full max-w-xl">