Files
office_translator/.impeccable/critique/2026-08-30T19-00-41Z__frontend-src.md
sepehr 50047ea8a2
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m51s
fix(ui): design critique overhaul — a11y, honest metrics, i18n repair (critique 2026-08-30)
P0 a11y: keyboard-accessible dropzone (role/tabIndex/Enter-Space), ARIA
combobox + listbox pattern for language selector, role=switch on glossary
toggle, role=status live region on notifications, aria-labels on password
toggles, visible-on-focus close buttons, RTL logical positioning (start-*).

Trust: third-party Memento promo removed from translate page, sidebar and
all 13 locales; fabricated stats (99.9%, Turbo, computed layout-integrity
bars) replaced with real measurements incl. API estimated remaining time;
silent download failure now surfaces an error notification.

Honesty: fake 100-byte file injections removed (format chips are now
informational); cancel-that-doesn't renamed 'Back to start' with hint;
Enterprise contact placeholder replaced with contact@wordly.art.

i18n: t() no longer returns raw keys (empty string + defaultValue support,
~30 dead || fallbacks now work); ~170 new keys EN+FR across new reviews/
teams namespaces, glossaries context tab, translate monitor, settings,
services, pricing, landing, fileUploader; split-key italic titles replace
lastIndexOf() surgery (zh/ja-safe); key-audit script added 0 missing.

Flow: active job persisted across refresh with polling resume (24h TTL);
client-side recent-jobs history with review links; review page linked from
complete state; settings/services added to dashboard nav; Business/
Enterprise regain glossary access (tier gate unified).

Typeset (sober-tool direction): 7.5-9px labels raised to 10-12px, /30
opacity to /45-/55, uppercase tracking reduced, trust footer legible,
country flags removed from language switcher, localized dates.

Cleanup: 5 orphaned translate components, dead site header/footer,
fossil tailwind.config.js, PipelineStepper, duplicate pill+H1 titles,
two-step confirm for cache clear, dead landing footer links.

Verified: next build exit 0, vitest 9/9, eslint 64 errors = HEAD
(no regression, -3 warnings), detector 4 -> 3 findings.
2026-08-30 21:44:53 +02:00

14 KiB

target, total_score, max_score, na_heuristics, p0_count, p1_count, timestamp, slug
target total_score max_score na_heuristics p0_count p1_count timestamp slug
frontend/src (UI complète Wordly.art) 20 40 1 4 2026-08-30T19-00-41Z frontend-src

Critique de design — Wordly.art (frontend/src)

Method: dual-agent (A: agent_0921d3fd-00f8-424c-a025-1e69fe2d49ff · B: agent_44a564e1-9137-48cf-a6b5-7d78e1042ead)

Design Health Score

