Page générée le 09 July 2026 à 20:55, mise à jour le 10 juillet 2026 (heure locale du dépôt). Agrégation brute des rapports d’audit techniques produits pendant le développement, classés chronologiquement pour suivre l’évolution du projet et les corrections apportées au fil du temps.
⚠️ Ce sont des notes d’ingénierie interne, publiées ici sans filtrage : bugs connus avec références de code précises, limitations, dette technique et pistes de refonte. Elles s’adressent en premier lieu aux développeurs et contributeurs curieux de l’état réel du projet — pas un résumé marketing. Quand un point a été corrigé depuis la rédaction d’un rapport, une note ✅ Statut l’indique juste après son titre.
input text/input keyevent contournent le suivi interne du clavier) et un bug applicatif réel identifié (MIN_WORD_LENGTH = 3 dans CreoleDictionaryWithUsage.kt exclut les mots courts de wordsDiscovered, même committés par suggestion). Bloqué avant le run complet par une disparition intermittente du clavier virtuel après les cycles de bind/unbind du service IME déclenchés par chaque checkpoint (onCreate() du service jamais rappelé malgré un bind OS réussi) ; une réinstallation complète de l’app a débloqué la situation une fois, sans garantie de reproductibilité. Suite (même jour) : cause racine identifiée dans le code, le commit f22bee3 avait supprimé l’appel à super.onFinishInput() dans KreyolInputMethodServiceRefactored.kt, ce qui cassait le cycle de vie du service après un changement d’app ; correctif appliqué (restauration de l’appel), build et tests au vert, puis vérification visuelle réussie sur machine reposée. Run complet de 600 mots ensuite mené à bien (23 min, zéro crash, 12 checkpoints), qui valide le correctif sur 12 cycles de changement d’app consécutifs. Deux enseignements : (1) la découverte de mots plafonne vers 180 (règle count == 1 de wordsDiscovered + suggestions concentrées sur le vocabulaire fréquent), un seul des 3 niveaux attendus se débloque, à revoir pour la motivation au-delà de Ti moun ; (2) le dialogue de célébration se déclenche bien (préférence last_celebrated_level_index passée de 0 à 1). Le flux de partage, rejoué manuellement ensuite, a révélé et permis de corriger un bug réel : l’image de la carte ne s’attachait pas sous Android 14 (shareLevelCard() passait l’URI via EXTRA_STREAM sans ClipData, d’où un refus de permission FileProvider et un repli en SMS texte) ; correctif ClipData.newUri(...) appliqué et vérifié (l’image s’attache désormais en MMS). Détail complet dans rapport_simulation_partage_niveaux_2026-07-13.md à la racine du dépôt.🗓️ Date du rapport : 06 novembre 2025
| Longueur | Nombre de mots | Pourcentage |
|---|---|---|
| 2 lettres | 82 | 2.2% █ |
| 3 lettres | 303 | 8.2% ████ |
| 4 lettres | 524 | 14.2% ███████ |
| 5 lettres | 657 | 17.9% ████████ |
| 6 lettres | 644 | 17.5% ████████ |
| 7 lettres | 487 | 13.2% ██████ |
| 8 lettres | 354 | 9.6% ████ |
| 9 lettres | 251 | 6.8% ███ |
| 10 lettres | 156 | 4.2% ██ |
| 11 lettres | 78 | 2.1% █ |
| 12 lettres | 56 | 1.5% |
| 13 lettres | 50 | 1.4% |
| 14 lettres | 15 | 0.4% |
| 15 lettres | 11 | 0.3% |
| 16 lettres | 6 | 0.2% |
| 17 lettres | 2 | 0.1% |
| 18 lettres | 1 | 0.0% |
| 19 lettres | 1 | 0.0% |
| 21 lettres | 1 | 0.0% |
| 22 lettres | 1 | 0.0% |
| Caractère | Fréquence | Usage |
|---|---|---|
| é | 1,542 | Très fréquent |
| è | 818 | Très fréquent |
| ò | 333 | Très fréquent |
| à | 6 | Rare |
| ê | 1 | Rare |
| ô | 1 | Rare |
| Digramme | Fréquence |
|---|---|
| an | 866 |
| la | 602 |
| ou | 581 |
| on | 420 |
| -l | 332 |
| en | 323 |
| ma | 254 |
| ko | 250 |
| nn | 242 |
| ch | 228 |
| ra | 213 |
| é- | 209 |
| yé | 196 |
| ré | 190 |
| té | 189 |
| as | 184 |
| al | 183 |
| ka | 168 |
| nm | 167 |
| yo | 165 |
| Rang | Mot | Fréquence | % Cumul |
|---|---|---|---|
| 1 | ka | 15,519 | 5.07% |
| 2 | an | 10,729 | 8.58% |
| 3 | sé | 7,177 | 10.92% |
| 4 | on | 6,933 | 13.19% |
| 5 | té | 6,834 | 15.42% |
| 6 | yo | 6,063 | 17.40% |
| 7 | pou | 5,812 | 19.30% |
| 8 | nou | 5,712 | 21.17% |
| 9 | pa | 5,244 | 22.88% |
| 10 | ki | 4,569 | 24.38% |
| 11 | mwen | 4,082 | 25.71% |
| 12 | ou | 3,709 | 26.92% |
| 13 | sa | 3,348 | 28.02% |
| 14 | fè | 3,274 | 29.09% |
| 15 | la | 2,531 | 29.92% |
| 16 | ni | 2,313 | 30.67% |
| 17 | di | 2,305 | 31.42% |
| 18 | kon | 2,230 | 32.15% |
| 19 | adan | 2,082 | 32.83% |
| 20 | ké | 2,042 | 33.50% |
| 21 | lè | 2,032 | 34.17% |
| 22 | si | 2,016 | 34.82% |
| 23 | moun | 1,836 | 35.42% |
| 24 | tout | 1,802 | 36.01% |
| 25 | pé | 1,756 | 36.59% |
| 26 | a-y | 1,654 | 37.13% |
| 27 | kou | 1,609 | 37.65% |
| 28 | mé | 1,461 | 38.13% |
| 29 | vwè | 1,454 | 38.61% |
| 30 | menm | 1,433 | 39.07% |
| 31 | zòt | 1,350 | 39.52% |
| 32 | pran | 1,261 | 39.93% |
| 33 | viktò | 1,235 | 40.33% |
| 34 | jan | 1,177 | 40.72% |
| 35 | tini | 1,147 | 41.09% |
| 36 | ti | 1,105 | 41.45% |
| 37 | pè | 1,096 | 41.81% |
| 38 | san | 1,057 | 42.16% |
| 39 | dé | 998 | 42.48% |
| 40 | épi | 944 | 42.79% |
| 41 | ja | 941 | 43.10% |
| 42 | rivé | 940 | 43.41% |
| 43 | èvè | 873 | 43.69% |
| 44 | fanm | 849 | 43.97% |
| 45 | men | 832 | 44.24% |
| 46 | ba | 823 | 44.51% |
| 47 | anba | 790 | 44.77% |
| 48 | pyè | 767 | 45.02% |
| 49 | palé | 763 | 45.27% |
| 50 | fò | 744 | 45.51% |
| Rang | Bigramme | Fréquence |
|---|---|---|
| 1 | té ka | 152 |
| 2 | ka fè | 86 |
| 3 | yo ka | 81 |
| 4 | an mwen | 75 |
| 5 | sé on | 59 |
| 6 | pè pyè | 59 |
| 7 | an nou | 58 |
| 8 | pa té | 52 |
| 9 | an ka | 47 |
| 10 | ou ka | 47 |
| 11 | pa ka | 45 |
| 12 | an té | 42 |
| 13 | ki té | 41 |
| 14 | nou ka | 40 |
| 15 | ka di | 39 |
| 16 | pa ni | 39 |
| 17 | adan on | 38 |
| 18 | ka pran | 33 |
| 19 | pou nou | 33 |
| 20 | yo té | 33 |
| 21 | ka vwè | 32 |
| 22 | té ni | 31 |
| 23 | sé té | 31 |
| 24 | pou yo | 30 |
| 25 | moun ka | 29 |
| 26 | ou pa | 29 |
| 27 | ni on | 27 |
| 28 | sa ki | 27 |
| 29 | kou kou | 27 |
| 30 | ou té | 26 |
| Marqueur | Fonction | Fréquence | Collocations principales |
|---|---|---|---|
| ka | Aspect progressif/habituel | 15,519 | fè, di, pran |
| té | Passé | 6,834 | ka, ni, ké |
| ké | Futur | 2,042 | — |
| kay | Futur | 148 | — |
| pa | Négation | 5,244 | té, ka, ni |
| ja | Déjà (accompli) | 941 | — |
| Mot source | Prédictions (probabilité) |
|---|---|
| ka | fè (0.08), di (0.04), pran (0.03), vwè (0.03), palé (0.02) |
| nou | ka (0.10), ké (0.06), pa (0.05), pé (0.04), té (0.04) |
| mwen | ka (0.09), té (0.07), pa (0.05), an (0.04), sé (0.04) |
| yo | ka (0.18), té (0.07), pa (0.05), pé (0.03), sé (0.03) |
| an | mwen (0.10), nou (0.08), ka (0.07), té (0.06), pa (0.02) |
| la | ka (0.13), pou (0.04), ki (0.04), yo (0.03), an (0.03) |
| té | ka (0.31), ni (0.06), ké (0.05), ja (0.03), pé (0.03) |
| pa | té (0.14), ka (0.12), ni (0.10), tini (0.02), menm (0.02) |
| tout | sé (0.12), biten (0.06), biten-la (0.04), ti (0.02), pakèt (0.02) |
| pou | nou (0.08), yo (0.07), kou (0.03), fè (0.03), mwen (0.03) |
| Rang | Mot | Longueur | Fréquence |
|---|---|---|---|
| 1 | diz-nèf-san-swasannsèt | 22 lettres | 5 |
| 2 | sèvis-ladministrasyon | 21 lettres | 18 |
| 3 | rèsponsabilité-lasa | 19 lettres | 13 |
| 4 | lagyè-lendépandans | 18 lettres | 18 |
| 5 | konkou-agrégasyon | 17 lettres | 18 |
| 6 | propriyétè-fonsyé | 17 lettres | 13 |
| 7 | toutmoun-bouliki | 16 lettres | 20 |
| 8 | fanm-laplenn-lin | 16 lettres | 18 |
| 9 | pawòl-senk-é-kat | 16 lettres | 18 |
| 10 | konsititisyon-la | 16 lettres | 18 |
| 11 | bazil-lanmòsibit | 16 lettres | 5 |
| 12 | lawa-a-gran-tété | 16 lettres | 5 |
| 13 | kréyòl-gwadloup | 15 lettres | 72 |
| 14 | véridik-véritab | 15 lettres | 36 |
| 15 | pipirit-chantan | 15 lettres | 20 |
| 16 | pè-dè-bòn-famiy | 15 lettres | 20 |
| 17 | lapèldéchanpyon | 15 lettres | 20 |
| 18 | popilasyon-moun | 15 lettres | 18 |
| 19 | sitiyasyon-lasa | 15 lettres | 18 |
| 20 | konstitisyon-la | 15 lettres | 18 |
| 21 | dèmen-spéra-dyé | 15 lettres | 13 |
| 22 | tout-sòt-kalité | 15 lettres | 13 |
| 23 | charlottesville | 15 lettres | 9 |
| 24 | rèsponsabilité | 14 lettres | 26 |
| 25 | gratèdpapyé-la | 14 lettres | 26 |
| 26 | gratèdjouné-la | 14 lettres | 26 |
| 27 | lagyè-sésésyon | 14 lettres | 18 |
| 28 | konchonni-lasa | 14 lettres | 18 |
| 29 | minis-lajistis | 14 lettres | 18 |
| 30 | andidanbway-la | 14 lettres | 13 |
Tokenisation : Expression régulière Unicode préservant les diacritiques créoles
| N-grams : Probabilités conditionnelles P(w₂ | w₁) avec seuil de pertinence à 1% |
Normalisation : Conversion en minuscules, préservation des traits d’union
Rapport généré automatiquement par Kreyòl Potomitan™ Pipeline v3.0 - Pipeline Unique
Pou an kreyòl ki ka viv é ka evolyé !
🗓️ Date du rapport : 03 juillet 2026
Date : 3 juillet 2026
Périmètre : pipeline de suggestions Android (SuggestionEngine.kt, InputProcessor.kt, LevenshteinDistance.kt, AccentTolerantMatcher.kt, FrenchDictionary.kt, KreyolInputMethodServiceRefactored.kt, BilingualSuggestion.kt) et assets JSON associés.
✅ Statut : Les deux points « A » de la feuille de route ont été traités les 06-07/08/2026. Le modèle n-grams porte désormais des clés à deux mots issues des trigrammes (v10.4.0), et le filtre de chemins mal orthographié
Dictionnaries/**debuild-apk.ymla été corrigé enDictionnaires/**. Le suivi du curseur (onUpdateSelection(), absent du service actif) a également été corrigé en v10.4.0.
Le moteur de suggestions est fonctionnel et bien découpé en composants, mais l’analyse révèle six points majeurs, par ordre de priorité :
creole_ngrams.json ne contient que des clés à un seul mot (3 582 unigrammes, 0 bigrammes/trigrammes), alors que le code cherche des clés "mot1 mot2" et "mot1 mot2 mot3". Deux des trois stratégies contextuelles ne se déclenchent donc jamais.AccentTolerantMatcher.normalize() compile ~10 objets Regex par mot du dictionnaire (3 680 mots), soit ~37 000 compilations de regex par caractère tapé. C’est la cause la plus probable des lenteurs constatées sur appareils low-end (Samsung A21s).fè, kréyòl) que le clavier veut promouvoir.Frappe → KeyboardLayoutManager → onKeyPress()
→ InputProcessor.processKeyPress() (maintient currentWord)
→ onWordChanged(word) (service IME)
→ SuggestionEngine.generateDictionarySuggestions(word)
1. AccentTolerantMatcher (préfixe insensible aux accents)
2. Fallback Levenshtein si zéro résultat préfixe (≥ 3 lettres)
3. Scoring (fréquence + bonus préfixe + longueur + accents)
→ onSuggestionsReady() → displaySuggestions() (3 boutons max)
Espace/Entrée → finalizeCurrentWord() → addWordToHistory()
→ generateContextualSuggestions() (N-grams, mot suivant)
Point important : le mode bilingue étant désactivé (enableBilingualSupport() commenté, KreyolInputMethodServiceRefactored.kt:229), le chemin réellement actif est generateDictionarySuggestions() — les chemins bilingues et MIXED décrits dans le code sont inertes.
getNgramSuggestions() (SuggestionEngine.kt:620-687) tente trois lookups : bigram "mot1 mot2", unigram "mot", trigram "mot1 mot2 mot3". Or le fichier creole_ngrams.json ne contient que des clés unigrammes (vérifié : 3 582 clés, aucune ne contient d’espace). Les stratégies 1 et 3, ainsi que leurs bonus de probabilité (+0.2 / +0.4), ne s’exécutent jamais. La prédiction contextuelle se réduit donc à « mot suivant le plus probable après le dernier mot », sans contexte étendu.
Correctif : régénérer le modèle avec de vraies clés bigram/trigram dans Dictionnaires/KreyolComplet.py, ou supprimer le code mort.
enableBilingualSupport() est commenté (KreyolInputMethodServiceRefactored.kt:229).KreyolInputMethodServiceRefactored.kt:245).Résultat : un appareil low-end dont la première initialisation échoue se retrouve en mode bilingue, les autres non. Par ailleurs FrenchDictionary (700 mots) est systématiquement chargé (SuggestionEngine.kt:151) alors qu’il n’est jamais consulté dans le chemin actif, et onBilingualSuggestionsReady() est un no-op (KreyolInputMethodServiceRefactored.kt:389-392).
Correctif : trancher (réactiver partout ou nulle part), et ne charger le dictionnaire français que si le mode est actif.
calculateDictionaryScore() (SuggestionEngine.kt:735) accorde +50 si word.startsWith(input, ignoreCase = true) — comparaison brute, sans normalisation d’accents. Tous les candidats proviennent pourtant d’un match par préfixe normalisé. Conséquence : en tapant fe, un mot comme fenmé reçoit +50 mais fè non ; le malus (-50 relatif) écrase largement le bonus « mot accentué » de +5 (SuggestionEngine.kt:760). Le classement pénalise donc les graphies créoles correctes, à rebours de l’objectif pédagogique du projet.
Correctif : appliquer le bonus sur les formes normalisées (AccentTolerantMatcher.normalize(word).startsWith(normalize(input))).
Le paramètre levenshteinDistance de calculateDictionaryScore() (SuggestionEngine.kt:730) n’est jamais transmis par aucun appelant : le bonus de correction (+30/+15) et son log sont du code mort. Les corrections orthographiques sont classées uniquement par (distance, fréquence) dans LevenshteinDistance.findClosestMatches().
InputProcessor.currentWord est la seule source de vérité pour les suggestions, mais :
onUpdateSelection() : si l’utilisateur déplace le curseur ou tape au milieu d’un mot, currentWord ne correspond plus au texte réel ;KreyolInputMethodServiceRefactored.kt:800-841) supprime du texte via deleteSurroundingText() sans mettre à jour currentWord (le commentaire ligne 832 l’admet) ;Les suggestions peuvent donc être calculées sur un préfixe fantôme, et processSuggestionSelection() (InputProcessor.kt:293) peut supprimer le mauvais nombre de caractères.
Correctif : implémenter onUpdateSelection() et reconstruire currentWord depuis getTextBeforeCursor().
Chaque frappe lance une coroutine (SuggestionEngine.kt:385) sans annuler la précédente ni horodater les résultats. Une requête lente (fallback Levenshtein sur 3 680 mots) peut aboutir après la requête suivante plus rapide et afficher des suggestions périmées.
Correctif : conserver le Job courant et l’annuler avant chaque nouvelle génération (pattern latestJob?.cancel()), ou utiliser un Flow + collectLatest.
wordHistory non thread-safe — modéréwordHistory (SuggestionEngine.kt:75) est une mutableListOf mutée depuis le thread main (addWordToHistory) et lue depuis Dispatchers.Default (getNgramSuggestions). Risque faible mais réel de ConcurrentModificationException ou de lecture incohérente.
handleBackspace() (InputProcessor.kt:121) fait deleteSurroundingText(1, 0) : sur un émoji (paire de substitution UTF-16, 2 code units), cela n’efface que la moitié du caractère et produit un caractère invalide. Utiliser la longueur du dernier grapheme cluster (ou sendKeyEvent(KEYCODE_DEL)).
InputProcessor.applyCaseToSuggestion() (InputProcessor.kt:317-335) : méthode privée jamais appelée (code mort, doublon de applyCasingPattern).onFinishInput() ne rappelle pas super.onFinishInput() (KreyolInputMethodServiceRefactored.kt:643-651), contournement fragile du cycle de vie IMEf22bee3) laissait le clavier invisible après un changement d’app puis retour ; l’appel à super.onFinishInput() a été restauré et le scénario validé visuellement (voir rapport_simulation_partage_niveaux_2026-07-13.md).getKreyolSuggestions() applique applyCasingPattern (SuggestionEngine.kt:296), puis generateBilingualSuggestions() la réapplique (SuggestionEngine.kt:235).SecurityException FileProvider, message parti en SMS texte seul)shareLevelCard() (SettingsActivity.kt) passait l’URI via EXTRA_STREAM seul, non couvert par FLAG_GRANT_READ_URI_PERMISSION ; ajout de ClipData.newUri(...), l’image s’attache désormais (MMS). Détail dans rapport_simulation_partage_niveaux_2026-07-13.md.Le chemin « une frappe → suggestions » est exécuté à chaque caractère ; c’est lui qu’il faut optimiser en priorité.
AccentTolerantMatcher.normalize() (AccentTolerantMatcher.kt:26-48) crée ~10 objets Regex à chaque appel. findAccentTolerantSuggestions() (AccentTolerantMatcher.kt:82-85) l’appelle sur chacun des 3 680 mots du dictionnaire, à chaque frappe. Ordre de grandeur : ~37 000 compilations de regex + autant de chaînes intermédiaires par caractère tapé — énorme pression GC, particulièrement sensible sur les appareils que le code tente déjà de ménager (fix A21s, monitoring mémoire).
Correctifs, par ordre d’impact :
Triple(mot, motNormalisé, fréquence)).Char → Char (un when ou un IntArray indexé) ou java.text.Normalizer.normalize(s, NFD) + filtrage des diacritiques.Regex une fois en val de l’objet.La recherche préfixe parcourt toute la liste (O(n) par frappe). Avec un dictionnaire trié alphabétiquement sur la forme normalisée, une recherche binaire donne la plage de préfixes en O(log n) ; un trie ferait encore mieux. Combiné au 4.1, le coût par frappe deviendrait négligeable.
findClosestMatchesNormalized() (LevenshteinDistance.kt:150-187) normalise tout le dictionnaire deux fois (une fois pour le pré-filtre longueur, une fois pour la distance) avec les regex du 4.1, puis calcule une matrice DP complète par candidat. Le pré-calcul des formes normalisées (4.1) et une DP à deux lignes avec sortie anticipée (bande de Ukkonen) réduiraient fortement le pic de latence — précisément dans le cas « l’utilisateur a fait une faute », où la réactivité compte.
LevenshteinDistance.cache (LevenshteinDistance.kt:193) croît sans limite et clearCache() n’est jamais appelé ; calculateCached() n’a d’ailleurs aucun appelant. Supprimer, ou remplacer par un LruCache.
runAccentTolerantTests() et testCreoleSpecificCases() (KreyolInputMethodServiceRefactored.kt:979-1065) s’exécutent en production à chaque création du service. testCreoleSpecificCases remplace temporairement le listener de suggestions réel : si l’utilisateur tape pendant ces ~600 ms, ses suggestions partent dans le listener de test (UI figée) et la restauration peut écraser un listener légitime. À déplacer en tests unitaires JVM ou à gater derrière BuildConfig.DEBUG.
System.gc() forcé dans le monitoring mémoire (KreyolInputMethodServiceRefactored.kt:147) : anti-pattern, provoque des pauses au lieu d’en éviter.displaySuggestions() (KreyolInputMethodServiceRefactored.kt:505-541) détruit et recrée 3 Button à chaque frappe ; recycler 3 vues fixes et changer leur text suffirait.addWordToDictionary() (SuggestionEngine.kt:450-466) recopie et retrie la liste entière à chaque ajout — acceptable seulement parce qu’il n’est jamais appelé (cf. 6.2).Section retirée de cette page publique : elle détaillait un point de journalisation interne aux builds de développement. Le build de production (release) est vérifié comme non concerné (R8 supprime les logs concernés). Voir l’historique Git du dépôt pour le détail technique complet si besoin.
Le chemin actif generateDictionarySuggestions() classe uniquement par fréquence globale ; le bonus n-gram (+50) n’existe que dans mergeAndRankSuggestions(), appelé par le mode MIXED inutilisé. Après « an ka », taper « ma » devrait favoriser « manjé » si le corpus le suggère — ce n’est pas le cas aujourd’hui. Réintégrer le signal n-gram dans le scoring de frappe est une amélioration à fort impact et faible risque.
addWordToDictionary() n’a aucun appelant, rien n’est persisté).SuggestionSource.LEARNED (BilingualSuggestion.kt:49) est prévu mais inutilisé.L’infrastructure existe pourtant : la gamification (CreoleDictionaryWithUsage) compte déjà l’usage réel de chaque mot et le persiste. Brancher ce compteur comme signal de personnalisation (score += k·log(usageCount)) donnerait un clavier qui s’adapte à chaque utilisateur à moindre coût.
MIN_WORD_LENGTH = 2 (SuggestionEngine.kt:23) et le garde-fou de findAccentTolerantSuggestions() (AccentTolerantMatcher.kt:75) suppriment toute suggestion à 1 caractère. Or le kréyòl est riche en mots courts très fréquents (ka 15 519, an 10 729, sé 7 177 — têtes du dictionnaire). Autoriser les suggestions dès la première lettre (éventuellement limitées au top fréquence) améliorerait directement la vitesse de saisie.
Le fallback Levenshtein (SuggestionEngine.kt:561-563) ne se déclenche que si la recherche préfixe ne renvoie rien. Une faute en début de mot (« bpnjou ») est corrigée, mais « bonjoy » ne le sera jamais si « bonjo… » matche autre chose. Pistes : mélanger systématiquement 1-2 candidats de correction quand le meilleur score préfixe est faible, et proposer une auto-correction à la validation (espace) avec possibilité d’annuler — comportement standard des claviers modernes.
Cf. 3.1 : tant que creole_ngrams.json ne contient pas de clés multi-mots, la prédiction contextuelle reste limitée à un seul mot de contexte. Le pipeline Dictionnaires/KreyolComplet.py est l’endroit où produire de vraies entrées bigram/trigram (attention : le déclencheur CI build-apk.yml filtre sur Dictionnaries/** — chemin mal orthographié — les changements de dictionnaire ne déclenchent pas de build).
CLAUDE.md et le README annoncent ~1 867 mots ; le dictionnaire embarqué en contient 3 680. À mettre à jour.
| # | Action | Impact | Réf. |
|---|---|---|---|
| 1 | Précalculer les formes normalisées du dictionnaire au chargement + supprimer les regex de normalize() |
Latence par frappe divisée par un ordre de grandeur | §4.1 |
| 2 | Gater tous les logs du chemin de saisie derrière BuildConfig.DEBUG |
Confidentialité + perf | §5 |
| 3 | Corriger le bonus préfixe pour comparer les formes normalisées | Classement cohérent des mots accentués | §3.3 |
| 4 | Annuler la coroutine de suggestion précédente à chaque frappe | Fin des suggestions périmées | §3.6 |
| 5 | Sortir les tests runAccentTolerantTests/testCreoleSpecificCases du démarrage production |
Démarrage plus rapide, pas d’écrasement du listener | §4.5 |
| 6 | Supprimer le code mort (applyCaseToSuggestion, calculateCached, paramètre levenshteinDistance, stratégies bigram/trigram si non régénérées) |
Lisibilité | §3.4, §3.9, §4.4 |
| 7 | Autoriser les suggestions dès 1 lettre (top fréquence) | Vitesse de saisie | §6.3 |
| # | Action | Impact | Réf. |
|---|---|---|---|
| A | Régénérer creole_ngrams.json avec clés bigram/trigram (+ corriger le filtre CI Dictionnaries) |
Prédiction contextuelle réelle | §3.1, §6.5 |
| B | Réintégrer le signal n-gram dans le scoring pendant la frappe | Suggestions contextuelles | §6.1 |
| C | Personnalisation : brancher les compteurs CreoleDictionaryWithUsage dans le score + apprendre les mots hors dictionnaire |
Clavier qui s’adapte à l’utilisateur | §6.2 |
| D | Implémenter onUpdateSelection() et resynchroniser currentWord |
Fiabilité des suggestions et du remplacement | §3.5 |
| E | Trancher le sort du mode bilingue (réactiver proprement ou retirer le chargement français) | Cohérence + mémoire | §3.2 |
| F | Index préfixe (tri alphabétique + recherche binaire, ou trie) | Scalabilité si le dictionnaire grossit | §4.2 |
| G | Auto-correction à l’espace + corrections mêlées aux suggestions préfixe | Rattrapage des fautes courantes | §6.4 |
Les 7 quick wins ont été appliqués et vérifiés (compileDebugKotlin, assembleDebug et 51 tests unitaires verts).
| # | Quick win | Ce qui a été fait |
|---|---|---|
| 1 | Normalisation précalculée | AccentTolerantMatcher.normalize() réécrit avec une table char→char (zéro regex) ; SuggestionEngine précalcule les formes normalisées du dictionnaire au chargement (normalizedWords) et fait la recherche préfixe dessus, avec arrêt anticipé dès 10 résultats (le dictionnaire étant trié par fréquence). Bonus : normalize() gère désormais aussi les majuscules accentuées (É→e), bug de l’ancienne version. |
| 2 | Logs | Le remplacement massif n’était pas nécessaire : R8 supprime déjà tous les Log.* en release (cf. mise à jour du §5). Le spam SHIFT_REAL_DEBUG a été supprimé de InputProcessor et du service. |
| 3 | Bonus préfixe normalisé | calculateDictionaryScore() utilise AccentTolerantMatcher.startsWith() : taper « fe » favorise désormais fè au même titre que les mots en « fe… ». |
| 4 | Annulation coroutines | suggestionJob?.cancel() avant chaque génération dans les 4 points d’entrée (generateSuggestions, generateBilingualSuggestions, generateDictionarySuggestions, generateContextualSuggestions). |
| 5 | Tests hors production | runAccentTolerantTests() / testCreoleSpecificCases() supprimés du service ; AccentTolerantMatchingTest.kt (sourceset main) supprimé ; remplacés par un vrai test JVM AccentTolerantMatcherTest.kt. |
| 6 | Code mort | Supprimés : applyCaseToSuggestion(), cache Levenshtein (calculateCached/clearCache), paramètre levenshteinDistance de calculateDictionaryScore, stratégies bigram/trigram de getNgramSuggestions() (le modèle n’a que des clés unigrammes), getSuggestionListener(), calculateMatchScore()/getDebugInfo()/runTests() du matcher. |
| 7 | Suggestions dès 1 lettre | MIN_WORD_LENGTH passé de 2 à 1 : ka, an, sé… sont proposés dès la première frappe. |
Chantier G (partie classement) appliqué également : la distance de Levenshtein est désormais propagée jusqu’au score (LevenshteinDistance renvoie (mot, fréquence, distance), calculateDictionaryScore donne un poids dominant à la distance). Vérifié sur émulateur : « mesli » propose désormais mèsi (distance 1) en première position, devant mésyé/mépri (distance 2, plus fréquents). Le second volet du chantier (auto-correction à l’espace, corrections mêlées aux résultats préfixe) reste à faire.
Découverte en passant — la suite de tests n’a jamais été verte (25 échecs préexistants sur main) pour deux raisons corrigées au passage :
android.util.Log et org.json sont des stubs en test JVM → ajout de unitTests.returnDefaultValues = true et de la dépendance testImplementation 'org.json:json:20240303' (app/build.gradle) ;calculate("mesli","mèsi") vaut 2, pas 1) → attentes corrigées dans LevenshteinDistanceTest.kt.À noter également : le script android_keyboard/gradlew est corrompu (il passe les arguments quotés à Gradle, eval manquant) — la CI n’est pas affectée car elle installe Gradle via gradle-version: 8.7, mais en local il faut invoquer java -classpath gradle/wrapper/gradle-wrapper.jar org.gradle.wrapper.GradleWrapperMain <tâche> ou régénérer le wrapper.
Rapport généré par analyse statique du code (branche main, commit 26e73bd) et inspection des assets JSON embarqués.
🗓️ Date du rapport : 03 juillet 2026
✅ Statut : Le point « supprimer le code mort VocabularyStatsActivity/MainActivity » de la Phase 1 a été traité le 04/07/2026 (commit
827254a), en même temps que les corrections UX ci-dessous.
Projet: KreyolKeyb (Clavier Prédictif Créole)
Date: 2026-07-03
Version analysée: 7.0.0
Auteur: Mistral Vibe
┌─────────────────────────────────────────────────────────────────────┐
│ ARCHITECTURE GAMIFICATION │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ SettingsActivity │ │ VocabularyStats │ │ WordSearch │ │
│ │ (Niveaux + UI) │◄───►│ + WordUsage │ │ + WordScramble │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ CreoleDictionaryWithUsage.kt │ │
│ │ - Tracking mots utilisateur │ │
│ │ - Persistance JSON (creole_dict_with_usage.json) │ │
│ │ - Cache mémoire + sauvegarde batch │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Fragments (ViewPager2) │ │
│ │ 0: OnboardingFragment → Démarrage │ │
│ │ 1: StatsFragment → Statistiques vocabulaire │ │
│ │ 2: WordSearchFragment → Jeu Mots Mêlés │ │
│ │ 3: WordScrambleFragment → Jeu Mots Mélangés │ │
│ │ 4: AboutFragment → À propos │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
8 Niveaux Culturels Créoles (Distribution Gaussienne)
| Niveau | Emoji | Seuil (%) | Seuil (3680 mots) | Pourcentage Population |
|---|---|---|---|---|
| Pipirit | 🌍 | 0% | 0 | 0.15% |
| Ti moun | 🌱 | 1.5% | 55 | 2% |
| Débrouya | 🔥 | 5% | 184 | 14% |
| An mitan | 💎 | 12% | 442 | 34% |
| Kompè Lapen | 🐇 | 25% | 920 | 34% |
| Kompè Zamba | 🐘 | 45% | 1656 | 14% |
| Potomitan | 👑 | 70% | 2576 | 2% |
| Benzo | 🧙🏿♀️ | 100% | 3680 | 0.15% |
Formule: calculateGaussianThresholds() dans SettingsActivity.kt (lignes 2070-2093)
android_keyboard/app/src/main/java/com/example/kreyolkeyboard/
├── gamification/
│ ├── VocabularyStats.kt # Data class statistiques
│ ├── WordUsageStats.kt # Data class usage par mot
│ ├── CreoleDictionaryWithUsage.kt # Gestion dictionnaire + tracking
│ ├── WordCommitListener.kt # Interface callback
│ └── VocabularyStatsActivity.kt # Activity stats (obsolète?)
├── SettingsActivity.kt # COEUR: Niveaux + UI + ViewPager2
├── MainActivity.kt # Navigation par onglets (obsolète?)
├── wordsearch/
│ ├── WordSearchActivity.kt # Jeu mots mêlés
│ ├── WordSearchGenerator.kt # Génération grille
│ └── WordSearchModels.kt # Data classes
└── wordscramble/
├── WordScrambleActivity.kt # Jeu mots mélangés
└── WordScrambleModels.kt # Data classes + chargement
WordCommitListenerupdateWordUsage() accessible depuis le service IMEPROBLÈME: Deux activités principales avec logique similaire
├── MainActivity.kt (62 lignes)
│ ├── Onglets: Clavier, Stats, Mots Mêlés, Paramètres
│ └── Navigation basique
│
└── SettingsActivity.kt (3115 lignes)
├── ViewPager2 avec 5 fragments
├── Onboarding, Stats, WordSearch, WordScramble, About
└── TOUTE la logique de gamification
Impact:
La classe SettingsActivity.kt contient:
Violations:
Code dupliqué:
// Dans CreoleDictionaryWithUsage.kt
private fun saveDictionaryToFile(dict: JSONObject) {
val file = File(context.filesDir, DICT_FILE)
file.writeText(dict.toString(2))
}
// Dans SettingsActivity.kt (companion object)
// Sauvegarde avec écriture atomique (meilleure implémentation)
val tempFile = File(context.filesDir, "creole_dict_with_usage.json.tmp")
tempFile.bufferedWriter().use { writer ->
writer.write(existingData.toString())
}
tempFile.renameTo(usageFile)
Problèmes:
CreoleDictionaryWithUsage n’utilise pas l’écriture atomiqueProblème de cache:
// Dans SettingsActivity.kt
private var cachedTotalWords: Int? = null
private fun getTotalDictionaryWords(): Int {
cachedTotalWords?.let { return it } // Cache jamais invalidé!
// ... chargement depuis assets
}
Problèmes:
Dans CreoleDictionaryWithUsage.kt:
// Ligne 163: Normalisation basique
val normalized = word.lowercase().trim()
// Ligne 167: Vérification dans le dictionnaire
return if (dictionary.has(normalized)) { ... }
// Problème: La normalisation ne gère pas les accents!
// Ex: "mesli" -> "mesli" (pas trouvé) mais devrait matcher "mèsi"
Impact:
Incohérence architecturale:
├── SettingsActivity (ViewPager2)
│ ├── StatsFragment → Affiche les stats
│ └── WordSearchFragment → Lance WordSearchActivity
│
└── Activities séparées:
├── VocabularyStatsActivity.kt (226 lignes)
└── WordSearchActivity.kt (jeu complet)
Problèmes:
Exemples:
// VocabularyStatsActivity.kt ligne 152-153:
// Note: Le système de niveaux est maintenant géré dans SettingsActivity.kt
// avec la distribution gaussienne et les noms culturels créoles
Problème: Si le système est déplacé, pourquoi garder ce fichier?
┌─────────────────────────────────────────────────────────────────────┐
│ ARCHITECTURE PROPOSÉE (MVVM) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ View │ │ ViewModel │ │ Repository │ │
│ │ (Activities + │ │ (Gamification │ │ (Data Layer) │ │
│ │ Fragments) │◄───►│ ViewModel) │◄───►│ │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ View: │ │
│ │ - MainActivity (navigation) │ │
│ │ - SettingsActivity (config) │ │
│ │ - GameActivity (jeux) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ViewModel: │ │
│ │ - GamificationViewModel: niveaux, stats, progression │ │
│ │ - WordUsageViewModel: tracking des mots │ │
│ │ - GameViewModel: logique des jeux │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Repository: │ │
│ │ - DictionaryRepository: chargement, sauvegarde │ │
│ │ - UsageRepository: tracking, stats │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
Fichiers à créer:
app/src/main/java/com/example/kreyolkeyboard/
├── viewmodel/
│ ├── GamificationViewModel.kt
│ ├── DictionaryViewModel.kt
│ └── WordUsageViewModel.kt
├── repository/
│ ├── DictionaryRepository.kt
│ └── UsageRepository.kt
└── gamification/
├── level/
│ ├── LevelSystem.kt
│ └── LevelThresholds.kt
└── stats/
├── VocabularyStats.kt (existant)
└── WordUsageStats.kt (existant)
Diviser SettingsActivity.kt (3115 lignes) en:
// 1. GamificationManager.kt (~300 lignes)
class GamificationManager {
fun calculateGaussianThresholds(): IntArray
fun getCurrentLevel(wordsDiscovered: Int): Level
fun getNextLevelInfo(wordsDiscovered: Int): LevelInfo
}
// 2. SettingsViewModel.kt (~200 lignes)
class SettingsViewModel : ViewModel() {
val levels: LiveData<List<Level>>
val currentLevel: LiveData<Level>
fun loadStats()
}
// 3. SettingsActivity.kt (~800 lignes)
class SettingsActivity : AppCompatActivity() {
// Seulement la logique UI + ViewPager2
private lateinit var viewModel: SettingsViewModel
// ... gestion des fragments
}
// 4. Fragments séparés (déjà bien)
// StatsFragment.kt, OnboardingFragment.kt, etc.
Actuellement:
// Hardcodé dans SettingsActivity.kt
private fun calculateGaussianThresholds(): IntArray {
val percentages = doubleArrayOf(0.0, 0.015, 0.05, 0.12, 0.25, 0.45, 0.70, 1.0)
// ...
}
Proposition:
// LevelConfiguration.kt
data class LevelConfig(
val name: String,
val emoji: String,
val percentage: Double,
val description: String
)
object LevelConfiguration {
val ALL_LEVELS = listOf(
LevelConfig("Pipirit", "🌍", 0.0, "Les tout premiers pas"),
LevelConfig("Ti moun", "🌱", 0.015, "Débutant"),
LevelConfig("Débrouya", "🔥", 0.05, "Débutant avancé"),
// ...
)
fun getThresholds(totalWords: Int): IntArray = ...
}
Avantages:
Problème actuel: CreoleDictionaryWithUsage.kt ligne 163
val normalized = word.lowercase().trim()
// Ne gère pas: mèsi, kréyòl, etc.
Solution: Utiliser AccentTolerantMatcher (déjà existant)
// Dans CreoleDictionaryWithUsage.kt
fun normalizeForTracking(word: String): String {
return AccentTolerantMatcher.normalize(word).lowercase().trim()
}
fun incrementWordUsage(word: String): Boolean {
val normalized = normalizeForTracking(word)
// ... vérifier dans dictionnaire avec normalisation
}
// DictionaryRepository.kt
interface DictionaryRepository {
suspend fun loadDictionary(): Map<String, WordData>
suspend fun updateWordUsage(word: String): Boolean
suspend fun getStats(): VocabularyStats
suspend fun save()
}
class FileDictionaryRepository(private val context: Context) : DictionaryRepository {
private var dictionary: Map<String, WordData> = emptyMap()
private var pendingUpdates = mutableMapOf<String, Int>()
override suspend fun loadDictionary() = withContext(Dispatchers.IO) {
// Charger depuis assets + fichier usage
}
override suspend fun updateWordUsage(word: String): Boolean = withContext(Dispatchers.IO) {
// Mise à jour + sauvegarde atomique
}
}
Avantages:
// CacheManager.kt
class CacheManager {
private val cache = mutableMapOf<String, Any>()
private val cacheExpiry = mutableMapOf<String, Long>()
private val CACHE_TTL_MS = 300_000L // 5 minutes
fun <T> getOrLoad(key: String, loader: () -> T): T {
if (isCacheValid(key)) {
return cache[key] as T
}
val value = loader()
cache[key] = value
cacheExpiry[key] = System.currentTimeMillis() + CACHE_TTL_MS
return value
}
fun invalidate(key: String) {
cache.remove(key)
cacheExpiry.remove(key)
}
fun invalidateAll() {
cache.clear()
cacheExpiry.clear()
}
private fun isCacheValid(key: String): Boolean {
val expiry = cacheExpiry[key] ?: return false
return System.currentTimeMillis() < expiry
}
}
Problème actuel:
Solution:
// GameManager.kt
interface GameManager {
fun onGameComplete(gameType: GameType, score: Int, wordsFound: List<String>)
fun updateGlobalProgress(word: String)
}
// Implémentation:
class GamificationGameManager(
private val usageRepository: UsageRepository
) : GameManager {
override fun onGameComplete(gameType: GameType, score: Int, wordsFound: List<String>) {
// Mettre à jour les stats globales
wordsFound.forEach { word ->
usageRepository.incrementUsage(word)
}
// Ajouter bonus de score
}
}
// RewardSystem.kt
data class Reward(
val type: RewardType,
val points: Int,
val description: String
)
enum class RewardType {
DAILY_LOGIN,
WORD_DISCOVERED,
WORD_MASTERED,
GAME_COMPLETED,
LEVEL_UP
}
class RewardManager {
private val rewards = mutableListOf<Reward>()
fun addReward(reward: Reward) {
rewards.add(reward)
}
fun getTotalPoints(): Int = rewards.sumOf { it.points }
fun getUnclaimedRewards(): List<Reward> = rewards.filter { !it.claimed }
}
Problème: La création des vues est dupliquée et inline
Solution: Créer des composants custom
// LevelBadge.kt
class LevelBadge @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : LinearLayout(context, attrs) {
fun setLevel(level: Level) {
findViewById<TextView>(R.id.levelEmoji).text = level.emoji
findViewById<TextView>(R.id.levelName).text = level.name
// Style dynamique
setBackgroundColor(level.color)
}
}
// ProgressCard.kt
class ProgressCard @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : CardView(context, attrs) {
fun setProgress(current: Int, max: Int, level: Level) {
progressBar.max = max
progressBar.progress = current
levelBadge.setLevel(level)
}
}
// LevelUpAnimator.kt
class LevelUpAnimator(private val context: Context) {
fun showLevelUpAnimation(oldLevel: Level, newLevel: Level) {
val dialog = AlertDialog.Builder(context)
.setView(R.layout.dialog_level_up)
.setCancelable(false)
.create()
dialog.findViewById<TextView>(R.id.tvNewLevel)?.text = newLevel.name
dialog.findViewById<TextView>(R.id.tvMessage)?.text = "Félisitasyon! Ou monte nan ${newLevel.name}"
// Animation
val animation = AnimationUtils.loadAnimation(context, R.anim.level_up)
dialog.findViewById<View>(R.id.animationView)?.startAnimation(animation)
dialog.show()
// Auto-dismiss après 5 secondes
Handler(Looper.getMainLooper()).postDelayed({
dialog.dismiss()
}, 5000)
}
}
// GamificationTest.kt
class LevelSystemTest {
@Test
fun `test level thresholds calculation`() {
val thresholds = LevelSystem.calculateThresholds(3680)
assertEquals(0, thresholds[0]) // Pipirit
assertEquals(55, thresholds[1]) // Ti moun (1.5%)
assertEquals(184, thresholds[2]) // Débrouya (5%)
assertEquals(3680, thresholds[7]) // Benzo (100%)
}
@Test
fun `test level progression`() {
val levelSystem = LevelSystem(3680)
assertEquals(Level.PIPIRIT, levelSystem.getLevel(0))
assertEquals(Level.TI_MOUN, levelSystem.getLevel(55))
assertEquals(Level.BENZO, levelSystem.getLevel(3680))
}
}
class UsageTrackingTest {
@Test
fun `test word usage tracking`() {
val repo = InMemoryUsageRepository()
repo.incrementUsage("bonjou")
repo.incrementUsage("bonjou")
assertEquals(2, repo.getUsageCount("bonjou"))
}
@Test
fun `test accent normalization`() {
val repo = InMemoryUsageRepository()
repo.incrementUsage("mèsi")
assertEquals(1, repo.getUsageCount("mesi")) // Normalisé
}
}
// GamificationIntegrationTest.kt
@ExperimentalCoroutinesApi
@ExtendWith(InstantTaskExecutorRule::class)
class GamificationIntegrationTest {
@get:Rule
val coroutineTestRule = CoroutineTestRule()
@Test
fun `test full gamification flow`() = coroutineTestRule.runBlockingTest {
val repo = FakeDictionaryRepository()
val viewModel = GamificationViewModel(repo)
// Simuler l'utilisation de mots
repo.incrementUsage("bonjou")
repo.incrementUsage("mèsi")
// Vérifier la mise à jour des stats
val stats = viewModel.stats.value
assertEquals(2, stats?.wordsDiscovered)
// Vérifier le niveau
assertEquals(Level.PIPIRIT, viewModel.currentLevel.value)
}
}
| # | Tâche | Complexité | Impact | Effort (jours) |
|---|---|---|---|---|
| 1 | Supprimer le code mort (VocabularyStatsActivity) | Faible | ⭐⭐⭐ | 0.5 |
| 2 | Fixer la normalisation des accents dans le tracking | Moyenne | ⭐⭐⭐⭐ | 1 |
| 3 | Unifier la persistance (écriture atomique partout) | Moyenne | ⭐⭐⭐⭐ | 1 |
| 4 | Clarifier la navigation (MainActivity vs SettingsActivity) | Élevée | ⭐⭐⭐ | 2 |
| # | Tâche | Complexité | Impact | Effort (jours) |
|---|---|---|---|---|
| 5 | Extraire GamificationManager de SettingsActivity | Élevée | ⭐⭐⭐⭐ | 3 |
| 6 | Implémenter DictionaryRepository | Élevée | ⭐⭐⭐⭐ | 2 |
| 7 | Créer GamificationViewModel | Moyenne | ⭐⭐⭐⭐ | 2 |
| 8 | Externaliser LevelConfiguration | Faible | ⭐⭐ | 0.5 |
| # | Tâche | Complexité | Impact | Effort (jours) |
|---|---|---|---|---|
| 9 | Intégrer les jeux dans la progression globale | Élevée | ⭐⭐⭐ | 3 |
| 10 | Implémenter le système de récompenses | Moyenne | ⭐⭐⭐ | 2 |
| 11 | Créer des composants UI réutilisables | Moyenne | ⭐⭐⭐ | 2 |
| 12 | Ajouter des animations de niveau | Faible | ⭐⭐ | 1 |
| # | Tâche | Complexité | Impact | Effort (jours) |
|---|---|---|---|---|
| 13 | Suite de tests unitaire gamification | Moyenne | ⭐⭐⭐⭐ | 2 |
| 14 | Tests d’intégration | Élevée | ⭐⭐⭐ | 3 |
| 15 | Documentation architecture | Faible | ⭐⭐ | 1 |
private fun getCurrentLevel(wordsDiscovered: Int): String {
val thresholds = calculateGaussianThresholds()
return when {
wordsDiscovered >= thresholds[7] -> "🧙🏿♀️ Benzo"
wordsDiscovered >= thresholds[6] -> "👑 Potomitan"
// ...
else -> "🌍 Pipirit"
}
}
private fun calculateGaussianThresholds(): IntArray {
val totalWords = getTotalDictionaryWords()
val percentages = doubleArrayOf(0.0, 0.015, 0.05, 0.12, 0.25, 0.45, 0.70, 1.0)
return IntArray(8) { index ->
if (index == 7) totalWords else (totalWords * percentages[index]).toInt()
}
}
fun incrementWordUsage(word: String): Boolean {
val normalized = word.lowercase().trim()
if (!isValidForTracking(normalized)) return false
return if (dictionary.has(normalized)) {
val wordData = getWordDataSafe(normalized)
val currentCount = wordData?.getInt("user_count") ?: 0
wordData?.put("user_count", currentCount + 1)
unsavedChanges++
if (unsavedChanges >= SAVE_BATCH_SIZE) saveDictionary()
true
} else {
false // Mot pas dans le dictionnaire
}
}
private fun isValidForTracking(word: String): Boolean {
if (word.length < MIN_WORD_LENGTH) return false
if (word.any { it.isDigit() }) return false
if (word.contains("http") || word.contains("www") || word.contains(".com")) return false
if (word.contains("@")) return false
return true
}
| Fichier | Lignes | Complexité Cyclomatique | Commentaire |
|---|---|---|---|
| SettingsActivity.kt | 3115 | ~50+ | À refactorer urgent |
| CreoleDictionaryWithUsage.kt | 466 | ~15 | Bon mais à améliorer |
| VocabularyStatsActivity.kt | 226 | ~10 | Code mort? |
| WordSearchActivity.kt | ~300 | ~12 | OK |
| WordScrambleActivity.kt | ~200 | ~10 | OK |
Le système de gamification de KreyolKeyb a une bonne base conceptuelle (niveaux culturels, distribution gaussienne, respect de la vie privée) mais souffre de problèmes architecturaux majeurs qui entravent sa maintenabilité et son évolutivité.
Priorité absolue:
Impact potentiel:
Estimation totale: ~15-20 jours de développement pour la refactorisation complète.
Document généré par Mistral Vibe - 2026-07-03
🗓️ Date du rapport : 04 juillet 2026
✅ Statut : La plupart des « quick wins » listés en fin de rapport ont été appliqués le jour même (commit
827254a, 2026-07-04) : mojibake corrigé, version figée remplacée parBuildConfig.VERSION_NAME, contraste illisible corrigé, et le code mort confirmé (MainActivity,VocabularyStatsActivity, Activities standalone des jeux) supprimé.
Ce rapport complète rapport.md (audit du moteur de suggestions) en couvrant cette fois les écrans de l’application companion : Paramètres (5 onglets), tableau de bord de progression, et les deux jeux de vocabulaire. Méthode : inspection du code (Activities, Fragments, layouts XML) croisée avec des captures d’écran live sur émulateur (Pixel 5, Android 14).
VocabularyStatsActivity (dashboard complet, thème sombre) n’est reliée à aucun bouton de l’UI shippée — remplacée en doublon par un onglet Stats qui réimplémente la même chose en thème clair.textColor défini).SettingsActivity.kt malgré une palette complète déjà définie dans colors.xml, sans aucun themes.xml/styles.xml — dette de cohérence visuelle qui grandit à chaque écran ajouté.contentDescription dans toute l’application.| Écran | Fichier | Accessible depuis l’UI ? |
|---|---|---|
| SettingsActivity (launcher, 5 onglets) | SettingsActivity.kt (~3200 lignes, construit entièrement en code, sans layout XML) |
Oui — écran d’accueil |
| VocabularyStatsActivity (dashboard autonome, thème sombre) | gamification/VocabularyStatsActivity.kt + activity_vocabulary_stats.xml |
Non — orpheline, seule MainActivity (elle-même absente du manifest) y référence un lien |
| WordSearchActivity (Mots Mêlés standalone, thème sombre) | wordsearch/WordSearchActivity.kt + activity_word_search.xml |
Oui, via VocabularyStatsActivity.launchWordSearchGame() — mais cette dernière est elle-même inatteignable ; dupliqué par WordSearchFragment (thème clair) dans les Paramètres |
| WordScrambleActivity (Mots Mélangés standalone, thème clair) | wordscramble/WordScrambleActivity.kt + activity_word_scramble.xml |
Dupliqué par WordScrambleFragment dans les Paramètres, pas de lien direct depuis l’UI shippée |
MainActivity + activity_main.xml / activity_main_tabs.xml |
MainActivity.kt |
Code mort — absente du manifest, ne peut pas être lancée |
En pratique, un utilisateur normal ne voit que SettingsActivity et ses 5 onglets. Les trois autres Activities ne sont accessibles que via adb shell am start direct (comme fait pour cet audit) — ce qui signifie que tout le travail de style/fonctionnalité qui y a été investi (thèmes sombres, boutons Indice/Thèmes, animations de score) n’est jamais vu par un utilisateur réel.
SettingsActivity.kt:2460 — text = "� Chargement..." : le caractère d’encodage cassé s’affiche brièvement dans l’en-tête de l’onglet Mots Mêlés pendant le chargement du thème.
SettingsActivity.kt:1245 — text = "Version : 6.5.1\n" + ... : chaîne littérale jamais mise à jour, alors que build.gradle déclare versionName "7.0.0". L’onglet « À Propos » ment sur la version installée à chaque release depuis au moins deux versions. Corriger avec BuildConfig.VERSION_NAME.
activity_word_search.xml:57 déclare android:numColumns="10", mais WordSearchActivity.kt:85 génère toujours une grille 8×8 et écrase la valeur à l’exécution (gridView.numColumns = puzzle.gridSize, ligne 112). Conséquence visible : sur la capture live, la dernière ligne de lettres est coupée par le bas de la grille, le conteneur ayant été dimensionné pour 10 colonnes de large mais la grille réelle génère un ratio différent — rendu clairement moins propre que la version embarquée (WordSearchFragment), qui n’a pas ce problème.
Visibles et stylés sur la capture live (« 💡 INDICE », « 🎨 THÈMES »), mais tous des TODO no-op côté code :
WordSearchActivity.kt:181 — intégration gamification/XP jamais branchéeWordSearchActivity.kt:221 — sélecteur de thèmes non implémentéWordSearchActivity.kt:225 — animation de score non implémentée (le conteneur scoreAnimationContainer de 80dp dans activity_word_search.xml reste en permanence vide)WordSearchActivity.kt:236 — dialog de fin de partie avec partage non implémentéWordSearchActivity.kt:240 — messages d’erreur non implémentésUn utilisateur qui appuie sur « Indice » ou « Thèmes » ne reçoit aucun retour visuel — pas même un message d’erreur.
Capture live de l’onglet « Kréyòl an mwen » : un vide vertical d’environ 300px sépare la carte « Mots à Découvrir » de la section « Mots les plus utilisés », sans élément visible pour l’expliquer — probablement une marge/hauteur de conteneur mal calculée.
Aucun contentDescription n’existe dans l’ensemble de res/layout/*.xml (grilles de jeu, boutons icône, barres de progression). Un utilisateur avec lecteur d’écran ne peut identifier aucun élément interactif de l’application par son nom.
Trois libellés du jeu Mots Mélangés n’ont aucune couleur de texte définie et héritent d’une couleur par défaut illisible sur le fond clair (#F5F5F5) :
SettingsActivity.kt) : tvWordNumber (l.2749, « Mot 1/10 »), labelScrambled (l.2785, « Lettres disponibles : »), labelAnswer (l.2815, « Ta réponse : »)activity_word_scramble.xml) : mêmes trois libellés, lignes 58, 87, 107Le fait que le même bug existe indépendamment dans les deux implémentations illustre concrètement le risque de la duplication de code (constat §6). Comparer avec les éléments voisins qui, eux, définissent bien une couleur explicite (title l.2778 → #1976D2, tvScore → #4CAF50) : l’omission n’est pas un choix de design, c’est un oubli.
Color.parseColor("#...") en dur dans SettingsActivity.kt, alors qu’une palette complète existe dans colors.xml (bleu_caraibe, orange_coucher, vert_canne…) et n’est presque jamais référencée via @color/... ou R.color. Même pratique dans les layouts XML des jeux/dashboard.styles.xml/themes.xml — toutes les Activities déclarent android:theme="@style/Theme.AppCompat" brut ; la cohérence visuelle repose entièrement sur la discipline de chaque développeur à réécrire les bons hex à la main.activity_word_search.xml:5 → android:background="#1E1E1E" (sombre)activity_word_scramble.xml:5 → android:background="#F5F5F5" (clair)activity_vocabulary_stats.xml → également sombreWordSearchFragment, WordScrambleFragment) sont, eux, tous les deux en thème clair.VocabularyStatsActivity : la toolbar système affiche déjà une flèche retour + le titre « Mon Kreyòl », et le contenu de l’écran répète une seconde flèche « ← » suivie de « MON KREYÒL » en toutes lettres juste en dessous.RAPPORT_LINGUISTIQUE.md) est illustré par un émoji 🌍 (globe terrestre) sans rapport avec le thème.strings.xml quasi inutilisé (9 entrées) — tout le texte UI (dizaines de libellés + emoji) est écrit en dur dans le Kotlin/XML, aucun chemin de traduction possible, libellés dupliqués entre fragments embarqués et Activities standalone (déjà partiellement en train de diverger, cf. §5).MainActivity.kt + activity_main.xml + activity_main_tabs.xml : aucune référence dans le manifest, aucun autre fichier ne les utilise — entièrement mort.VocabularyStatsActivity : dashboard complet et fonctionnel (progression, Top 5, niveaux de maîtrise), mais inatteignable depuis l’UI réelle (cf. §2) — double maintenance avec StatsFragment sans bénéfice utilisateur actuel.| Priorité | Action | Effort |
|---|---|---|
| Quick win | Corriger le mojibake SettingsActivity.kt:2460 |
Trivial |
| Quick win | Remplacer "Version : 6.5.1" par BuildConfig.VERSION_NAME (SettingsActivity.kt:1245) |
Trivial |
| Quick win | Ajouter setTextColor(...) aux 3 libellés illisibles, dans les 2 fichiers concernés |
Trivial |
| Quick win | Fixer android:numColumns dans activity_word_search.xml pour refléter la grille 8×8 réelle |
Trivial |
| Quick win | Supprimer MainActivity.kt + activity_main.xml + activity_main_tabs.xml (code mort confirmé) |
Trivial |
| Chantier | Décider du sort de VocabularyStatsActivity : soit la relier réellement (bouton dans l’onglet Stats), soit la supprimer au profit de StatsFragment |
Moyen |
| Chantier | Dédupliquer les jeux : garder une seule implémentation par jeu (Fragment réutilisé dans l’Activity standalone, ou inversement) pour arrêter la divergence déjà observée | Important |
| Chantier | Unifier le thème clair/sombre entre tous les écrans de jeu/dashboard | Moyen |
| Chantier | Créer un themes.xml/styles.xml et migrer progressivement les Color.parseColor vers @color/... |
Important |
| Chantier | Implémenter ou retirer les boutons no-op (Indice, Thèmes, partage) du jeu Mots Mêlés standalone | Moyen |
| Chantier | Ajouter les contentDescription sur les éléments interactifs (accessibilité) |
Important |
| Chantier | Externaliser les libellés UI vers strings.xml (prérequis à toute traduction future) |
Important |
🗓️ Date du rapport : 09 juillet 2026
✅ Statut : Aucun lag perceptible mesuré (calcul des suggestions en dessous du plancher de mesure ADB, ~100 ms de bruit). Un bug de casse mineur identifié (majuscules intempestives sur la 1ʳᵉ suggestion d’un message). Les 3 trous de dictionnaire (
blòké,apeti/lapeti,nwit/lannwit) restent ouverts — voir le rapport du 10 juillet, qui confirme qu’ils persistent même après un enrichissement du dataset source.
Date : 09 juillet 2026
Heure : 15:47 – 16:08 (CEST)
Testeur : Claude Code (agent), à la demande de l’utilisateur
Version testée : 7.0.1 (versionCode 70001), compilée depuis main à jour
| Élément | Détail |
|---|---|
| Émulateur | AVD kreyol_test — Pixel 5, Android 14 (API 34), image google_apis x86_64 |
| Affichage | Fenêtre visible via WSLg (X11/Wayland) |
| APK | Potomitan_Kreyol_Keyboard_v7.0.1_debug_2026-07-09.apk, recompilé pour l’occasion |
| IME | com.potomitan.kreyolkeyboard/com.example.kreyolkeyboard.KreyolInputMethodServiceRefactored — activé et sélectionné par défaut |
| Application cible | Google Messages (com.google.android.apps.messaging), nouvelle conversation SMS vers un contact fictif 0690654321 |
L’émulateur n’a ni SIM ni réseau : les messages ne partent pas réellement, mais chaque envoi vide le champ de saisie et crée une bulle dans le fil, ce qui simule un véritable échange de messages continu (contexte n-gramme conservé d’un message à l’autre, comme dans une vraie conversation).
SuggestionEngine / InputProcessor.processKeyPress) ne réagit qu’aux appuis sur les vraies touches du clavier custom, pas à un adb shell input text qui contournerait entièrement la logique de composition de mot. Les coordonnées de chaque touche ont été calculées à partir des poids définis dans KeyboardLayoutManager.kt (getKeyWeight), puis validées par calibration empirique (frappe de lettres de contrôle et vérification du texte obtenu).displaySuggestions appelé avec ... du logcat — il reflète les suggestions affichées pour le dernier mot en cours de frappe.rapport_test_clavier_2026-07-09_screenshots/).| # | Phrase tapée | Texte final dans le champ | Suggestions affichées (dernier mot) |
|---|---|---|---|
| 1 | bèl bonjou | Bèl bonjou | 1 : bonjou |
| 2 | sa ou fè | Sa ou fè | 5 : fè, fèt, fèmé, fè-nou, fè-y |
| 3 | ka ou fè jòdi a | Ka ou fè jòdi a | 5 : an, adan, a-y, anba, asi |
| 4 | mwen la wi | Mwen la wi | 1 : wi |
| 5 | ou byen | Ou byen | 3 : byen, byendéfwa, byenmèsi |
| 6 | mèsi anpil | Mèsi anpil | 1 : anpil |
| 7 | an ka vini | An ka vini | 2 : vini, vini-zòt |
| 8 | ki lè i yé | Ki lè i yé | 5 : yé, yenki, yè, yépa, yen |
| 9 | ou vlé manjé | Ou vlé manjé | 2 : manjé, manjé-la |
| 10 | mwen fen | Mwen fen | 2 : fen, fenfon |
| 11 | an bwè an tigout dlo | An bwè an tigout dlo | 2 : dlo, dlo-la |
| 12 | koté ou yé | Koté ou yé | 5 : yé, yenki, yè, yépa, yen |
| 13 | an kaz mwen | An kaz mwen | 4 : mwen, mwenmenm, mwens, mwen-la |
| 14 | ou ka travay jòdi a | Ou ka travay jòdi a | 5 : an, adan, a-y, anba, asi |
| 15 | wi mwen ka travay | Wi mwen ka travay | 5 : travayè, travay, travayè-la, travay-la, travay-li |
| 16 | an ka alé lékòl | An ka alé lékòl | 2 : lékòl, lékòl-la |
| 17 | timoun yo la | Timoun yo la | 5 : la, lang, lavi, laplaj, larichès |
| 18 | gran moun ka palé | Gran moun ka palé | 4 : palé, palé-w, palé-la, palé-mwen |
| 19 | fanmi mwen la | Fanmi mwen la | 5 : la, lang, lavi, laplaj, larichès |
| 20 | an renmen’w | An renmen’w | 5 : wouvè, wayayay, woy, wi, woulé |
| 21 | sa ka fè plézi | Sa ka fè plézi | 1 : plézi |
| 22 | bon apeti | Bon apeti | 5 : avèti, apési, pati, péyi, pété |
| 23 | bon nwit | Bon nwit | 5 : nuit, ni, dwèt, wi, pit |
| 24 | dòmi byen | Dòmi byen | 3 : byen, byendéfwa, byenmèsi |
| 25 | rété la | Rété la | 5 : la, lang, lavi, laplaj, larichès |
| 26 | vini isi | Vini isi | 2 : isidan, isi |
| 27 | an ka vann fwi | An ka vann fwi | 2 : fwi, fwitaj |
| 28 | jaden an bel | Jaden an bel | 4 : bèl, bèl-la, bèlté, bel |
| 29 | soley ka bril jòdi a | Soley ka bril jòdi a | 5 : an, adan, a-y, anba, asi |
| 30 | lapli ka tonbé | Lapli ka tonbé | 1 : tonbé |
| 31 | lanmè bel toubannman | Lanmè bel toubannman | 1 : toupannan |
| 32 | an ka péché pwason | An ka péché pwason | 1 : pwason |
| 33 | sé bon manjé | Sé bon manjé | 2 : manjé, manjé-la |
| 34 | dokan an ouvè | Dokan an ouvè | 1 : ouvè |
| 35 | bous mwen vid | Bous mwen vid | 1 : vid |
| 36 | lajan ka manké | Lajan ka manké | 1 : manké |
| 37 | travay la red | Travay la red | 5 : rèd, rédé, rédé-yo, rédé-y, red |
| 38 | an fatigé anpil | An fatigé anpil | 1 : anpil |
| 39 | an bizwen répozé | An bizwen répozé | 5 : dépozé, réponn, pozé, opozé, répondè |
| 40 | telefòn mwen kasé | Telefòn mwen kasé | 1 : kasé |
| 41 | voiti an pann | Voiti an pann | 5 : pannan, pann, pannansitan, pannyé, pannyé-la |
| 42 | wout la blòké | Wout la blòké | 5 : baké, blo, blé, blagé, lòdè |
| 43 | lékòl fini | Lékòl fini | 1 : fini |
| 44 | lévé bonnè | Lévé bonnè | 1 : bonnè |
| 45 | kouché ta | Kouché ta | 5 : tan, tann, tanbou, ta, tata |
| 46 | fè cho jòdi a | Fè cho jòdi a | 5 : an, adan, a-y, anba, asi |
| 47 | van ka soufflé | Van ka soufflé | 3 : souflé, soufè, souplé |
| 48 | mizik la bon | Mizik la bon | 5 : bon, bonjou, bonmaten-la, bondyé, bonswa |
| 49 | dansé kréyòl | Dansé kréyòl | 4 : kréyòl, kréyòl-gwadloup, kréyol, kréyol-la |
| 50 | viv kréyòl gwadloup | Viv kréyòl gwadloup | 2 : gwadloup, gwadloupéyen |
é/è/ò gérées sans souci), et la majorité des suggestions sont pertinentes et utiles (bonjou, manjé, travay, lékòl, kréyòl, etc.).kréyòl) : le moteur suggère à la fois kréyòl et kréyol, bonne illustration du matching tolérant aux accents (AccentTolerantMatcher).a jamais suggéré seul (phrases 3, 14, 29, 46 — “…jòdi a”) : le moteur propose systématiquement an, adan, a-y, anba, asi mais jamais a lui-même, alors qu’il vient d’être tapé tel quel. À vérifier si a est absent du dictionnaire ou explicitement filtré (mot trop court ?).avèti, apési, pati, péyi, pété, aucune ne correspond vraiment à « appétit ». Confirmé : ni apeti ni lapeti ne sont dans le dictionnaire.baké, blo, blé, blagé, lòdè, sans jamais proposer blòké alors qu’il a été tapé lettre par lettre. Confirmé : ni blòké ni aucune variante blok* ne figurent dans creole_dict.json — trou de dictionnaire, pas un bug de scoring.nuit, ni, dwèt, wi, pit), mélangeant un mot français (nuit) à des correspondances Levenshtein peu convaincantes. Confirmé : ni nwit ni lannwit (forme kréyòl courante) ne sont dans le dictionnaire.toubannman déclenche une correction fuzzy vers toupannan (1 seule suggestion, cohérent avec le mécanisme de tolérance aux fautes documenté dans LevenshteinDistanceTest).manjé-la, palé-mwen, mwen-la, travay-li), reflet du corpus littéraire kréyòl utilisé pour construire le dictionnaire.Le pipeline de suggestions fonctionne de façon fiable sur l’ensemble des 50 phrases testées, avec une bonne couverture du vocabulaire courant et une gestion correcte des accents. Piste d’amélioration concrète identifiée : enrichir creole_dict.json avec blòké, apeti/lapeti et nwit/lannwit, absents du dictionnaire actuel et donc mal servis par le fallback Levenshtein.
Second passage sur le même APK 7.0.1, cette fois avec l’émulateur affiché en fenêtre (WSLg) et deux angles d’analyse supplémentaires demandés : la rapidité des suggestions et la progression caractère par caractère de la pertinence sur la première phrase. Même méthodologie de frappe (taps réels sur le clavier custom), nouveau fil de conversation avec un contact fictif différent (0690999888) pour repartir sur un historique n-gramme propre.
Pour chaque phrase, le script clique sur la dernière touche du dernier mot en notant l’horodatage juste avant le tap (horloge hôte Python), puis sonde le logcat de l’émulateur (adb logcat -v epoch) jusqu’à l’apparition de la ligne displaySuggestions appelé avec ... correspondante, et calcule le delta.
Limite importante : cette mesure inclut l’aller-retour ADB (hôte WSL → émulateur) en plus du calcul réel côté clavier, et dépend de la synchronisation d’horloge hôte/émulateur. Sur les 50 phrases mesurées :
Une partie des valeurs est négative, ce qui est physiquement impossible pour un vrai délai de calcul — cela révèle simplement que le bruit de mesure (gigue d’horloge + aller-retour ADB, de l’ordre de ±100 ms) est supérieur au temps de calcul réel des suggestions. Autrement dit : le calcul lui-même est trop rapide pour être isolé par cette méthode de mesure côté hôte. C’est en soi une conclusion positive et cohérente avec les correctifs de performance déjà appliqués le 3 juillet 2026 (normalisation précalculée du dictionnaire, arrêt anticipé de la recherche préfixe — voir le rapport d’audit du moteur de suggestions ci-dessus, §4.1 et §8) : aucun ralentissement perceptible n’a été observé sur les 3 680 mots du dictionnaire, y compris sur les phrases déclenchant un fallback Levenshtein (ex. phrase 31, toubannman, delta mesuré : +136 ms — dans le même ordre de grandeur bruité que le reste).
Seul un point dépasse nettement le lot : la frappe de l’espace après « Bèl » (voir tableau de progression ci-dessous) a montré +319 ms — à surveiller si le phénomène se reproduit sur d’autres mesures, mais un seul échantillon ne permet pas de conclure à une régression.
Verdict rapidité : aucun lag perceptible constaté sur l’émulateur (Pixel 5 / Android 14) — les suggestions sont prêtes essentiellement au moment où la frappe est traitée, sans décalage visible à l’écran ni dans les captures.
Contrairement au reste du test (qui ne capture que l’état final des suggestions par mot), la première phrase a été tapée avec une capture après chaque caractère individuel, pour observer concrètement comment les propositions évoluent pendant la frappe :
| Caractère tapé | Champ | Latence | Suggestions affichées |
|---|---|---|---|
b |
B | +18 ms | BA, BYEN, BÈL, BITEN, BON |
è |
Bè | -20 ms | Bèl, Bèf, Bésé, Bèf-la, Bennyé |
l |
Bèl | +3 ms | Bèl, Bèl-la, Bèlté, Bel |
| ` ` | Bèl | +319 ms | |
b |
Bèl b | -25 ms | ba, byen, bèl, biten, bon |
o |
Bèl bo | +14 ms | bon, bout, bouch, boug, bouko |
n |
Bèl bon | +36 ms | bon, bonjou, bonmaten-la, bondyé, bonswa |
j |
Bèl bonj | +45 ms | bonjou |
o |
Bèl bonjo | +17 ms | bonjou |
u |
Bèl bonjou | -39 ms | bonjou |
Observations :
ba, byen, bèl, biten, bon), conforme au quick win « suggestions dès 1 lettre » du 3 juillet 2026 (MIN_WORD_LENGTH = 1).BA, BYEN, BÈL, BITEN, BON) au lieu d’une casse capitalisée normale (Ba, Byen, Bèl...). Dès le deuxième caractère (« Bè »), la casse redevient correcte (Bèl, Bèf, Bésé...). Cela suggère que applyCasingPattern() traite un mot d’une seule lettre majuscule comme un motif « tout en majuscules » plutôt que « première lettre capitalisée » — cas limite mineur mais visible dès la toute première frappe d’un message.bonjou), et elle reste stable jusqu’à la fin du mot — bon signal de précision une fois le préfixe suffisamment discriminant.bon, bonjou, bonmaten-la) alors que le logcat interne en scorait 5 (+ bondyé, bonswa) — cohérent avec la limite documentée dans CLAUDE.md (« 3 suggestions affichées, 5 scorées en interne »).Captures d’écran de la progression : progression_05.png (« Bèl b »), progression_07.png (« Bèl bon »), progression_10.png (« Bèl bonjou »).
Les deux nouveaux axes confirment et complètent la première passe :
applyCasingPattern().blòké, apeti/lapeti, nwit/lannwit) restent d’actualité sur ce second passage (mêmes suggestions non pertinentes reproduites à l’identique).🗓️ Date du rapport : 10 juillet 2026
✅ Statut : Deux cycles d’enrichissement le 10/07 (703 puis 2383 textes), tous deux confirmés sans régression (+363 puis +868 mots). Les 3 trous de vocabulaire déjà connus (
blòké,apeti/lapeti,nwit/lannwit) persistent aux deux cycles — même un enrichissement ciblé (phrases de sécurité) n’a pas suffi car ces mots précis n’étaient dans aucun texte ajouté. Pollution par noms propres (wilyàm,ana,kévin) détectée au second cycle, à corriger.
Date : 10 juillet 2026
Heure : 11:41 – 12:06 (CEST)
Testeur : Claude Code (agent), à la demande de l’utilisateur
Contexte : l’utilisateur a enrichi le dataset Hugging Face source de vérité et l’a rendu public (gated: false), puis a demandé de mesurer concrètement l’impact de cet enrichissement sur le dictionnaire embarqué et sur la qualité des suggestions.
| Avant (dataset initial) | Après (dataset enrichi) | Delta | |
|---|---|---|---|
| Textes du corpus HF | 427 | 703 | +276 (+64,6%) |
| Mots du dictionnaire | 3 680 | 4 043 | +363 (+9,9%) |
| Mots supprimés | — | — | 0 |
| Prédictions n-grammes | 3 582 | 3 902 | +320 (+8,9%) |
Malgré une croissance du corpus de +64,6%, le dictionnaire ne croît que de +9,9% : effet attendu de la loi de Heaps — les mots les plus fréquents du kréyòl étaient déjà couverts par le corpus initial, et l’enrichissement apporte surtout du vocabulaire plus rare (voir échantillon ci-dessous).
Dictionnaires/KreyolComplet.py (pipeline existant, non modifié) contre le dataset HF fraîchement enrichi.android_keyboard/app/src/main/assets/creole_dict.json et creole_ngrams.json).kreyol_test (Pixel 5, Android 14), émulateur affiché à l’écran (WSLg).adb logcat -v epoch), dans une nouvelle conversation Google Messages (0690777000).results2.json (09/07, avant enrichissement) et results3.json (10/07, après enrichissement).Le rapport du 09/07 avait identifié 3 mots/orthographes absents du dictionnaire : blòké, apeti/lapeti, nwit/lannwit. Vérification directe dans le nouveau creole_dict.json :
| Mot recherché | Présent après enrichissement ? |
|---|---|
blòké |
❌ Absent |
apeti |
❌ Absent |
lapeti |
❌ Absent |
nwit |
❌ Absent |
lannwit |
❌ Absent |
nuit (orthographe française) |
✅ Présent (déjà avant) |
Confirmé également en conditions réelles sur l’émulateur (captures d’écran) :
avèti, apési, pati, péyi, pété (identique au 09/07, apeti toujours absent)nuit, ni, dwèt, wi, pit (identique au 09/07)baké, blo, blé, blagé, lòdè (identique au 09/07, blòké toujours absent)Conclusion : les 276 textes ajoutés au corpus ne contiennent pas ces mots/graphies précis. L’enrichissement quantitatif du corpus ne garantit pas la couverture de vocabulaire spécifique déjà identifié comme manquant — il faudrait soit cibler l’ajout de textes contenant ces mots, soit les ajouter manuellement au dictionnaire.
43 phrases sur 50 ont des suggestions finales strictement identiques. 7 phrases montrent une évolution (marquées 🔄), toutes dues à l’arrivée de nouveaux mots dans le dictionnaire qui viennent concurrencer les suggestions existantes :
| # | Phrase | Suggestions avant (09/07) | Suggestions après (10/07) |
|---|---|---|---|
| 10 | mwen fen | fen, fenfon | fen, fenfon, fenyan |
| 11 | an bwè an tigout dlo | dlo, dlo-la | dlo, dlo-la, dlo-a |
| 17 | timoun yo la | la, lang, lavi, laplaj, larichès | la, lang, lavi, lapenn, larichès |
| 19 | fanmi mwen la | la, lang, lavi, laplaj, larichès | la, lang, lavi, lapenn, larichès |
| 20 | an renmen’w | wouvè, wayayay, woy, wi, woulé | (voir note ci-dessous) |
| 25 | rété la | la, lang, lavi, laplaj, larichès | la, lang, lavi, lapenn, larichès |
| 28 | jaden an bel | bèl, bèl-la, bèlté, bel | bèl, bèl-la, bèlté, bel, belmè |
Note sur la phrase 20 : le script de mesure a capturé 0 suggestion juste après le dernier caractère (w), ce qui ressemblait à une régression. Un re-test manuel immédiat a montré que les 5 suggestions (wouvè, wayayay, woy, wi, woulé) réapparaissent bien, identiques à avant — le logcat montre une brève fenêtre à 0 suggestion juste après la finalisation du mot précédent (apostrophe), que le script a capturée par malchance avant l’arrivée de la vraie suggestion. Artefact de mesure, pas une régression du moteur.
Aucune suggestion existante n’a disparu ou changé d’ordre parmi les 4 premiers rangs sur l’ensemble des 50 phrases — l’enrichissement n’a eu qu’un effet marginal et additif (nouveaux mots insérés en fin de liste de suggestions), sans perturber les suggestions déjà correctes.
| Avant (09/07) | Après (10/07) | |
|---|---|---|
| Latence moyenne | -40.7ms | -83.8ms |
| Latence médiane | -61.4ms | -114.2ms |
| Écart-type | 54.4ms | 105.8ms |
Les deux séries restent négatives en moyenne, ce qui — comme documenté le 09/07 — signifie que le bruit de mesure (aller-retour ADB + désynchronisation d’horloge hôte/émulateur, de l’ordre de ±100 à 200ms) domine largement le temps de calcul réel des suggestions : celui-ci reste trop rapide pour être isolé par cette méthode, avant comme après l’enrichissement. L’écart-type plus élevé après enrichissement (105.8ms vs 54.4ms) est cohérent avec un bruit de mesure plus variable ce jour-là (l’émulateur a par ailleurs affiché deux fois le dialogue « System UI isn’t responding » au démarrage), pas avec un ralentissement réel du moteur — le dictionnaire (+363 mots) et les n-grams (+320 prédictions) restent des structures en mémoire minuscules pour un appareil mobile moderne.
Aucun ralentissement perceptible attribuable à l’enrichissement du dictionnaire.
Captures conservées pour les phrases 1, 2, 3, 10, 20, 22, 23, 30, 40, 42, 50, ainsi que la progression caractère par caractère de « Bèl bonjou » (phrases 5 à 10) — dossier rapport_test_dataset_enrichi_2026-07-10_screenshots/. Les phrases 22, 23 et 42 (les 3 mots-trous) ont été spécifiquement capturées pour ce test.
Le bug de casse déjà documenté le 09/07 (première suggestion en MAJUSCULES sur le tout premier caractère d’un message, ex. VWÈ, VIKTÒ, VLÉ pour « van ka soufflé ») a été observé à nouveau à l’identique — non lié à l’enrichissement, toujours ouvert dans applyCasingPattern().
L’enrichissement du dataset (427 → 703 textes, +64,6%) a produit une croissance mesurée mais réelle du dictionnaire embarqué (+363 mots, +9,9%) et des n-grams (+320 prédictions, +8,9%), sans aucune régression : 43 des 50 phrases de test ont des suggestions identiques à avant, les 7 phrases modifiées ne font qu’ajouter de nouvelles options sans supplanter les suggestions correctes déjà en place, et aucun ralentissement n’est mesurable.
En revanche, les 3 trous de vocabulaire identifiés le 09/07 persistent à l’identique (blòké, apeti/lapeti, nwit/lannwit) : l’enrichissement générique du corpus n’a pas ciblé ces mots précis. Pour les combler, il faudrait soit ajouter spécifiquement des textes contenant ce vocabulaire courant au dataset source, soit les insérer manuellement dans creole_dict.json.
Plutôt que de continuer à enrichir le dataset de façon générique, une liste de 48 mots courants confirmés absents du dictionnaire (vérifiés programmatiquement, organisés par thème : verbes, famille, nourriture, météo, temps, émotions, adjectifs, objets du foyer, santé, école, animaux) a été établie pour orienter un enrichissement ciblé — voir Dictionnaire des trous de vocabulaire — kréyòl Gwadloupéyen. Cette liste part de connaissances linguistiques générales (pas d’un corpus) et doit être validée par des locuteurs natifs avant toute intégration ; elle inclut la marche à suivre pour transformer ces mots en textes puis en enrichissement effectif du dataset POTOMITAN/PawolKreyol-gfc.
Un lot de 1 680 phrases (curées séparément à partir d’un dataset de traduction privé, dont 53 phrases de sécurité/premiers secours) a été ajouté au dataset PawolKreyol-gfc le jour même (703 → 2383 textes). Le pipeline a été rejoué, l’APK reconstruit, et le même protocole de 50 phrases rejoué une troisième fois pour mesurer l’impact de ce second cycle.
Résumé : dictionnaire 4 043 → 4 911 mots (+868, +21,5%), n-grams 3 902 → 4 232 prédictions (+330, +8,5%), 0 mot supprimé. Un enrichissement ciblé fait donc croître le dictionnaire proportionnellement bien plus qu’un enrichissement générique (+21,5% de mots pour ce cycle contre +9,9% lors du premier).
Les 3 trous historiques persistent une troisième fois (blòké, apeti/lapeti, nwit/lannwit) : les 1 680 phrases ajoutées ne contenaient tout simplement pas ces graphies précises. Enseignement confirmé : cibler un thème (sécurité, quotidien) ne suffit pas à combler un trou de vocabulaire précis — il faut que le mot exact apparaisse dans un texte ajouté.
En revanche, une partie du vocabulaire des phrases de sécurité est bien détectable : blesé, doktè, rimèd, vitman, évakwasyon sont désormais dans le dictionnaire — preuve que l’approche fonctionne quand le mot cible est effectivement présent dans le texte source.
⚠️ Nouveau problème détecté : des noms de personnages fictifs issus de dialogues pédagogiques (wilyàm, ana, kévin) sont désormais dans le dictionnaire et apparaissent en concurrence avec du vocabulaire réel dans les suggestions (ex. « mwen la wi » propose désormais wi, wilyàm, wilyam, wifi). À filtrer avant un prochain cycle d’enrichissement à partir de contenu dialogué.
Rapport complet avec tableau des 11 phrases modifiées et détails méthodologiques : rapport_test_dataset_enrichi_2026-07-10.md.
🗓️ Date du rapport : 10 juillet 2026
✅ Statut : Release publiée, build et signature réussis, 4 artefacts disponibles (APK/AAB × debug/release).
Suite aux deux cycles d’enrichissement documentés ci-dessus, la version 7.0.2 (versionCode 70002) a été publiée le 10 juillet 2026 : voir la release GitHub.
blesé, doktè, rimèd, évakwasyon, vitman…)| Fichier | Usage |
|---|---|
KreyolKeyboard-Release-v7.0.2.aab |
À uploader sur Google Play Console |
KreyolKeyboard-Release-v7.0.2.apk |
Installation directe, signé production |
KreyolKeyboard-Debug-v7.0.2.aab / .apk |
Build de développement |
Les notes de version ont été générées automatiquement depuis android_keyboard/CHANGELOG.md par le workflow build-apk.yml (job create-release, déclenché par le tag v7.0.2), et sont directement réutilisables pour la fiche Google Play Store.
Documentées en détail dans les rapports ci-dessus, non corrigées dans le code à ce stade :
blòké, apeti/lapeti, nwit/lannwit)wilyàm, ana, kévin) issus de contenu pédagogique non filtré🗓️ Date du rapport : 10 juillet 2026
✅ Statut : 76,9 % des messages envoyés parfaitement corrects malgré 8,7 % de mots fautés délibérément ; 98,8 % d’exactitude caractère par caractère. Aucun ralentissement mesurable. Deux artefacts isolés de l’automatisation identifiés et documentés (pas des défauts du clavier).
Date : 10 juillet 2026
Heure : 21:38 – 22:10 (CEST)
Testeur : Claude Code (agent), à la demande de l’utilisateur
Version testée : 7.0.2 (versionCode 70002)
⚠️ Méthodologie et limites du contenu : le dialogue ci-dessous est un texte imaginaire écrit à partir de connaissances générales du kréyòl guadeloupéen (pas un corpus authentique ni relu par un locuteur natif). Il sert uniquement de matériau de test pour mesurer le comportement du clavier — pas une référence linguistique.
Tous les tests précédents (50 phrases, 09-10 juillet) simulaient une frappe parfaite : chaque caractère de la phrase cible tapé sans erreur, sans jamais utiliser les suggestions ni corriger quoi que ce soit. Cette simulation va plus loin : elle imite un comportement humain réaliste — fautes de frappe sur des touches physiquement voisines, corrections au backspace, et sélection de mots dans la barre de suggestions (autocomplétion et récupération après faute) — pour mesurer des statistiques plus représentatives d’un usage réel.
Limite technique : un seul appareil est disponible (pas de second téléphone pour l’autre ami). Le dialogue est donc tapé et envoyé intégralement depuis le même champ de composition, dans l’ordre, comme une suite de messages sortants — les deux locuteurs sont distingués ci-dessous (« Ami A » / « Ami B ») mais apparaissent tous comme des messages envoyés sur l’écran du téléphone.
Environnement de test : émulateur Android, AVD kreyol_test.
displaySuggestions) au moment où il est complété — mesure à la granularité du mot, pas seulement de la phrase., . ') accessible sur le clavier alphabétique a été utilisée.Les messages marqués ⚠️ ont été envoyés avec au moins un caractère différent du texte visé (faute non corrigée) — voir le détail dans la section Résultats.
| Métrique | Valeur |
|---|---|
| Messages envoyés | 134 |
| Mots tapés | 982 |
| Mots avec faute injectée | 85 (8,7 %) |
| Messages envoyés identiques au texte visé | 103 / 134 (76,9 %) |
| Exactitude au niveau caractère (Levenshtein) | 98,79 % |
| Distance de Levenshtein moyenne par message | 0,39 caractère |
| Pire cas | 4 caractères d’écart (sur 134 messages) — message « Chak chanson i ka touché kè mwen. » |
Malgré des fautes de frappe délibérément injectées sur 8,7 % des mots, plus de 3 messages sur 4 arrivent parfaitement corrects grâce aux corrections manuelles et aux suggestions — un signal positif sur l’utilité réelle du correcteur.
| Action | Nombre | % |
|---|---|---|
| Tapé intégralement (sans incident) | 794 | 80,9 % |
| Autocomplétion tapée avec succès | 51 | 5,2 % |
| Autocomplétion tentée mais mot absent des suggestions | 52 | 5,3 % |
| Faute corrigée au backspace | 36 | 3,7 % |
| Faute laissée telle quelle (délibéré) | 18 | 1,8 % |
| Faute « récupérée » via une suggestion | 16 | 1,6 % |
| Récupération par suggestion tentée mais échouée | 15 | 1,5 % |
Sur les 103 mots où une tentative d’autocomplétion a eu lieu (suggestion vérifiée à mi-frappe), le mot visé était disponible dans la liste et a été tapé 49,5 % du temps. Dans l’autre moitié des cas, le mot n’était pas encore proposé au point de vérification et la frappe manuelle a été terminée normalement.
Sur les 85 mots avec une faute injectée (distribution observée sur cet échantillon : substitution par touche voisine géométrique 36 %, omission 39 %, inversion 25 % — à comparer à la cible du modèle 50 % / 30 % / 20 %, l’écart restant dans la variance attendue pour n=85) :
| Devenir | Nombre | % |
|---|---|---|
| Corrigée au backspace (l’utilisateur se corrige lui-même) | 36 | 42,4 % |
| Récupérée via une suggestion (le clavier « rattrape » la faute) | 16 | 18,8 % |
| Suggestion tentée mais le mot visé n’apparaissait pas → envoyée telle quelle | 15 | 17,6 % |
| Envoyée telle quelle (l’utilisateur ne corrige pas, délibéré) | 18 | 21,2 % |
| Total envoyé avec la faute encore présente | 33 | 38,8 % |
Sur les 31 tentatives de récupération via suggestion (36,5 % des 85 fautes), le mot visé apparaissait effectivement dans la liste 51,6 % du temps — le correcteur flou (distance de Levenshtein) rattrape environ une faute sur deux qu’on lui présente, le reste nécessitant une correction manuelle.
| Valeur | |
|---|---|
| Mesures (une par mot minimum) | 1068 |
| Moyenne | -127,0 ms |
| Médiane | -180,6 ms |
| Écart-type | 180,4 ms |
Comme lors des tests précédents, les valeurs négatives confirment que le calcul des suggestions reste en dessous du plancher de mesure (bruit ADB/horloge hôte-émulateur) — aucun ralentissement perceptible, y compris avec la charge supplémentaire des fautes/corrections/taps de suggestion.
Deux cas isolés parmi les 134 messages montrent une perte ou un déplacement de caractère (espace manquant, lettre dupliquée) sans qu’aucune faute n’ait été injectée à ce mot d’après le journal d’actions — par exemple « an ka » envoyé comme « a nka », ou « konprann ou » envoyé comme « konpranou ». Ces deux cas coïncident avec des séquences de frappe rapides sur ~40 minutes d’exécution automatisée ; l’hypothèse la plus probable est un événement tactile émis par le harnais légèrement plus vite que l’IME ne pouvait le committer de façon fiable — un artefact connu de l’automatisation par adb input tap sur des sessions longues, pas un défaut du moteur de suggestions. Ces deux cas sont inclus tels quels dans les statistiques d’exactitude ci-dessus (ils n’ont pas été retirés).
Une capture a été prise tous les ~15-20 messages, plus une capture montrant la barre de suggestions active en cours de frappe (suggestions_actives.png) — dossier rapport_simulation_frappe_humaine_2026-07-10_screenshots/.
L’usage des suggestions par le simulateur (colonne « Autocomplétion tapée avec succès », 51 mots sur 982) est peu visible à l’œil nu pendant un test de 40 minutes qui défile vite — il ne concerne qu’environ 1 mot sur 7-8, et l’action est instantanée. Voici une démonstration ciblée, rejouée manuellement sur l’émulateur juste après le test, reproduisant exactement un des 51 cas réels du journal (message 5, mot « dòmi », an bizwen repozé → suivi immédiatement du mot « dòmi » dans le message suivant) :
| Avant le tap | Après le tap |
|---|---|
![]() |
![]() |
Champ : Dòm (3 lettres tapées). Barre de suggestions : Dòmi, Domino, Dominasyon. |
Un seul tap sur la 1ʳᵉ suggestion → champ : Dòmi (mot complété + espace inséré automatiquement), nouvelle barre de suggestions déjà prête pour le mot suivant. |
Cette simulation de frappe humaine — la première de cette série de tests à inclure fautes, corrections et usage réel des suggestions plutôt qu’une frappe parfaite — montre un clavier globalement robuste face à des erreurs réalistes : 76,9 % des messages arrivent parfaitement corrects malgré 8,7 % de mots fautés, et l’exactitude caractère par caractère atteint 98,8 %. La correction floue (Levenshtein) rattrape environ une faute sur deux quand on la sollicite, et l’autocomplétion est adoptée dans un cas sur deux quand le mot visé est disponible. Aucun ralentissement mesurable n’a été observé malgré la charge de test nettement supérieure aux passages précédents (982 mots avec mesure systématique, contre 50 phrases mesurées ponctuellement).
Deux artefacts isolés de l’automatisation elle-même (perte/déplacement de caractère sans faute injectée) ont été identifiés et documentés plutôt que masqués — un signal de fiabilité de la méthode de test autant que du clavier.
Date : 11 juillet 2026
Heure : 12:08 – 12:46 (CEST)
Testeur : Claude Code (agent, modèle Fable 5), à la demande de l’utilisateur
Version testée : 7.0.2 (versionCode 70002)
Environnement : émulateur Android, AVD kreyol_test, fenêtre affichée à l’écran (WSLg) pour observation en direct ; conversation SMS dédiée dans Google Messages.
⚠️ Méthodologie et limites du contenu : le dialogue est le même texte imaginaire que celui du rapport du 10 juillet (écrit à partir de connaissances générales du kréyòl guadeloupéen, non relu par un locuteur natif). Il sert uniquement de matériau de test — pas une référence linguistique.
En observant la simulation « frappe humaine réaliste » à l’écran, l’utilisateur a constaté que les taps de suggestion étaient quasiment invisibles : dans ce mode, seul ~1 mot sur 7-8 est committé via la barre de suggestions, et le tap est instantané. Ce nouveau passage inverse le principe : l’utilisateur simulé ne termine jamais un mot à la main. Pour chaque mot, il tape les lettres une à une et, dès que le mot visé apparaît parmi les 3 suggestions affichées, il tape dessus. La frappe intégrale n’arrive que si le clavier ne propose jamais le mot — ce cas est compté à part et constitue une mesure directe de la couverture du dictionnaire.
C’est aussi le protocole le plus exigeant pour le moteur de suggestions : il est interrogé à chaque lettre (2 123 lectures sur ce passage, contre 1 068 sur le passage réaliste du 10 juillet).
Note de traçabilité : une réplique du passage « réaliste » du 10 juillet (même dialogue, nouvelle graine) avait été lancée en début de séance, puis interrompue à la demande de l’utilisateur après 43 messages au profit du présent mode ; aucune statistique n’en a été conservée.
displaySuggestions) ; si le mot visé (comparaison insensible à la casse, accents compris) figure parmi les 3 chips affichés, le chip est tapé — le clavier committe alors le mot suivi d’un espace automatique.| Métrique | Valeur |
|---|---|
| Messages envoyés | 134 |
| Mots traités | 982 |
| Mots committés par tap de suggestion | 806 (82,1 %) |
| … dont avant la fin du mot (vraie économie) | 667 (67,9 % des mots) |
| … dont seulement à la dernière lettre | 139 |
| Mots jamais proposés (frappe intégrale forcée) | 176 (17,9 %) |
| Lettres tapées | 2 142 / 3 196 (67,0 %) |
| Lettres économisées | 1 054 (33,0 %) — plus les espaces, offerts par le commit |
Un utilisateur qui s’appuie exclusivement sur les suggestions économise donc un tiers des frappes de lettres, et la quasi-totalité des espaces.
Sur les 667 mots attrapés avant la fin de frappe :
| Lettres tapées avant apparition | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| Mots | 362 | 171 | 88 | 38 | 7 | 1 |
Médiane : 1 lettre. Plus de la moitié des mots attrapés le sont dès la première lettre — l’effet combiné des fréquences du dictionnaire et du modèle n-gram contextuel.
| 1ᵉʳ chip | 2ᵉ chip | 3ᵉ chip |
|---|---|---|
| 468 (58,1 %) | 244 (30,3 %) | 94 (11,7 %) |
Le classement est bon : quand le mot est disponible, il est en tête plus d’une fois sur deux.
| Longueur | Proposé / total | Taux |
|---|---|---|
| 1 lettre | 0 / 44 | 0 % (jamais suggérés — voir ci-dessous) |
| 2 lettres | 365 / 389 | 93,8 % |
| 3 lettres | 153 / 173 | 88,4 % |
| 4 lettres | 157 / 174 | 90,2 % |
| 5 lettres | 69 / 97 | 71,1 % |
| 6 lettres | 48 / 78 | 61,5 % |
| 7-8 lettres | 14 / 24 | 58,3 % |
| 9-10 lettres | 0 / 3 | 0 % |
Les mots d’une lettre (i, a, é — 44 occurrences) ne sont jamais proposés : ils sont absents du dictionnaire et, une fois la lettre tapée, il n’y a de toute façon plus rien à compléter. Pour un utilisateur « strictement suggestions », ce sont les seuls mots où l’espace doit être tapé à la main systématiquement.
| Métrique | Brute | Insensible à la casse |
|---|---|---|
| Messages identiques au texte visé | 47 / 134 (35,1 %) | 101 / 134 (75,4 %) |
| Exactitude caractère (Levenshtein) | 95,08 % | 97,44 % |
L’écart spectaculaire entre les deux colonnes vient d’un seul phénomène, détaillé ci-dessous : 54 messages (40,3 %) ne diffèrent du texte visé que par la casse du premier mot.
Quand le premier mot d’un message est committé par tap alors que le shift automatique est encore actif (une seule lettre tapée, en majuscule), applyCasingPattern() généralise la casse du préfixe « 100 % majuscule » à toute la suggestion : Bèl → BÈL, An → AN, Nou → NOU, Mwen → MWEN… Le phénomène a touché 54 messages sur 134. Un préfixe d’une seule lettre majuscule devrait être traité comme une casse de type titre (première lettre seulement), pas comme du tout-majuscules.
176 mots n’ont jamais été proposés. La plupart sont réellement absents du dictionnaire, mais 5 mots présents dans creole_dict.json ne sont jamais apparus dans le top 3, éjectés par des concurrents plus fréquents via la correspondance tolérante aux accents :
| Mot tapé (fréq. dict) | Top 3 affiché après frappe complète |
|---|---|
fo (60) |
fò, fòs, fon |
bò (72) |
bon, bout, bouch |
red (15) |
rèd, rédé, rédé-yo (red classé 5ᵉ en interne) |
jòdi (3), soley (2) |
variantes plus fréquentes |
Le cas bò est le plus parlant : l’utilisateur a explicitement tapé le ò accentué, mais les mots en bo-/bon- non accentués, plus fréquents, remplissent les 3 chips. Une priorité de rang à la correspondance exacte du préfixe (accents compris) corrigerait les cinq cas d’un coup.
renmen (6 occ.), konsè (5), démen (5), fatigé (4), sérié (3), nòwmal (3), aswè (3), ainsi que les mots-outils d’une lettre i, é, a. Rappel : le dictionnaire étant régénéré depuis le dataset Hugging Face par la CI, l’enrichissement durable passe par le dataset (les ajouts manuels dans le JSON seraient écrasés).
| Valeur | |
|---|---|
| Mesures (une par lettre tapée) | 2 123 |
| Moyenne | 27,6 ms |
| Médiane | -38,0 ms |
| Écart-type | 223,8 ms |
Toujours sous le plancher de mesure (bruit ADB/horloge hôte-émulateur), alors que ce mode sollicite le moteur à chaque lettre — soit le double de lectures du passage du 10 juillet. Aucun ralentissement observé sur les ~40 minutes.
33 messages diffèrent du texte visé au-delà de la casse. Trois familles, tirées de la comparaison texte envoyé / texte visé :
ka, mwen ×3, ni, gwadloup…) : le tap sur le chip est parti pendant un rafraîchissement de la barre et n’a rien committé, le harnais croyant le mot inséré.pou ou → pou ouvriyé, té → tout, lwen → lè…) : la liste s’est rafraîchie entre la lecture du logcat et le tap, un autre mot occupait la position visée.an pi → anpi, ka alé → kalé…) : espace automatique ou manuel perdu dans les mêmes conditions de vitesse.Ces races sont un artefact de l’automatisation (taps à ~100 ms du rafraîchissement, 806 taps de chips contre 67 au passage réaliste, d’où une exposition ~12× supérieure — 2 anomalies le 10 juillet, ~38 ici, ordres de grandeur cohérents). À noter tout de même : un humain rapide peut rencontrer la variante « mauvais mot committé » dans la vraie vie (tap au moment où la barre se rafraîchit) — un délai minimal de stabilité de la barre avant prise en compte du tap pourrait la neutraliser. Tous ces cas sont inclus dans les statistiques ci-dessus, non retirés.
Une capture tous les ~15-20 messages (12 au total, human3_msg_*.png) — dossier rapport_simulation_suggestions_uniquement_2026-07-11_screenshots/. Contrairement au passage réaliste, chaque mot de ce passage est visible à l’écran en train d’être committé depuis la barre de suggestions.
En usage « strictement suggestions », le Klavyé Kréyòl committe 82 % des mots par tap et fait économiser un tiers des frappes de lettres (plus tous les espaces), avec un mot visé disponible dès la première lettre dans plus de la moitié des cas attrapés en cours de frappe et classé en 1ᵉʳ chip 58 % du temps. La latence reste indétectable malgré une sollicitation du moteur à chaque lettre.
Le protocole a surtout révélé deux défauts actionnables du moteur — la casse tout-majuscules appliquée aux suggestions committées sous shift automatique (40 % des messages touchés dans ce mode), et l’absence de priorité de la correspondance exacte face aux variantes accentuées plus fréquentes — ainsi qu’une liste concrète de mots à ajouter au dataset (renmen, konsè, démen, fatigé, sérié, nòwmal, aswè…). C’est exactement le type de retour que le passage « frappe parfaite » et le passage « réaliste » ne pouvaient pas produire.
Date : 11 juillet 2026
Heure : 13:20 – 14:05 (CEST)
Testeur : Claude Code (agent, modèle Fable 5), à la demande de l’utilisateur
Version testée : 7.0.3 (versionCode 70003) — inclut le correctif de casse sous majuscule automatique
Environnement : émulateur Android, AVD kreyol_test, fenêtre affichée à l’écran ; conversation SMS dédiée dans Google Messages.
⚠️ Méthodologie et limites du contenu : même dialogue imaginaire de 982 mots que les rapports précédents (non relu par un locuteur natif) — pas une référence linguistique.
Deux demandes ont motivé ce troisième passage sur le même dialogue :
| Métrique | Valeur | Rappel run précédent (7.0.2, sans hésitation) |
|---|---|---|
| Messages envoyés | 134 | 134 |
| Mots traités | 982 | 982 |
| Messages identiques au texte visé (sensible casse) | 93 / 134 (69,4 %) | 47 / 134 (35,1 %) |
| Exactitude caractère (Levenshtein) | 97,62 % | 95,08 % (97,44 % insensible casse) |
| Mots committés par tap de suggestion | 807 (82,2 %) | 806 (82,1 %) |
| Mots jamais proposés | 175 (17,8 %) | 176 (17,9 %) |
Le bug de casse est corrigé : sur 134 messages, seuls 4 diffèrent encore par la casse, et ce ne sont pas des majuscules parasites mais l’inverse — des noms propres en milieu de phrase (Jòj, Vytò, Pwentapit, Fwanswa) tapés en minuscules, conforme à la méthodologie documentée depuis le premier rapport (seul le début du champ bénéficie de l’auto-capitalisation native). Rien à corriger côté clavier ici.
| Métrique | Valeur |
|---|---|
| Mots ayant déclenché une hésitation | 98 / 982 (10,0 %) |
| … committés par suggestion malgré l’hésitation | 85 / 98 (86,7 %) |
| … jamais proposés après l’hésitation | 13 / 98 |
| Taux de disponibilité — mots hésités | 86,7 % |
| Taux de disponibilité — mots non hésités | 81,7 % |
| Frappes additionnelles dues à l’hésitation (préfixe + faute + backspace) | 320 |
L’hésitation ne dégrade pas la disponibilité des suggestions — au contraire, légèrement au-dessus de la moyenne (86,7 % vs 81,7 %), ce qui est attendu : le préfixe fautif est corrigé avant toute vérification finale, le moteur ne voit jamais la faute une fois le backspace effectué. La différence n’est pas significative sur cet échantillon (bruit d’échantillonnage), mais confirme au minimum que l’hésitation n’introduit pas de pénalité.
| Lettres tapées avant apparition | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| Mots | 343 | 182 | 101 | 37 | 8 | 1 |
Médiane inchangée : 1 lettre. Position du chip : 1ᵉʳ 474 (58,7 %), 2ᵉ 240 (29,7 %), 3ᵉ 93 (11,5 %) — quasi identique au run précédent.
| Longueur | Disponible / total |
|---|---|
| 2 lettres | 362/389 |
| 3 lettres | 156/173 |
| 4 lettres | 158/174 |
| 5 lettres | 69/97 |
| 6 lettres | 48/78 |
| 7-8 lettres | 14/24 |
Même liste qu’avant : renmen, konsè, démen, fatigé, sérié, nòwmal, bò, soley, red, fo, jòdi — confirmée stable, ces mots restent candidats à l’enrichissement du dataset (rappel : tout ajout doit passer par le dataset Hugging Face source, pas par une édition directe du JSON, qui serait écrasée au prochain run du pipeline).
| Valeur | |
|---|---|
| Mesures (une par lettre + une par lettre fautive d’hésitation) | 2 140 |
| Moyenne | 21,3 ms |
| Médiane | -30,2 ms |
| Écart-type | 197,4 ms |
Toujours sous le plancher de mesure, y compris avec la charge supplémentaire des lectures pendant les hésitations.
37 messages sur 134 diffèrent du texte visé au-delà des 4 cas de noms propres non capitalisés — mot perdu ou tronqué (ex. sav → sa, on disparu), mauvais mot committé pendant un rafraîchissement de la barre (ex. mété → mét pou sa fusionné en mété sa), du même ordre de grandeur et de même nature que dans les deux rapports précédents : le tap arrive juste au moment où la liste de suggestions se rafraîchit. Comme précédemment, ces cas sont inclus dans les statistiques et non retirés. Le nombre plus élevé qu’au run du 10/07 (2 cas) reflète la même explication déjà documentée : ce mode sollicite un tap de suggestion sur 82 % des mots (contre 8 % au run réaliste), donc une exposition largement supérieure au même phénomène.
Une capture tous les ~15-20 messages (12 au total) — dossier rapport_simulation_suggestions_hesitation_2026-07-11_screenshots/.
Ce troisième passage confirme deux choses. D’abord, le correctif 7.0.3 fonctionne : le taux de messages parfaitement conformes double (35,1 % → 69,4 %) et les 4 écarts de casse restants sont un phénomène différent, déjà documenté et non lié au bug corrigé. Ensuite, l’ajout d’hésitations réalistes (frappe, faute, backspace) au mode « suggestions uniquement » ne change quasiment rien aux statistiques de fond — couverture du dictionnaire (82,2 % vs 82,1 %), vitesse d’apparition (médiane 1 lettre), position du chip, latence : tous stables par rapport au run sans hésitation. C’est un résultat rassurant en soi : le moteur de suggestions ignore proprement les faux départs de l’utilisateur, sans effet de bord une fois le backspace effectué.
Date : 11 juillet 2026
Testeur : Claude Code (agent), à la demande de l’utilisateur
Version testée : 7.0.3
Méthode : inspection du code (SettingsActivity.kt, AndroidManifest.xml, docs/, KreyolKeybPlayStore/) croisée avec un parcours live sur émulateur (AVD kreyol_test, Pixel 5, Android 14) après désinstallation + désactivation forcée de l’IME côté système, pour reproduire fidèlement l’état d’un appareil qui n’a jamais eu l’app — ce que l’émulateur de test réutilisé sur cette session n’était plus, ses réglages système ayant persisté d’un test précédent malgré une simple désinstallation.
Ce rapport complète rapport_ux.md (audit général des écrans) en se concentrant spécifiquement sur le tunnel d’activation : du premier lancement post-install Play Store jusqu’au premier mot tapé avec le clavier actif ailleurs que dans l’app.
createActivationBanner(), createStatusBar()) : le signal qu’une meilleure mise en avant de l’état d’activation avait été envisagée, sans être finalisée.Pas de MainActivity : SettingsActivity (seule Activity LAUNCHER du manifest) s’ouvre directement sur l’onglet « 🚀 Démarrage », Étape 1 sur 3, avec un bouton unique OUVRIR LES PARAMÈTRES.

openKeyboardSettings() (SettingsActivity.kt:1493) lance Intent(Settings.ACTION_INPUT_METHOD_SETTINGS). L’utilisateur atterrit sur la liste des claviers du téléphone, avec le nôtre éteint, à côté de Gboard et Google Voice Typing.

Dès que l’utilisateur bascule l’interrupteur, Android affiche un dialogue générique et non personnalisable par l’app :
Attention — This input method may be able to collect all the text you type, including personal data like passwords and credit card numbers. It comes from the app Klavyé Kréyòl. Use this input method?

Rien dans le parcours ne prépare à ce moment. C’est le point de friction le plus élevé de tout le tunnel — l’endroit où un utilisateur prudent (le public visé, personnes soucieuses de confidentialité) abandonne, alors que le projet a précisément une politique « zéro collecte » à faire valoir ici.
Après avoir validé le premier, un second dialogue apparaît immédiatement, sans lien apparent avec le premier :
Note: After a reboot, this app can’t start until you unlock your phone.

Cause technique : AndroidManifest.xml ne déclare android:directBootAware nulle part (ni sur <application>, ni sur le service IME), donc l’app est traitée par défaut comme incompatible avec le stockage chiffré avant déverrouillage — comportement standard Android pour toute app dans ce cas, pas un bug introduit par le code du projet, mais un second choc inattendu dans une séquence déjà tendue. La plupart des utilisateurs n’ont jamais vu ce message et ne savent pas à quoi il correspond.
L’IME apparaît enfin activé dans la liste :

L’utilisateur revient (bouton retour du téléphone) : l’app a déjà détecté le changement d’état par elle-même — carte « Vous y êtes presque ! », étape 1 cochée ✓ ACTIVÉ, étape 2 débloquée. Ce rafraîchissement automatique (isKeyboardEnabled()/isKeyboardSelected() revérifiés à onResume() et par un polling toutes les 2s tant que l’onglet est visible, startPeriodicRefresh, l.2229) fonctionne bien et évite à l’utilisateur de devoir cliquer sur un bouton « J’ai fini ».
openInputMethodPicker() (l.1516) appelle imm.showInputMethodPicker(). Le sélecteur système liste Gboard et notre clavier — mais le nom est coupé :
Klavyé Kréyòl Karukera Potomita…

Le nom affiché dans les réglages système est en réalité « Klavyé Kréyòl Karukera Potomitan™ » (label de l’IME dans le manifest), plus long que celui utilisé dans les textes d’onboarding de l’app (« Klavyé Kréyòl Karukera », sans « Potomitan™ »). Un utilisateur qui scanne rapidement la liste ne voit ni le nom exact annoncé par l’app, ni le nom complet.
Une fois le clavier sélectionné, l’utilisateur qui revient dans l’app voit :
Tout est prêt ! Vous pouvez taper en Kréyòl partout ! — Configuration terminée ✓

C’est un bon écran, clair, positif. Mais rien ne le montre à l’utilisateur qui ne revient pas dans l’app — par exemple s’il change de clavier directement depuis la barre système (appui long sur la barre d’espace) puis va droit dans Messages : aucun Toast, aucune notification, aucun signal ne confirme jamais explicitement que tout est en ordre en dehors de ce retour volontaire dans l’app.
Le champ de test intégré (« Tester le clavier », hint Ékri an Kréyòl la…) fait apparaître le clavier pour la première fois :

Rien à l’écran n’indique la fonctionnalité d’accents par appui long (AccentHandler) — elle n’est mentionnée que dans le texte de la carte « Tout est prêt ! » de l’onglet Démarrage, jamais affichée au niveau du clavier lui-même, ni ici ni la première fois que l’utilisateur tape réellement dans une autre app (Messages, Notes…).
Recherche exhaustive dans le code de l’app (android_keyboard/app/src/main/) : zéro occurrence de « privacy »/« confidentialité ». Rien n’anticipe le dialogue « collecte de données » avant qu’il n’apparaisse, alors que c’est prévisible et systématique (Android l’affiche pour tout IME tiers, sans exception).
docs/privacy/PRIVACY_POLICY_FR.md (856 lignes) et privacy-policy.html existent, sont à jour (v2.0), détaillent précisément le stockage 100% local, le zéro-collecte, les permissions (BIND_INPUT_METHOD, WRITE_USER_DICTIONARY), la conformité RGPD. Ce document n’est lié nulle part : ni dans SettingsActivity.kt, ni dans KreyolKeybPlayStore/texts/description.md. Le contenu qui répondrait le mieux à la peur suscitée par le dialogue système (§3.1) existe déjà, il est juste invisible au bon moment.
createActivationBanner() (SettingsActivity.kt:1386) — bandeau “Clavier non activé” / bouton “Activer maintenant” — jamais appelée.createStatusBar(isEnabled, isSelected) (l.899) — barre de statut compacte avec bouton d’action contextuel (“Activer →” / “Sélectionner →”) — jamais appelée.createProgressBar() (l.984, appelée l.617) est utilisée en pratique.Deux implémentations alternatives d’un statut plus visible ont donc été écrites puis abandonnées au profit de la checklist actuelle — ce n’est pas gênant en soi, mais laisser ce code mort dans le fichier source (déjà signalé comme pattern récurrent par rapport_ux.md) complique la maintenance future de cet écran précis.
Le label déclaré (android:label de l’IME, probablement dans strings.xml/manifest) est « Klavyé Kréyòl Karukera Potomitan™ » — 33 caractères, systématiquement tronqué dans les listes système Android (§2.6, capture à l’appui). Les textes internes de l’app utilisent une version plus courte (« Klavyé Kréyòl Karukera »), créant une discordance entre ce que l’app annonce et ce que le système affiche réellement.
Aucun texte de l’onglet Démarrage n’est dans strings.xml (qui ne contient que 9 entrées, toutes des noms d’app/service) — tout est en dur dans SettingsActivity.kt. Cela ferme la porte à une traduction (anglais, pour la diaspora ?) sans toucher au code, et complique les tests unitaires de contenu.
KreyolKeybPlayStore/texts/description.md promet une « installation simple en 2 minutes » et met en avant le zéro-collecte, mais ne prépare à aucune des étapes réelles (Paramètres > Langues et saisie, ni les deux dialogues système). Un futur utilisateur qui lit la fiche avant d’installer n’a aucune idée de ce qui l’attend.
Aucune détection de « première frappe réelle » nulle part dans le code (recherche de patterns first_run/isFirstTime infructueuse). Le seul contenu pédagogique (accents longue-pression) est enfermé dans l’onglet Démarrage de l’app, jamais poussé au moment où il serait le plus utile : la première fois que l’utilisateur tape réellement quelque part.
isKeyboardEnabled()/isKeyboardSelected() interrogent directement InputMethodManager/Settings.Secure, avec un polling 2s qui détecte le retour de l’utilisateur depuis les paramètres système sans action de sa part.ACTION_INPUT_METHOD_SETTINGS, showInputMethodPicker()) — pas de détour, pas d’écran intermédiaire inutile.| # | Proposition | Constat lié | Effort estimé |
|---|---|---|---|
| 1 | Ajouter une carte explicative juste avant le bouton « Ouvrir les paramètres » : une phrase du type « Android va vous montrer un avertissement standard affiché pour tous les claviers tiers — nous ne collectons rien, tout reste sur votre téléphone » + un lien cliquable vers la politique de confidentialité déjà écrite. | §3.1, §3.2 | Faible — texte + Intent(ACTION_VIEW) vers l’URL déjà publiée |
| 2 | Lier la politique de confidentialité depuis l’onglet « À Propos » et depuis la fiche Play Store (description.md), au minimum. |
§3.2, §3.6 | Très faible |
| 3 | Raccourcir ou clarifier le label système de l’IME (retirer « Potomitan™ » du label affiché dans les listes système, le garder pour le branding interne) pour qu’il ne soit plus tronqué et corresponde au nom utilisé dans les textes d’onboarding. | §3.4 | Faible — modification du label déclaré, à valider avec l’identité de marque |
| 4 | Ajouter une confirmation de succès qui ne dépend pas du retour dans l’app : un Toast ou une notification légère déclenchée dès que isKeyboardEnabled() && isKeyboardSelected() devient vrai (détectable par un ContentObserver sur Settings.Secure.DEFAULT_INPUT_METHOD plutôt que seulement du polling en foreground). |
§3.5, §2.7 | Moyen |
| 5 | Supprimer ou finaliser createActivationBanner()/createStatusBar() — code mort qui alourdit SettingsActivity.kt sans bénéfice utilisateur. |
§3.3 | Faible |
| 6 | Tip contextuel au premier usage réel : une notification ou un overlay léger déclenché une seule fois (flag SharedPreferences) lors de la première frappe détectée hors de l’app, rappelant l’astuce accents longue-pression. |
§3.7 | Moyen — nécessite une détection de premier usage dans l’IME lui-même |
| 7 | (Optionnel, faible priorité) Envisager android:directBootAware="true" sur le service IME pour supprimer le second dialogue — complexité et implications sécurité à évaluer avant de s’engager (accès au stockage chiffré par les identifiants uniquement) ; à documenter au moins dans la carte d’explication du point 1 si non traité. |
§2.4 | Élevé / à évaluer |
Le tunnel d’activation du Klavyé Kréyòl repose sur une base technique solide — détection d’état en temps réel, déblocage progressif, bons Intents système — mais laisse l’utilisateur seul face aux deux moments les plus anxiogènes du parcours : les deux avertissements système consécutifs, dont le premier est exactement celui qu’une politique de confidentialité « zéro collecte » déjà rédigée pourrait neutraliser, si elle était montrée au bon endroit. Les corrections les plus rentables (liens vers la politique de confidentialité, phrase d’explication avant l’avertissement, nettoyage du code mort) sont à faible effort et s’attaquent directement au point où un utilisateur prudent — précisément le public que ce projet cherche à rassurer — est le plus susceptible d’abandonner.
Suite au rapport rapport_onboarding_activation_2026-07-11.md, 5 des 7 recommandations ont été implémentées et vérifiées sur émulateur (kreyol_test, premier lancement reproduit à l’identique : désinstallation + désactivation forcée de l’IME côté système). La recommandation #7 (directBootAware) reste volontairement hors scope (implications sécurité à évaluer séparément).
Une carte apparaît désormais dans l’onglet Démarrage, tant que le clavier n’est pas activé, expliquant que l’avertissement système est standard pour tout clavier tiers et pointant vers la politique de confidentialité.

Le lien ouvre bien la politique de confidentialité dans le navigateur :

Nouvelle carte « 🔒 Confidentialité » ajoutée à l’onglet À Propos, avec le même lien.

keyboard_name/subtype_kreyol raccourcis de « Klavyé Kréyòl Karukera Potomitan™ » à « Klavyé Kréyòl Karukera » — le nom s’affiche désormais en entier dans les paramètres système.

Un Toast unique (« Kréyòl a klavyé a ! Apiyé lontan asi on lèt pou wè aksan yo… ») se déclenche la première fois que le clavier est utilisé réellement en dehors de l’app elle-même (détecté via EditorInfo.packageName, flag SharedPreferences posé une seule fois). Vérifié fonctionnellement : le flag first_real_use_tip_shown se pose bien lors de la première frappe dans Google Messages, jamais dans le champ de test intégré à l’app.
createActivationBanner() et createStatusBar() — deux fonctions de statut jamais appelées — retirées de SettingsActivity.kt.
KreyolKeybPlayStore/texts/description.md complétée avec le lien vers la politique de confidentialité et le rappel explicite des 3 étapes d’activation (Paramètres > Langues et saisie > Claviers virtuels).
Un 6ᵉ onglet a été ajouté (GuideFragment), un guide détaillé de l’utilisateur : écriture en kréyòl, accents par appui long, suggestions/autocomplétion, correction orthographique système, chiffres/symboles, les 2 jeux de vocabulaire, les 8 niveaux de progression (Pipirit → Benzo), et une FAQ courte (clavier invisible, changer de clavier, confidentialité).

Build + suite de tests unitaires au vert après chaque changement. Parcours complet rejoué manuellement sur l’émulateur kreyol_test (état vierge reproduit). Navigation cyclique entre les 6 onglets vérifiée (tab bar et contenu toujours synchronisés).