feat(domain,application,infrastructure,backend,web-server,app-tauri,frontend): lot utilisateur #325 #326 — icônes projet avec watermark CLI custom et localisation FR/EN (QA verte)
Lot en un seul commit: les deux features partagent les mêmes contrats (domain/settings.rs, DTO, commandes Tauri/HTTP, web-server) et les mêmes surfaces UI — la séparation par fichiers était impossible sans chirurgie de hunks risquant des états intermédiaires non compilables (convention lot des batches #310-#313 et #315). #325 — icône configurable par projet, watermark CLI custom: - domaine: ProjectIconSet (devicon, simple-icons, lucide, idea), ProjectIconRef validé (nom ASCII 1..=64, -_. internes), champ icon optionnel sur ProjectSettings; sérialisation camelCase et try_from strict - application: UpdateProjectSettings.execute accepte un patch theme_color OU icon (absent = inchangé, null = défaut produit), tests in-dependants avec store mémoire - backend/app-tauri/web-server: ProjectIconRefDto, UpdateProjectSettingsRequestDto avec icon optionnel (désérialisation additive), commandes update_project_settings alignées Tauri/HTTP, tests DTO et HTTP (défaut null, set, clear, nom invalide rejeté) - frontend: catalogue projectIcons (recherche EN), ProjectIconPicker en grille multi-lignes dans Paramètres projet, affichage de l'icône dans ProjectTabs et watermark grand format à faible opacité en fond de CLI custom; ports ProjectSettings.icon, Partial<ProjectSettings>, adapters tauri/mock/http et tests alignés #326 — localisation FR/EN et traduction des textes IdeA: - domaine: IdeaLanguage (fr | en), champ language optionnel sur IdeaUserSettings (absence = défaut produit, série additive validée) - backend/web-server: UserSettingsDto.language, update_user_settings accepte language, tests HTTP alignés; fixtures structured_launch_d3 mises à jour - frontend: i18n/ (catalogue FR/EN, I18nProvider, scan anti-régression des sources visibles), GeneralSettings avec sélecteur de langue dans les paramètres, setupI18n dans vitest; migration de l'ensemble des textes hardcodés vers le catalogue sur toutes les surfaces (agents, layout, projets, tickets, sprints, web, plugins, permissions, git, templates, skills, mémoire, model servers, embedder, first-run, devices, scheduled tasks, workstate, dialogs), FR conservé par défaut; recherche d'icônes #325 en anglais conformément au ticket QA verte sur feature/batch-325-326-project-icons-localisation. Aucun push.
This commit is contained in:
@ -18,6 +18,7 @@ import {
|
||||
WebSession,
|
||||
} from "@/adapters/http";
|
||||
import { WebApp } from "./WebApp";
|
||||
import { I18nProvider } from "@/i18n";
|
||||
|
||||
afterEach(() => setWebLiveClient(null));
|
||||
|
||||
@ -53,7 +54,7 @@ function renderPaired(gateways: Gateways) {
|
||||
session.markPaired();
|
||||
return render(
|
||||
<DIProvider gateways={gateways}>
|
||||
<WebApp session={session} />
|
||||
<I18nProvider><WebApp session={session} /></I18nProvider>
|
||||
</DIProvider>,
|
||||
);
|
||||
}
|
||||
@ -93,7 +94,7 @@ describe("WebWorkspace live surfaces (F5)", () => {
|
||||
|
||||
fireEvent.click(await screen.findByText("Demo"));
|
||||
const panel = await screen.findByTestId("web-workstate");
|
||||
expect(panel.textContent).toContain("Idle");
|
||||
expect(panel.textContent).toContain("Inactif");
|
||||
|
||||
// The agent goes busy server-side; the read-model now reflects it…
|
||||
(gateways.workState as MockWorkStateGateway)._setProjectWorkState(
|
||||
@ -106,24 +107,24 @@ describe("WebWorkspace live surfaces (F5)", () => {
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("web-workstate").textContent).toContain("Busy"),
|
||||
expect(screen.getByTestId("web-workstate").textContent).toContain("Occupé"),
|
||||
);
|
||||
});
|
||||
|
||||
it("renders background tasks and wires cancel through the gateway", async () => {
|
||||
it("tâches de fond de renders and wires cancel through the gateway", async () => {
|
||||
const task = backgroundTask("bt-1", "a1");
|
||||
const { gateways } = await seeded(state([{ ...AGENT_IDLE, backgroundTasks: [task] }]));
|
||||
const cancelSpy = vi.spyOn(gateways.workState, "cancelBackgroundTask");
|
||||
await openLivePanel(gateways);
|
||||
|
||||
fireEvent.click(
|
||||
await screen.findByRole("button", { name: "Afficher les background tasks de Archi" }),
|
||||
await screen.findByRole("button", { name: "Afficher les tâches de fond de Archi" }),
|
||||
);
|
||||
const tasks = await screen.findByLabelText("Archi background tasks");
|
||||
expect(tasks.textContent).toContain("Running");
|
||||
const tasks = await screen.findByLabelText("Tâches de fond de Archi");
|
||||
expect(tasks.textContent).toContain("En cours");
|
||||
expect(tasks.textContent).toContain("shell");
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Annuler" }));
|
||||
await waitFor(() => expect(cancelSpy).toHaveBeenCalledWith("bt-1"));
|
||||
});
|
||||
|
||||
@ -135,33 +136,33 @@ describe("WebWorkspace live surfaces (F5)", () => {
|
||||
await openLivePanel(gateways);
|
||||
|
||||
const archiToggle = await screen.findByRole("button", {
|
||||
name: "Afficher les background tasks de Archi",
|
||||
name: "Afficher les tâches de fond de Archi",
|
||||
});
|
||||
const frontToggle = await screen.findByRole("button", {
|
||||
name: "Afficher les background tasks de Front",
|
||||
name: "Afficher les tâches de fond de Front",
|
||||
});
|
||||
expect(archiToggle.getAttribute("aria-expanded")).toBe("false");
|
||||
expect(frontToggle.getAttribute("aria-expanded")).toBe("false");
|
||||
expect(screen.queryByLabelText("Archi background tasks")).toBeNull();
|
||||
expect(screen.queryByLabelText("Front background tasks")).toBeNull();
|
||||
expect(screen.queryByLabelText("Tâches de fond de Archi")).toBeNull();
|
||||
expect(screen.queryByLabelText("Tâches de fond de Front")).toBeNull();
|
||||
|
||||
fireEvent.click(archiToggle);
|
||||
expect(
|
||||
screen
|
||||
.getByRole("button", { name: "Masquer les background tasks de Archi" })
|
||||
.getByRole("button", { name: "Masquer les tâches de fond de Archi" })
|
||||
.getAttribute("aria-expanded"),
|
||||
).toBe("true");
|
||||
expect(
|
||||
screen
|
||||
.getByRole("button", { name: "Afficher les background tasks de Front" })
|
||||
.getByRole("button", { name: "Afficher les tâches de fond de Front" })
|
||||
.getAttribute("aria-expanded"),
|
||||
).toBe("false");
|
||||
expect(await screen.findByLabelText("Archi background tasks")).toBeTruthy();
|
||||
expect(screen.queryByLabelText("Front background tasks")).toBeNull();
|
||||
expect(await screen.findByLabelText("Tâches de fond de Archi")).toBeTruthy();
|
||||
expect(screen.queryByLabelText("Tâches de fond de Front")).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Afficher les background tasks de Front" }));
|
||||
expect(screen.getByLabelText("Archi background tasks")).toBeTruthy();
|
||||
expect(screen.getByLabelText("Front background tasks")).toBeTruthy();
|
||||
fireEvent.click(screen.getByRole("button", { name: "Afficher les tâches de fond de Front" }));
|
||||
expect(screen.getByLabelText("Tâches de fond de Archi")).toBeTruthy();
|
||||
expect(screen.getByLabelText("Tâches de fond de Front")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("updates task counters without changing the current expanded state", async () => {
|
||||
@ -171,11 +172,11 @@ describe("WebWorkspace live surfaces (F5)", () => {
|
||||
await openLivePanel(gateways);
|
||||
|
||||
const collapsedToggle = await screen.findByRole("button", {
|
||||
name: "Afficher les background tasks de Archi",
|
||||
name: "Afficher les tâches de fond de Archi",
|
||||
});
|
||||
expect(collapsedToggle.getAttribute("aria-expanded")).toBe("false");
|
||||
expect(collapsedToggle.textContent).toContain("1");
|
||||
expect(screen.queryByLabelText("Archi background tasks")).toBeNull();
|
||||
expect(screen.queryByLabelText("Tâches de fond de Archi")).toBeNull();
|
||||
|
||||
(gateways.workState as MockWorkStateGateway)._setProjectWorkState(
|
||||
projectId,
|
||||
@ -198,18 +199,18 @@ describe("WebWorkspace live surfaces (F5)", () => {
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Afficher les background tasks de Archi" }).textContent,
|
||||
screen.getByRole("button", { name: "Afficher les tâches de fond de Archi" }).textContent,
|
||||
).toContain("2"),
|
||||
);
|
||||
expect(
|
||||
screen
|
||||
.getByRole("button", { name: "Afficher les background tasks de Archi" })
|
||||
.getByRole("button", { name: "Afficher les tâches de fond de Archi" })
|
||||
.getAttribute("aria-expanded"),
|
||||
).toBe("false");
|
||||
expect(screen.queryByLabelText("Archi background tasks")).toBeNull();
|
||||
expect(screen.queryByLabelText("Tâches de fond de Archi")).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Afficher les background tasks de Archi" }));
|
||||
expect(await screen.findByLabelText("Archi background tasks")).toBeTruthy();
|
||||
fireEvent.click(screen.getByRole("button", { name: "Afficher les tâches de fond de Archi" }));
|
||||
expect(await screen.findByLabelText("Tâches de fond de Archi")).toBeTruthy();
|
||||
|
||||
(gateways.workState as MockWorkStateGateway)._setProjectWorkState(
|
||||
projectId,
|
||||
@ -227,15 +228,15 @@ describe("WebWorkspace live surfaces (F5)", () => {
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Masquer les background tasks de Archi" }).textContent,
|
||||
screen.getByRole("button", { name: "Masquer les tâches de fond de Archi" }).textContent,
|
||||
).toContain("1"),
|
||||
);
|
||||
expect(
|
||||
screen
|
||||
.getByRole("button", { name: "Masquer les background tasks de Archi" })
|
||||
.getByRole("button", { name: "Masquer les tâches de fond de Archi" })
|
||||
.getAttribute("aria-expanded"),
|
||||
).toBe("true");
|
||||
expect(screen.getByLabelText("Archi background tasks")).toBeTruthy();
|
||||
expect(screen.getByLabelText("Tâches de fond de Archi")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("hides and resets an open background task group when its task count reaches zero", async () => {
|
||||
@ -244,9 +245,9 @@ describe("WebWorkspace live surfaces (F5)", () => {
|
||||
]));
|
||||
await openLivePanel(gateways);
|
||||
fireEvent.click(await screen.findByRole("button", {
|
||||
name: "Afficher les background tasks de Archi",
|
||||
name: "Afficher les tâches de fond de Archi",
|
||||
}));
|
||||
expect(await screen.findByLabelText("Archi background tasks")).toBeTruthy();
|
||||
expect(await screen.findByLabelText("Tâches de fond de Archi")).toBeTruthy();
|
||||
|
||||
(gateways.workState as MockWorkStateGateway)._setProjectWorkState(
|
||||
projectId,
|
||||
@ -263,9 +264,9 @@ describe("WebWorkspace live surfaces (F5)", () => {
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByRole("button", { name: /background tasks de Archi/ })).toBeNull(),
|
||||
expect(screen.queryByRole("button", { name: /tâches de fond de Archi/ })).toBeNull(),
|
||||
);
|
||||
expect(screen.queryByLabelText("Archi background tasks")).toBeNull();
|
||||
expect(screen.queryByLabelText("Tâches de fond de Archi")).toBeNull();
|
||||
|
||||
(gateways.workState as MockWorkStateGateway)._setProjectWorkState(
|
||||
projectId,
|
||||
@ -284,11 +285,11 @@ describe("WebWorkspace live surfaces (F5)", () => {
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen
|
||||
.getByRole("button", { name: "Afficher les background tasks de Archi" })
|
||||
.getByRole("button", { name: "Afficher les tâches de fond de Archi" })
|
||||
.getAttribute("aria-expanded"),
|
||||
).toBe("false"),
|
||||
);
|
||||
expect(screen.queryByLabelText("Archi background tasks")).toBeNull();
|
||||
expect(screen.queryByLabelText("Tâches de fond de Archi")).toBeNull();
|
||||
});
|
||||
|
||||
it("re-synchronises the read-model when the WS reconnects", async () => {
|
||||
|
||||
Reference in New Issue
Block a user