fix(frontend,application): lot utilisateur #315 — détachement Paramètres projet, couleurs projet fiables, restauration conversation custom CLI, images post-aller-retour projet, retrait menus Projects/Project Context (QA verte)

- détachement: le panneau Paramètres du projet devient détachable (mapping historique panel=context → projectSettings dans parseViewWindowParams, DETACHABLE/ViewPanelBody alignés, ProjectsView ne court-circuitant plus le refus); Retry d'échec de détachement préservé
- couleurs projet: projectTheme publie idea:project-theme-changed localement + BroadcastChannel inter-webviews (fenêtres OS détachées), garde de révision par projet dans ProjectsView/ViewWindow contre les lectures settings en course — plus de flash/retour au bleu par défaut
- conversation custom CLI: transcript_restore_expected ne considère plus l'absence de scrollback volatile comme une restauration échouée quand la session rouvre en SessionPlan::None (continuation vide valide); CustomAgentChatView n'affiche plus le faux « conversation liée n'a pas pu être restaurée »
- images: conservation des images de conversation après changement de projet puis retour
- menus Panneaux: retrait des entrées Projects et Project Context (panelOrder WebWorkspace, création/switch via tab-bar +, contexte projet fusionné dans Paramètres du projet)
- QA verte: tests frontend ciblés window/ViewWindow/ProjectSettingsPanel/ProjectsView.detach/projects/CustomAgentChatView/WebDesktopWorkspace + cargo test -p application lifecycle transcript_restore_expected; fichiers .ideai/* (tickets 308-315, index/counter, agents/test, chat-drafts, system-permissions, project-settings.json, attachments/) et scripts/ hors périmètre préservés en worktree; aucun push
This commit is contained in:
2026-09-11 11:27:09 +02:00
parent 352ec2d8b7
commit 45f819e1e8
15 changed files with 434 additions and 82 deletions

View File

@ -199,10 +199,14 @@ describe("CustomAgentChatView", () => {
it("settles an isolated conversation id without recovered turns as an actionable unavailable state (#307/#314)", async () => {
const agent = {
launchAgentChat: vi.fn(async () => ({ sessionId: "fresh-session" })),
launchAgentChat: vi.fn(async () => ({
sessionId: "fresh-session",
transcriptUnavailableReason: "missingTranscript" as const,
})),
reattachAgentChat: vi.fn(async (sessionId: string) => ({
sessionId,
scrollback: [],
transcriptUnavailableReason: "missingTranscript" as const,
})),
sendAgentChat: vi.fn(async () => {}),
cancelAgentChat: vi.fn(async () => {}),
@ -253,6 +257,29 @@ describe("CustomAgentChatView", () => {
expect(await screen.findByText("Envoyez un message pour démarrer la conversation structurée.")).toBeTruthy();
});
it("keeps an empty linked custom CLI continuation usable without explicit transcript loss", async () => {
const agent = {
launchAgentChat: vi.fn(async () => ({ sessionId: "valid-session" })),
reattachAgentChat: vi.fn(async (sessionId: string) => ({
sessionId,
scrollback: [],
})),
sendAgentChat: vi.fn(async () => {}),
cancelAgentChat: vi.fn(async () => {}),
closeAgentChat: vi.fn(async () => {}),
};
render(
<DIProvider gateways={{ agent, system: { pickFile: vi.fn(async () => null) } } as unknown as Gateways}>
<CustomAgentChatView projectId="project-1" agentId="agent-1" agentName="Worker" profile={profile} cwd="/repo" nodeId="node-1" sessionId={null} conversationId="valid-conversation" onSessionId={vi.fn()} onConversationId={vi.fn()} />
</DIProvider>,
);
expect(
await screen.findByText("Envoyez un message pour démarrer la conversation structurée."),
).toBeTruthy();
expect(screen.queryByText("La conversation liée n’a pas pu être restaurée.")).toBeNull();
});
it("treats emptyConversation as a normal fresh conversation", async () => {
const agent = {
launchAgentChat: vi.fn(async () => ({
@ -4944,7 +4971,7 @@ describe("CustomAgentChatView", () => {
);
});
it("does not persist attachments with the restored draft", async () => {
it("keeps attachment drafts in memory across project unmount and return", async () => {
const drafts = new Map<string, string>();
const scope = { projectId: "project-1", layoutId: "layout-a", nodeId: "node-1" };
const agent = {
@ -5023,7 +5050,8 @@ describe("CustomAgentChatView", () => {
renderView();
await screen.findByDisplayValue("draft with file[Image 1]");
expect(screen.queryByText("Fichier joint: not-persisted.png")).toBeNull();
expect(screen.getByText("Fichier joint: not-persisted.png")).toBeTruthy();
expect(screen.getByTestId("attachment-preview-not-persisted.png")).toBeTruthy();
});
it("clears the persisted draft when sending the composer", async () => {