# Heuristique Score Problème clé
1 Visibility of System Status 2 Stats fabriquées pendant le traitement (« 99.9% » translate/page.tsx:404, « Intégrité Layout » = 40 + progress*0.55 translate/page.tsx:695) ; estimatedRemaining_seconds récupéré mais jamais affiché (useTranslationSubmit.ts:78) ; échec de téléchargement silencieux (translate/page.tsx:152) ; notifications sans role="status" (notification.tsx:118).
2 Match System / Real World 2 Produit annoncé 12 locales, mais « Source/Cible/Moteur » codés en dur FR (translate/page.tsx:671-728), page reviews 100% FR (reviews/[jobId]/page.tsx), erreurs upload FR (useFileUpload.ts:8-9), erreurs polling EN (useTranslationSubmit.ts:62,102), « Loading… » EN (DashboardLayoutClient.tsx:29).
3 User Control and Freedom 2 « Annuler le processus » (translate/page.tsx:700-705) ne fait qu'un reset local — le job continue côté serveur, crédits consommés ; aucun historique : rafraîchir pendant une traduction perd tout (useTranslationSubmit.ts sans persistance) ; page review inatteignable (unique lien dans un composant mort, TranslationComplete.tsx:168).
4 Consistency and Standards 1 Trois systèmes visuels (éditorial / shadcn auth / admin violet admin/login/page.tsx:52) ; pricing landing 3 plans 7/15/39€ (landing-page.tsx:330-332) vs /pricing 5 plans (pricing/page.tsx:56-192) ; deux systèmes de toasts (useNotification + useToast) ; 5 composants translate/ orphelins jamais importés ; tailwind.config.js:28-35 décrit un thème dark/bleu qui n'existe plus.
5 Error Prevention 2 Boutons Word/Excel/Slides/PDF du dropzone injectent un faux fichier de 100 octets à taille truquée (useFileUpload.ts:83-90) ; « Effacer le cache » sans confirmation (settings/page.tsx:13-28) ; gating incohérent : glossaires réservés à tier === 'pro' strict (glossaries/page.tsx:56) alors que la sidebar traite business/enterprise comme Pro (DashboardSidebar.tsx:20) — clients Business privés de la feature.
6 Recognition Rather Than Recall 3 Nav icône+texte (DashboardSidebar.tsx:42-54), résumé de config visible pendant le processing (translate/page.tsx:647-684). Perd : pages settings/services/reviews orphelines hors nav (constants.ts:10-17), badge tier text-[7px] illisible (DashboardSidebar.tsx:113).
7 Flexibility and Efficiency 1 Aucun raccourci clavier dans le code vivant (le seul handler Enter/Espace est dans le composant mort FileDropZone.tsx:35) ; un fichier à la fois, aucun batch ; pas d'historique ni rejeu de config.
8 Aesthetic and Minimalist Design 2 Micro-typographie : text-[7.5px] (translate/page.tsx:342), text-[8px] (translate/page.tsx:263,336), texte à /30 opacité ; tout en uppercase tracking-widest jusqu'au CTA primaire (translate/page.tsx:322) ; deux blocs promo Memento concurrents sur l'écran de travail.
9 Error Recovery 3 humanFriendlyError() mappe les erreurs brutes vers des messages clairs localisés (translate/page.tsx:90-111) ; état failed conserve le fichier + Réessayer (translate/page.tsx:452-489). Gaps : téléchargement silencieux, messages backend bruts sur register (RegisterForm.tsx:221).
10 Help and Documentation 2 Aide contextuelle bonne sur moteurs/glossaires (ProviderSelector.tsx:276-306, GlossarySelector.tsx:332-334) mais aucune doc vivante : liens footer morts href="#" (landing-page.tsx:436-445), contact Enterprise = mailto:contact@votre-domaine.com (pricing/page.tsx:362) — un placeholder de domaine.
Total 20/40 Acceptable — améliorations significatives nécessaires

Verdict de spécificité design

LLM assessment : Landing — spécifique, parfois remarquable (copy métier : « CTA restera "Air Handling Unit", jamais "Call To Action" » landing.json:35 ; identité crème/Playfair/or globals.css:14-17,112) mais le hero illustre le produit avec une photo Unsplash générique (landing-page.tsx:139) au lieu de montrer l'argument n°1 — la mise en page préservée. Dashboard — l'ossature du flux traduction est écrite pour CE produit, mais rendue interchangeable par la bannière publicitaire tierce « Memento » (translate/page.tsx:748-781, DashboardSidebar.tsx:73-93) et des stats décoratives fausses. Auth — interchangeable (Register variant="premium" RegisterForm.tsx:337 vs Login default LoginForm.tsx:179). Admin — littéralement un autre produit (sidebar compacte EN codée en dur, accent violet, AdminSidebar.tsx:64-84).

Deterministic scan : 4 findings (exit 2). Deux overused-font sur Inter (globals.css:141,147) — le détecteur confirme le diagnostic de la revue par l'autre bout : l'app est portée par Inter, la police par défaut de l'ère IA, tandis que l'identité réelle du produit (Playfair + crème + or) ne vit que sur la landing. Un side-tab (border-l-4 dashboard/profile/page.tsx:469, visible pour les comptes payants) que la revue n'avait pas isolé. Un border-accent-on-rounded (globals.css:215) : faux positif — c'est le pattern canonique d'un spinner de chargement, pas une carte.

