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:
@ -19,13 +19,18 @@
|
||||
* and crucially no `openProject()` — opening a project is the main window's job.
|
||||
*/
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import type { CustomPluginLayoutCell } from "@/domain";
|
||||
import type { FocusedProject } from "@/ports";
|
||||
import { Panel } from "@/shared";
|
||||
import { PANEL_TITLE, type PanelId } from "@/features/projects";
|
||||
import { ViewPanelBody, type ViewPanelId } from "@/features/projects";
|
||||
import {
|
||||
onProjectThemeChanged,
|
||||
projectThemeStyle,
|
||||
validThemeColor,
|
||||
} from "@/features/projects/projectTheme";
|
||||
import {
|
||||
PluginLayoutCellView,
|
||||
PluginRuntimeProvider,
|
||||
@ -98,7 +103,8 @@ export function parseViewWindowParams(
|
||||
};
|
||||
}
|
||||
|
||||
const panel = params.get("panel");
|
||||
const rawPanel = params.get("panel");
|
||||
const panel = rawPanel === "context" ? "projectSettings" : rawPanel;
|
||||
if (!panel || !DETACHABLE.has(panel)) return null;
|
||||
return { kind: "panel", panel: panel as ViewPanelId };
|
||||
}
|
||||
@ -108,12 +114,14 @@ export interface ViewWindowProps {
|
||||
}
|
||||
|
||||
export function ViewWindow({ panel }: ViewWindowProps) {
|
||||
const { focusedProject } = useGateways();
|
||||
const { focusedProject, project } = useGateways();
|
||||
// The focused project this window follows. `undefined` = not yet resolved
|
||||
// (initial read in flight); `null` = resolved, no project focused.
|
||||
const [focus, setFocus] = useState<FocusedProject | null | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const [themeColors, setThemeColors] = useState<Record<string, string | null>>({});
|
||||
const themeRevisionRef = useRef<Record<string, number>>({});
|
||||
|
||||
// Read the current focus at mount, then track changes. Never opens a project:
|
||||
// the window is a pure follower of the main window's focus (#47).
|
||||
@ -142,8 +150,42 @@ export function ViewWindow({ panel }: ViewWindowProps) {
|
||||
};
|
||||
}, [focusedProject]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!focus) return;
|
||||
let cancelled = false;
|
||||
const revision = themeRevisionRef.current[focus.id] ?? 0;
|
||||
void project.readProjectSettings(focus.id).then((settings) => {
|
||||
if (
|
||||
!cancelled &&
|
||||
(themeRevisionRef.current[focus.id] ?? 0) === revision
|
||||
) {
|
||||
setThemeColors((current) => ({
|
||||
...current,
|
||||
[focus.id]: validThemeColor(settings.themeColor),
|
||||
}));
|
||||
}
|
||||
}).catch(() => {
|
||||
// Keep the last known colour; settings content owns its own retry state.
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [focus?.id, project]);
|
||||
|
||||
useEffect(() => onProjectThemeChanged(({ projectId, themeColor }) => {
|
||||
themeRevisionRef.current[projectId] =
|
||||
(themeRevisionRef.current[projectId] ?? 0) + 1;
|
||||
setThemeColors((current) => ({
|
||||
...current,
|
||||
[projectId]: validThemeColor(themeColor),
|
||||
}));
|
||||
}), []);
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col bg-canvas text-content">
|
||||
<div
|
||||
className="flex h-full flex-col bg-canvas text-content"
|
||||
style={projectThemeStyle(focus ? themeColors[focus.id] : null)}
|
||||
>
|
||||
<header className="flex shrink-0 items-center gap-2 border-b border-border px-4 py-2.5">
|
||||
<h1 className="text-sm font-semibold text-content">
|
||||
{PANEL_TITLE[panel]}
|
||||
|
||||
Reference in New Issue
Block a user