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:
2026-09-11 21:53:56 +02:00
parent cd744b5f18
commit 1a1429f10e
157 changed files with 4531 additions and 3129 deletions

View File

@ -18,6 +18,7 @@
import { useState } from "react";
import { cn } from "@/shared";
import { useI18n } from "@/i18n";
import type { ModelServerStatus } from "@/domain";
import type { AgentLaunchFailure } from "./useAgents";
@ -31,20 +32,21 @@ export interface ModelServerLaunchBadgeProps {
/** Human label + tone for a non-failed lifecycle state. */
function describeStatus(
status: ModelServerStatus,
tr: (french: string, english: string) => string,
): { label: string; tone: string; role?: "status" } | null {
switch (status.state) {
case "notConfigured":
return null; // Nothing to surface for an unmanaged endpoint.
case "probing":
return { label: "vérification du serveur…", tone: "bg-muted/20 text-muted", role: "status" };
return { label: tr("vérification du serveur…", "checking server…"), tone: "bg-muted/20 text-muted", role: "status" };
case "starting":
return { label: "démarrage du serveur…", tone: "bg-warning/20 text-warning", role: "status" };
return { label: tr("démarrage du serveur…", "starting server…"), tone: "bg-warning/20 text-warning", role: "status" };
case "downloading":
return { label: "téléchargement du modèle…", tone: "bg-warning/20 text-warning", role: "status" };
return { label: tr("téléchargement du modèle…", "downloading model…"), tone: "bg-warning/20 text-warning", role: "status" };
case "ready":
return status.reused
? { label: "serveur prêt (réutilisé)", tone: "bg-success/20 text-success" }
: { label: "serveur prêt", tone: "bg-success/20 text-success" };
? { label: tr("serveur prêt (réutilisé)", "server ready (reused)"), tone: "bg-success/20 text-success" }
: { label: tr("serveur prêt", "server ready"), tone: "bg-success/20 text-success" };
case "failed":
// Handled by the failure branch below (richer, actionable).
return null;
@ -55,6 +57,7 @@ export function ModelServerLaunchBadge({
status,
failure,
}: ModelServerLaunchBadgeProps) {
const { tr } = useI18n();
const [showDetail, setShowDetail] = useState(false);
// A launch failure (from `agentLaunchFailed`) or a `failed` status both mean
@ -72,26 +75,26 @@ export function ModelServerLaunchBadge({
<span className="flex min-w-0 flex-wrap items-center gap-2">
<span
role="alert"
aria-label="model server launch failed"
aria-label={tr("échec du lancement du serveur de modèle", "model server launch failed")}
className={cn(
"rounded-full px-2 py-0.5 text-xs font-medium",
"bg-danger/20 text-danger",
)}
>
Échec du lancement : {failed.message}
{tr("Échec du lancement :", "Launch failed:")} {failed.message}
</span>
<button
type="button"
aria-label="toggle launch failure detail"
aria-label={tr("afficher ou masquer le détail de l'échec", "toggle launch failure detail")}
onClick={() => setShowDetail((v) => !v)}
className="text-xs text-muted underline hover:text-content"
>
Détails
{tr("Détails", "Details")}
</button>
{showDetail && (
<span aria-label="launch failure detail" className="text-xs text-faint">
code&nbsp;{failed.code}
{failed.cause ? ` · cause ${failed.cause}` : ""}
<span aria-label={tr("détail de l'échec du lancement", "launch failure detail")} className="text-xs text-faint">
{tr("code", "code")}&nbsp;{failed.code}
{failed.cause ? ` · ${tr("cause", "cause")} ${failed.cause}` : ""}
</span>
)}
</span>
@ -99,13 +102,13 @@ export function ModelServerLaunchBadge({
}
if (!status) return null;
const described = describeStatus(status);
const described = describeStatus(status, tr);
if (!described) return null;
return (
<span
role={described.role}
aria-label="model server status"
aria-label={tr("état du serveur de modèle", "model server status")}
className={cn(
"rounded-full px-2 py-0.5 text-xs font-medium",
described.tone,