Visual overlays : aucun overlay disponible — l'injection mutable s'est heurtée à « Browser is not available in subagent » après deux tentatives réelles. Signal de fallback : scan CLI complet + vérification HTTP statique (/, /login, /pricing → 200, avec réserve : un port-forward de l'éditeurCursor sur 127.0.0.1:3000 a probablement répondu à la place du serveur local).

Impression générale

La fondation est saine — machine à états propre, gestion d'erreurs humaine, copy métier différenciante — mais l'exécution sabote la confiance au moment exact où un professionnel en a besoin : publicité tierce dans l'écran de travail, statistiques fabriquées, parcours primaire inaccessible au clavier, i18n de façade. La plus grande opportunité : transformer la fin de job — déjà le meilleur moment émotionnel du produit — en preuve produit (lien vers la review segment par segment qui existe déjà), et évacuer le bruit qui la noie.

Ce qui fonctionne

  1. La machine à états du flux de traduction (translate/page.tsx:172-181) : upload → config → processing → complete/failed se relaient proprement dans un seul écran, une intention à la fois, zéro navigation. C'est la bonne architecture pour ce produit.
  2. humanFriendlyError() (translate/page.tsx:90-111) : pattern-matching des erreurs brutes (« quota », « 429 », « timed out ») vers des messages clairs localisés, avec état failed qui conserve le fichier et propose Réessayer. La majorité des SaaS affichent le message brut — ici c'est pensé.
  3. La copy landing FR comme différenciateur (« Les autres traducteurs détruisent votre mise en page. Pas nous. » landing.json:43) : c'est du domaine, pas du template.

Problèmes prioritaires

  1. [P0] Le dépôt de fichier est impossible au clavier — et le composant accessible est du code mort. Le dropzone vivant est un div avec onClick sans tabIndex, role ni handler clavier (translate/page.tsx:256-261) ; l'utilisateur clavier seul ne peut pas faire la tâche primaire. Le handler Enter/Espace correct existe dans FileDropZone.tsx:35, jamais importé. Le combobox de langue custom n'a ni role="combobox" ni flèches ni Échap (LanguageSelector.tsx:66-113) ; le switch glossaire n'a pas de role="switch" (GlossarySelector.tsx:286-312) ; contrastes massivement sous 4.5:1 (text-brand-dark/30 sur crème, translate/page.tsx:263,336,342) ; actions révélées au survol uniquement (GlossaryCard.tsx:65,75). → Fix : brancher FileDropZone.tsx ou rendre le dropzone focusable, Select shadcn pour les langues, role="switch"/role="status" manquants. Commandes : $impeccable audit (quantifier a11y) puis $impeccable harden (corriger).

  2. [P1] Publicité tierce « Memento » greffée dans l'outil de travail. Deux blocs promo vers un produit externe (sidebar DashboardSidebar.tsx:73-93 + bannière pleine largeur à double CTA translate/page.tsx:748-781) sur l'écran du cœur de métier, pour un SaaS qui vend la confidentialité documentaire — le message « Rétention zéro » en 7.5px juste au-dessus (translate/page.tsx:342-349) est contredit par la promo en dessous. Pill « Ecosystème Wordly » codé en dur non traduit (translate/page.tsx:764). → Fix : sortir la promo du flux de traduction ; au maximum un emplacement unique post-tâche, discret, désactivable, traduit. Commande : $impeccable distill.

  3. [P1] Les boutons « Word / Excel / Slides / PDF » injectent de faux fichiers. 4 boutons ressemblant à des filtres de format chargent un blob de 100 octets avec taille falsifiée (useFileUpload.ts:83-90, noms factices useFileUpload.ts:74-77). L'utilisateur croit sélectionner un type ; il obtient un fichier qui échouera à la soumission — piège à erreurs garanti. → Fix : supprimer, ou transformer en démo explicite (« Essayer avec un exemple ») sur un flux séparé. Commandes : $impeccable harden + $impeccable clarify.

  4. [P1] L'i18n est une façade : la moitié des strings visibles sont codées en dur, et le pattern de fallback est mort. Un utilisateur EN voit « Source/Cible » (translate/page.tsx:671-681), des pages entières FR (reviews, teams, glossaries), des erreurs FR (« Erreur serveur » pricing/page.tsx:390,434) et EN (« Loading... »). Pire : t() retourne la clé brute si absente (i18n.tsx:161), donc tous les t('key') || 'fallback' (~30 occurrences, ex. GlossarySelector.tsx:277) n'appliquent jamais leur fallback — clé manquante = translate.glossary.title affiché à l'écran. Les découpes lastIndexOf(' ') pour l'italique du dernier mot (translate/page.tsx:206-208) casseront en zh/ja. → Fix : t() retourne une chaîne vide sur clé manquante, auditer les ||, extraire les strings codées en dur. Commande : $impeccable harden.

  5. [P1] Features fantômes et perte de contrôle : review inatteignable, pages orphelines, annulation factice, pas d'historique. La page de review segment par segment (reviews/[jobId]/page.tsx) n'est liée nulle part dans l'UI vivante (unique lien dans TranslationComplete.tsx:168, composant jamais importé) ; settings et services absents de la nav (constants.ts:10-17) ; « Annuler » ne fait qu'un reset local pendant que crédits partent en consommation ; rafraîchir pendant un traitement détruit tout état (aucun jobId persisté) ; clients Business/Enterprise privés de glossaires (glossaries/page.tsx:56). → Fix : persister le jobId avec reprise du polling, historique des jobs récents, lien complet → /dashboard/reviews/[jobId], vraie API d'annulation ou renommage honnête, unifier isPro. Commande : $impeccable shape.

Red flags personas

Alex (utilisateur expert pressé) : aucun raccourci clavier vivant ; un fichier à la fois, aucun batch (rédhibitoire pour 20 docs/semaine) ; aucun historique/rejeu — chaque session repart de zéro ; les stats « 99.9% précision / Turbo » (translate/page.tsx:404-405) détruisent sa confiance ; « Annuler » qui n'annule pas — il le découvrira à la facture.

Sam (accessibilité) : bloqué dès l'étape 1 — dropzone inaccessible clavier (translate/page.tsx:256-261) ; contraste ~1.8:1 sur des dizaines de libellés (text-brand-dark/30) ; tailles 7.5-9px uppercase tracking-widest illisibles ; notifications muettes pour lecteur d'écran (notification.tsx:118) ; échec de téléchargement sans aucun rendu (translate/page.tsx:152) ; actions invisibles hors hover (GlossaryCard.tsx:65,75, notification.tsx:181) ; RTL : dir bien posé mais barres/badges en left-0/right-0 physiques (translate/page.tsx:387).

Jordan (premier usage) : premier écran correct (dropzone central « Glissez-déposez ici ») puis 4 boutons formats qui ne font pas ce qu'ils annoncent ; jargon non défini (« Standard » vs « IA contextuelle », « Intégrité Layout 100% SÉCURISÉ » translate.json:85-86) ; aucun doc vivante (footer « Documentation » mort landing-page.tsx:436) ; le premier paiement peut aboutir sur mailto:contact@votre-domaine.com (pricing/page.tsx:362).

Observations mineures

  • Titre dupliqué pill + H1 sur settings et services (settings/page.tsx:40-42, services/page.tsx:56-58).
  • Dates glossaires fr-FR en dur (glossaries/page.tsx:412) contre formatDate() locale existante (i18n.tsx:108).
  • Prix IA en $ sur une page facturée en (pricing/page.tsx:870,885).
  • site-header.tsx/site-footer.tsx importés nulle part ; le header fantôme pointe « Connexion » vers /dashboard (site-header.tsx:31).
  • Mention CGU non cliquable (RegisterForm.tsx:366-370).
  • Drapeaux pays pour des langues (ar→🇸🇦, zh→🇨🇳 language-switcher.tsx:24-25) — discutable pour un outil pro multilingue ; 13 locales à plat dans le switcher.
  • Bon réflexe rare : prefers-reduced-motion et prefers-contrast gérés (globals.css:237-254) ; skeleton pricing anti-flash (pricing/page.tsx:221-259).
  • Le switch « Traduire les images » désactivé rend toute la carte grisée sans explication avant interaction (translate/page.tsx:556-585).

Questions à considérer

  1. Si un prospect demande « prouvez-moi que ma mise en page survit », quelle partie de l'écran répond ? Aucune aujourd'hui — le hero montre une photo Unsplash de bureau (landing-page.tsx:139) alors que le moteur produit déjà un avant/après à chaque job. Un seul DOCX tri-langue côte à côte vaudrait plus que les 6 cartes de LayoutFeatures.
  2. Pourquoi l'état « traduction terminée » — le meilleur moment émotionnel — se termine-t-il sur un bouton fantôme de 10px alors que la page de review segment par segment existe déjà ?
  3. Qu'est-ce qui coûte le plus : les revenus Memento, ou la crédibilité « documents confidentiels » que la promo détruit à chaque écran ?