Files
IdeA/frontend/src/app/App.tsx
Blomios 1a1429f10e 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.
2026-09-11 21:53:56 +02:00

131 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Root component. Smoke-tests the full hexagonal pipeline by calling `health`
* through the {@link SystemGateway} (never `invoke()` directly) and listening
* for relayed domain events.
*/
import { useEffect, useState } from "react";
import type { DomainEvent, HealthReport } from "@/domain";
import { ProjectsView } from "@/features/projects";
import { FirstRunWizard } from "@/features/first-run";
import { AppExitConfirmDialog } from "@/features/appExit";
import { PluginRuntimeProvider } from "@/features/plugins";
import { Panel, Spinner, Toolbar } from "@/shared";
import { useGateways, shouldUseMock } from "./di";
import { useI18n } from "@/i18n";
export function App() {
const { tr } = useI18n();
const { system, profile } = useGateways();
const [health, setHealth] = useState<HealthReport | null>(null);
const [error, setError] = useState<string | null>(null);
const [events, setEvents] = useState<DomainEvent[]>([]);
// First-run gating: null while loading, then true (wizard) / false (normal).
const [firstRun, setFirstRun] = useState<boolean | null>(null);
useEffect(() => {
let unsub: (() => void) | undefined;
let cancelled = false;
system
.onDomainEvent((e) => setEvents((prev) => [...prev, e]))
.then((u) => {
if (cancelled) u();
else unsub = u;
})
.catch(() => {
/* event relay unavailable in this environment; ignore for smoke test */
});
system
.health("ui-boot")
.then((report) => setHealth(report))
.catch((e: unknown) => setError(describeError(e)));
return () => {
cancelled = true;
unsub?.();
};
}, [system]);
useEffect(() => {
let cancelled = false;
profile
.firstRunState()
.then((s) => {
if (!cancelled) setFirstRun(s.isFirstRun);
})
.catch(() => {
// First-run state unavailable: behave as a normal (non-first) run.
if (!cancelled) setFirstRun(false);
});
return () => {
cancelled = true;
};
}, [profile]);
return (
<PluginRuntimeProvider>
<div className="flex h-full flex-col bg-canvas text-content">
{/* ── Header ── */}
<header className="flex shrink-0 items-center justify-between border-b border-border px-6 py-3">
<div className="flex items-baseline gap-2">
<h1 className="text-lg font-semibold tracking-tight">IdeA</h1>
<span className="rounded-md bg-raised px-1.5 py-0.5 text-[0.65rem] font-medium uppercase text-muted">
{shouldUseMock() ? "mock" : "tauri"}
</span>
</div>
<Toolbar justify="end">
<span className="text-xs text-faint">
{health ? (
<>
{tr("backend", "backend")} <span className="text-success">●</span> v{health.version} ·{" "}
{events.length} {tr("événements", "events")}
</>
) : error ? (
<span className="text-danger">{tr("backend indisponible", "backend unavailable")}</span>
) : (
<span className="inline-flex items-center gap-1.5">
<Spinner size={12} /> {tr("vérification…", "checking…")}
</span>
)}
</span>
</Toolbar>
</header>
{/* ── Body ── */}
<div className="flex flex-1 flex-col overflow-hidden">
{error && (
<Panel className="mx-4 mt-4 border-danger/40">
<p className="text-sm text-danger">{tr("Erreur :", "Error:")} {error}</p>
</Panel>
)}
{firstRun ? (
// First launch: the wizard takes over the area, in a scrollable column
// (it can be taller than the viewport).
<div className="flex flex-1 justify-center overflow-y-auto p-6">
<div className="w-full max-w-xl">
<FirstRunWizard onDone={() => setFirstRun(false)} />
</div>
</div>
) : (
// ProjectsView owns the full remaining area (its own IDE layout,
// single menu bar, and the AI Profiles view switch — ticket #16).
<ProjectsView />
)}
</div>
</div>
<AppExitConfirmDialog />
</PluginRuntimeProvider>
);
}
function describeError(e: unknown): string {
if (e && typeof e === "object" && "message" in e) {
return String((e as { message: unknown }).message);
}
return String(e);
}