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:
@ -14,6 +14,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { Button, Input, cn } from "@/shared";
|
||||
import { useI18n } from "@/i18n";
|
||||
import type { AgentLimitState } from "./useAgents";
|
||||
|
||||
/**
|
||||
@ -75,6 +76,7 @@ export function AgentLimitBadge({
|
||||
onSetResumeAt,
|
||||
busy = false,
|
||||
}: AgentLimitBadgeProps) {
|
||||
const { tr } = useI18n();
|
||||
const { limitedUntil, resumeFireAt, suspected } = state;
|
||||
|
||||
// Tick a local clock once per second only while a resume countdown is armed,
|
||||
@ -106,17 +108,17 @@ export function AgentLimitBadge({
|
||||
|
||||
const limitLabel =
|
||||
limitedUntil !== undefined
|
||||
? `limité jusqu'à ${formatResetTime(limitedUntil)}`
|
||||
: "limité";
|
||||
? tr(`limité jusqu'à ${formatResetTime(limitedUntil)}`, `limited until ${formatResetTime(limitedUntil)}`)
|
||||
: tr("limité", "limited");
|
||||
|
||||
return (
|
||||
<span className="flex min-w-0 flex-wrap items-center gap-2">
|
||||
<span
|
||||
aria-label="session limit"
|
||||
aria-label={tr("limite de session", "session limit")}
|
||||
title={
|
||||
suspected
|
||||
? "Limite de session détectée sans heure fiable"
|
||||
: "Limite de session atteinte"
|
||||
? tr("Limite de session détectée sans heure fiable", "Session limit detected without a reliable time")
|
||||
: tr("Limite de session atteinte", "Session limit reached")
|
||||
}
|
||||
className={cn(
|
||||
"rounded-full px-2 py-0.5 text-xs font-medium",
|
||||
@ -134,14 +136,14 @@ export function AgentLimitBadge({
|
||||
<form
|
||||
onSubmit={handleSubmitResumeTime}
|
||||
className="flex items-center gap-2"
|
||||
aria-label="resume time form"
|
||||
aria-label={tr("formulaire d'heure de reprise", "resume time form")}
|
||||
>
|
||||
<span className="text-xs text-muted" role="note">
|
||||
heure inconnue
|
||||
{tr("heure inconnue", "unknown time")}
|
||||
</span>
|
||||
<Input
|
||||
type="time"
|
||||
aria-label="resume time"
|
||||
aria-label={tr("heure de reprise", "resume time")}
|
||||
value={resumeTime}
|
||||
disabled={busy}
|
||||
onChange={(e) => setResumeTime(e.target.value)}
|
||||
@ -151,27 +153,27 @@ export function AgentLimitBadge({
|
||||
type="submit"
|
||||
size="sm"
|
||||
variant="primary"
|
||||
aria-label="schedule resume"
|
||||
aria-label={tr("programmer la reprise", "schedule resume")}
|
||||
disabled={busy || resumeTime.trim() === ""}
|
||||
>
|
||||
Programmer la reprise
|
||||
{tr("Programmer la reprise", "Schedule resume")}
|
||||
</Button>
|
||||
</form>
|
||||
)}
|
||||
|
||||
{resumeFireAt !== undefined && (
|
||||
<span className="flex items-center gap-2">
|
||||
<span aria-label="resume countdown" className="text-xs text-muted">
|
||||
reprise dans {formatCountdown(resumeFireAt - now)}
|
||||
<span aria-label={tr("compte à rebours de reprise", "resume countdown")} className="text-xs text-muted">
|
||||
{tr("reprise dans", "resuming in")} {formatCountdown(resumeFireAt - now)}
|
||||
</span>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
aria-label="cancel resume"
|
||||
aria-label={tr("annuler la reprise", "cancel resume")}
|
||||
disabled={busy}
|
||||
onClick={onCancelResume}
|
||||
>
|
||||
Annuler la reprise
|
||||
{tr("Annuler la reprise", "Cancel resume")}
|
||||
</Button>
|
||||
</span>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user