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.
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
- 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. 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é.- 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
-
[P0] Le dépôt de fichier est impossible au clavier — et le composant accessible est du code mort. Le dropzone vivant est un
divaveconClicksanstabIndex,roleni 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 dansFileDropZone.tsx:35, jamais importé. Le combobox de langue custom n'a nirole="combobox"ni flèches ni Échap (LanguageSelector.tsx:66-113) ; le switch glossaire n'a pas derole="switch"(GlossarySelector.tsx:286-312) ; contrastes massivement sous 4.5:1 (text-brand-dark/30sur crème,translate/page.tsx:263,336,342) ; actions révélées au survol uniquement (GlossaryCard.tsx:65,75). → Fix : brancherFileDropZone.tsxou rendre le dropzone focusable,Selectshadcn pour les langues,role="switch"/role="status"manquants. Commandes :$impeccable audit(quantifier a11y) puis$impeccable harden(corriger). -
[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 CTAtranslate/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. -
[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 facticesuseFileUpload.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. -
[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 lest('key') || 'fallback'(~30 occurrences, ex.GlossarySelector.tsx:277) n'appliquent jamais leur fallback — clé manquante =translate.glossary.titleaffiché à l'écran. Les découpeslastIndexOf(' ')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. -
[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 dansTranslationComplete.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, unifierisPro. 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-FRen dur (glossaries/page.tsx:412) contreformatDate()locale existante (i18n.tsx:108). - Prix IA en
$sur une page facturée en€(pricing/page.tsx:870,885). site-header.tsx/site-footer.tsximporté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-motionetprefers-contrastgé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
- 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. - 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à ?
- Qu'est-ce qui coûte le plus : les revenus Memento, ou la crédibilité « documents confidentiels » que la promo détruit à chaque écran ?