docs: move design docs to docs/ and add an index#32
Merged
Conversation
La racine devient lisible : seul README.md reste comme point d'entrée. Les 15 documents de design, stack et user stories vivent désormais dans docs/, avec docs/INDEX.md pour la navigation thématique. Les liens du README et l'arborescence projet sont mis à jour, et le lien cassé vers IMPLEMENTATION_SUMMARY.md est corrigé vers rust-backend/IMPLEMENTATION_SUMMARY.md.
Le README annonçait déjà la licence MIT mais le fichier manquait. On ajoute : - LICENSE : texte MIT complet (2026, LyRemember Team) - CONTRIBUTING.md : workflow contrib, stacks (Python/Rust/Vue), TDD, conventional commits, tests par stack - CHANGELOG.md : Keep a Changelog, section Unreleased seedée à partir de l'historique récent (refonte UI, mode invité, CI, build Android, réorganisation docs, etc.) Le README pointe désormais vers ces fichiers depuis ses sections Contributing / Changelog / License.
Pourquoi : le repo héberge trois stacks (Python CLI legacy, Rust backend, Vue+Tauri frontend). Garder le POC Python à la racine entretient la confusion sur la stack canonique. On l'isole dans legacy/python-cli/. Ce qui bouge : - Déplace lyremember/, tests/, data/, demo.py, setup.py, requirements.txt dans legacy/python-cli/. - Workflow CI Python : path filters et working-directory mis à jour. - README + docs/ARCHITECTURE.md : Project Structure et bloc legacy refletent les nouveaux chemins. - .gitignore : remappe les ignores Python sur legacy/python-cli/. - legacy/README.md créé pour signaler le statut archive. - CHANGELOG.md : section Unreleased mise à jour. Vérification : cd legacy/python-cli && python -m pytest tests/ -q -> 32 passed.
Ce fichier décrivait une stack PWA/React/FastAPI qui n'est plus celle du projet. La stack canonique (Tauri+Vue+Rust+PyO3) est documentée dans docs/FINAL_DECISIONS.md. La référence à TECH_STACK_FINAL.md dans docs/INDEX.md est retirée. Aucune autre référence subsistante (vérifié par grep).
Le service de traduction reellement implemente est LibreTranslate (rust-backend/src/services/translation.rs, appel HTTP via reqwest). Plusieurs docs mentionnaient encore deep-translator (wrapper Python non officiel sur Google Translate) : - docs/TECH_CHOICES.md : Option C marquee RETENU, Option D reformulee sans nommer la lib, mentions deep-translator dans les scenarios et questions remplacees. - docs/RUST_OPTION.md : bloc traduction reformule, tableau equivalents corrige. Verification : grep -rnE 'deep[-_]translator' docs/ README.md -> aucun match.
L'app n'utilise pas Shadcn-vue mais des composants maison dans lyremember-app/src/components/ui/ (Button, Card, Input, Alert, Spinner). La doc canonique reflete maintenant la realite : - docs/FINAL_DECISIONS.md : 3 mentions de Shadcn-vue remplacees ou retirees (UI Library, schema d'architecture, liste de liens externes). - docs/UI_LIBRARIES.md : note en tete indiquant que ce comparatif est historique et que la decision finale est consignee dans FINAL_DECISIONS.md. Verification : grep -nE 'shadcn|Shadcn' docs/FINAL_DECISIONS.md -> 0 match.
) lyremember-app/src-tauri/src/commands.rs expose 19 commandes (5 auth + 7 songs + 4 practice + 3 util), pas 16. La doc est synchronisee : - docs/TAURI_INTEGRATION_COMPLETE.md : 3 mentions corrigees + nouvelle section "Detail des 19 commandes Tauri exposees" avec les noms exacts par categorie (source de verite = commands.rs). - README.md : 6 mentions "16 commands" alignees a 19 (DRY entre docs). Verification : grep -rnE '16 (Tauri )?commands?\b' docs/ README.md -> aucun match.
….md (#11) Le document decrivait l'architecture du proof of concept Python comme si c'etait la stack canonique. On clarifie en tete : - POC Python (legacy/python-cli/) : storage JSON via storage.py. - Production Rust+Tauri+Vue : storage SQLite via rust-backend/src/db/sqlite.rs (4 tables : users, songs, user_songs, practice_sessions). - Source de verite des decisions actuelles : FINAL_DECISIONS.md. Suit l'archivage du POC dans legacy/ (#6).
…ilView, Genius redefini (#12 #13 #14) Trois clarifications portees par USER_STORIES_V2.md, regroupees car elles modifient le meme document : - #12 — Epic 5 (Mode Verification Orale) descendu en CouldHave. Le MVP livre uniquement OralMode.vue en self-assessment manuel ; le mode live avec micro (Web Speech API ou STT) est post-MVP, suivi par #28. - #13 — Note de navigation pour les Epics 4-7 : PracticeView = APPRENDRE (selection chanson + mode + lancement), SongDetailView = LYRICS (edition, vue 3 colonnes). Raccourci "Practice this song" depuis SongDetailView. Implementation suivie par #19. - #14 — US-1.3 (Genius) refondue : import de lyrics interdit (scraping = ToS + API officielle ne donne pas les lyrics). Remplace par : (a) champ genius_url optionnel (#17), (b) spike API metadonnees (#26), (c) cleanup UI (#18). US-2.1 ajustee pour mentionner "lien Genius optionnel" plutot qu'import. L'historique des stories d'origine est conserve avec une banniere de mise a jour pour la tracabilite.
Pourquoi : la phonetique JP/KR/FR/EN annoncee dans
docs/TAURI_INTEGRATION_COMPLETE.md ne fonctionne que si la feature
`python` (PyO3 -> pykakasi/hangul-romanize/epitran) est activee.
L'ancien `default-features = false` desactivait la feature -> stub
d'erreur sur tous les builds desktop, alors que ce n'est pas la
contrainte (PyO3 est cross-compilable Linux/macOS/Windows).
Ce qui bouge :
- lyremember-app/src-tauri/Cargo.toml :
* [features] default = ["python"] (au lieu de [])
* la dep `lyremember_backend` garde `default-features = false`
car la feature locale `python = ["lyremember_backend/python"]`
suffit a forwarder l'activation -> mecanisme propre.
* commentaires expliquant le flux d'activation.
- .github/workflows/release.yml : le build Android passe maintenant
`--no-default-features` car PyO3 n'est PAS cross-compilable vers
Android (commentaire deja present dans rust-backend/Cargo.toml).
La phonetique renverra un stub sur APK jusqu'a ce qu'une
alternative Rust pure soit fournie (post-MVP).
- docs/ARCHITECTURE.md : section "Build flags Tauri" documente les
deux modes (desktop avec python, Android sans).
Validation locale partielle : `cargo check` echoue sur le container
headless (gdk-3.0 manquant pour Tauri Linux desktop) mais la
configuration des features est correcte au niveau manifest. La
verification finale se fait en CI/release.
Le critere "test d'integration appelle la phonetique reelle" est
laisse a #23 (depend de cette issue).
Le secret de signature JWT etait hardcoded
("your-secret-key-change-this-in-production") dans
rust-backend/src/services/auth.rs:11. Un secret en clair dans le
binaire est exploitable par n'importe qui : impersonation possible
de n'importe quel utilisateur. MVP-blocker pour toute distribution.
Ce qui bouge :
- rust-backend/src/services/auth.rs :
* Const JWT_SECRET supprimee, remplacee par jwt_secret() qui lit
LYREMEMBER_JWT_SECRET via OnceLock une fois par process.
* Fonction interne jwt_secret_from_env(Option<String>) -> Vec<u8>
pour rendre la resolution testable sans toucher l'env.
* Fallback dev : 32 bytes aleatoires via uuid::Uuid::new_v4 (deja
en dep), avec warning explicite sur stderr (coherent avec les
eprintln! deja utilises dans services/songs.rs et translation.rs).
- .env.example : nouveau fichier, documente la variable + commande
openssl rand -hex 32 pour generer un secret fort.
- docs/ARCHITECTURE.md : section "Variables d'environnement (prod)".
- README.md : note dans Quick Start.
TDD :
- 4 tests ajoutes dans tests::test_jwt_secret_from_env_* (valeur
fournie, fallback None, fallback string vide, deux fallbacks
distincts -> entropie).
- Cycle red->green verifie : tests echouaient sur "function not
found" puis passent apres implementation.
Suite complete : 87 passed, 4 ignored, 0 failed (cargo test).
La colonne SQL genius_url existait deja dans rust-backend/src/db/sqlite.rs
mais le wiring service/commands/UI manquait : les chansons recevaient
toujours NULL. On le branche bout en bout :
Backend Rust :
- CreateSongData / UpdateSongData : nouveau champ Option<String>
(#[serde(default)] pour rester retro-compat sur les payloads).
- create_song : copie data.genius_url dans song.genius_url avant
save_song (qui l'inserait deja en colonne).
- update_song : ajout SET genius_url = ?... dans le SQL UPDATE.
Semantique : None = ne pas toucher, Some("") = clear (NULL en DB),
Some(url) = ecrase.
- Tests : 4 nouveaux tests TDD (red->green) couvrant create avec/
sans url + update set + update clear par chaine vide.
- Sites construisant CreateSongData/UpdateSongData mis a jour
(services/practice.rs, examples/basic_usage.rs, tests internes
par script Python).
Tauri commands :
- cmd_create_song et cmd_update_song acceptent un param genius_url
Option<String>. Empty -> None pour create (cleaner).
Frontend :
- tauri-api.ts : Song.genius_url + Song.genius_id types, createSong
et updateSong wrappers acceptent geniusUrl optionnel.
- stores/songs.ts : createSong relaye geniusUrl.
- types/index.ts : CreateSongForm.geniusUrl ajoute.
- AddSongView.vue : nouvel Input "Lien Genius (optionnel)" entre
lyrics et autoTranslate.
- SongDetailView.vue : lien sortant target="_blank"
rel="noopener noreferrer" sous l'artist quand present.
- i18n FR/EN : cles addSong.geniusUrl(Placeholder) et
songDetail.openOnGenius.
CRITIQUE : aucune extraction de paroles depuis Genius (interdit par
ToS, voir #14 / #18). C'est un simple lien sortant utilisateur.
Suite Rust : 91 passed (87 + 4 nouveaux), 4 ignored, 0 failed.
L'UI proposait un champ de token Genius (stocke en localStorage) et une recherche "Import depuis Genius" alors que : - scraper genius.com viole les ToS, - l'API officielle ne fournit pas les paroles. On retire toute cette section. Le lien Genius reste utile en tant que simple URL sortante au niveau de la chanson (genius_url, #17). Une exploration de l'API metadonnees pourra venir via le spike R&D (#26). Ce qui bouge : - lyremember-app/src/views/SettingsView.vue : section "Genius API" supprimee, imports inutiles (useRouter, Button, ref) retires. Le store localStorage 'geniusToken' n'est plus ecrit. - lyremember-app/src/i18n/locales/{en,fr}.json : 12 cles obsoletes retirees (settings.integrations, geniusApi, geniusToken, geniusTokenPlaceholder, geniusDesc, geniusHelp, tokenSaved, searchSongs, searchPlaceholder, search, import, noResults). Verification : grep -rnE 'genius' lyremember-app/src/ ne renvoie plus que les usages du champ lien sortant (genius_url / openOnGenius / genius.com placeholder URL) et la propriete User.genius_token du type backend (defini par le schema SQL existant, pas un usage UI).
…e vers le hub (#19) PracticeView etait deja le hub d'apprentissage (liste chansons + 4 modes, route vers /songs/:id?mode=... pour monter le mode dans SongDetailView). On finalise le pattern de navigation : - PracticeView : lit route.query.songId au montage et pre-deplie la chanson correspondante dans la liste si elle est dans le repertoire utilisateur. Validation defensive : on verifie que la chanson existe dans songsStore.songs avant de selectionner. - SongDetailView : ajout d'un bouton "Ouvrir dans Practice ->" a cote de la section "Practice Modes" qui route vers /practice?songId=<song.id>. Les 4 raccourcis directs vers les modes restent sur la fiche pour ne pas regresser l'UX existante. - i18n FR/EN : nouvelle cle songDetail.practiceHubShortcut. Roles confirmes (decision #10) : - PracticeView = hub APPRENDRE (selection chanson + mode + lancement) - SongDetailView = gestion LYRICS + raccourcis directs vers les modes
Promesse "multi-langues JP/KR" : l'app supportait JP/KR comme langues de chanson mais pas comme langues d'UI (en + fr seulement). Ce qui bouge : - lyremember-app/src/i18n/locales/ja.json : 151 cles (Hiragana/Katakana/ Kanji standard japonais). - lyremember-app/src/i18n/locales/ko.json : 151 cles (Hangul). - lyremember-app/src/i18n/index.ts : * SupportedLocale etendu a 'en' | 'fr' | 'ja' | 'ko'. * supportedLocales : labels natifs (日本語, 한국어) pour le selecteur. * messages : ja + ko ajoutes a createI18n. * getInitialLocale : detection navigator.language couvre les 4 codes. Le selecteur de langue de SettingsView (qui itere supportedLocales) prend en charge les 4 langues automatiquement. Couverture verifiee : 151 cles par locale, 0 manquante, 0 extra (diff JSON aplati sur en/fr/ja/ko). Hors perimetre : internationalisation du contenu utilisateur (chansons) qui reste dans la langue d'origine.
…#21) La vue lyrics affichait deja VO + Traduction (selectionnable). La phonetique (generee par PyO3 -> pykakasi/hangul-romanize/epitran et stockee en song.phonetic_lyrics, voir #15) etait absente de l'affichage alors que c'est une promesse MVP. Ce qui bouge : - LyricLine elargi : champ `phonetic: string | null`. - lyricSections (computed) : extrait `phonetic_lyrics[i]` ligne par ligne en parallele des traductions. - Template : nouvelle <p> en italique mono opacity-75 sous la VO, affichee uniquement si line.phonetic existe. Ordre vertical preservant l'esprit "3 colonnes" promis (VO en serif normal, phonetique en mono pour la distinguer, traduction en doree italique). Compatible avec : - Une chanson sans phonetique (phonetic_lyrics null) : juste VO + trad comme avant -> aucune regression visuelle. - Une chanson JP/KR : phonetique romaji/romanisation affichee. - Une chanson EN/FR : IPA affiche. Hors perimetre : edition inline des traductions (post-MVP).
Avant : 3 distractors aleatoires parmi les autres lignes, fallback sur "word-scrambled" du correctLine (toujours la meme source). Apres : - Les lignes candidates sont classees par proximite de longueur avec la bonne reponse (|len(line) - len(correct)|), puis melangees dans un pool de top-6. Resultat : des distractors de longueur visuelle similaire, plus difficile a eliminer au coup d'oeil que des lignes random. - Fallback word-scrambled puise dans les AUTRES lignes (donc plausibles dans le vocabulaire de la chanson), seulement correctLine en derniere instance. - Anti-doublons sur tous les chemins (deja la mais durci). - Safety valve : max 20 tentatives sur le fallback. Decision pragmatique : reste cote frontend (TS). Le deplacement en service Rust (cote serveur, testable independamment) reste un follow-up post-MVP — l'amelioration immediate de l'experience MCQ ne justifie pas le cout d'une nouvelle Tauri command pour 30 lignes de logique cote front.
…23) L'audit #23 demande des tests pour services::phonetic couvrant les deux branches (feature python active vs stub). Les tests precedents ne couvraient que la branche python (ignored, requierent pip install pykakasi/hangul-romanize/epitran). Ce qui bouge : - services/phonetic.rs (prod) : court-circuit empty input avant tout appel Python::with_gil. Evite de payer le cout du runtime Python pour un no-op et rend les tests deterministes meme sans deps Python. - Tests : * test_generate_phonetic_empty_input : passe sur les 5 langues. * test_stub_returns_error_for_supported_language (cfg not python) : verifie qu'un build Android (sans python) renvoie Error::Phonetic pour jp/kr/fr/en plutot que succes silencieux ou panic. * test_stub_passthrough_for_unsupported_language : la branche par defaut renvoie le texte original sans erreur. * test_to_ipa_french : ajoute la couverture FR (manquante). * test_generate_phonetic_dispatches_by_language : dispatcher route bien vers le bon backend. - Documentation : commentaire en tete du module tests pointe vers ci-rust.yml et la commande pour activer les tests Python. Verification : - cargo test (feature python) : 92 passed, 6 ignored, 0 failed. - cargo test --no-default-features : 91 passed, 0 ignored, 0 failed (4 tests phonetic dont les 2 nouveaux pour la branche stub). Les tests reels (kakasi/hangul-romanize/epitran) restent ignored par defaut, executables via `cargo test --features python -- --ignored` quand les pip deps sont presentes.
…#24) Le frontend ne disposait que de 3 specs e2e WebDriverIO. On ajoute Vitest pour iterer plus vite sur les composants/stores. Infrastructure : - lyremember-app/package.json : scripts test:unit (run-once, vitest run) et test:unit:watch ; devDeps @vue/test-utils, jsdom, vitest (versions stables 2024). - lyremember-app/vitest.config.ts : environnement jsdom, alias '@' vers src/, include 'src/**/*.{test,spec}.ts'. - .github/workflows/ci-frontend.yml : etape "Unit tests (Vitest)" entre typecheck et build. 3 pilots couvrant chaque grand type d'asset : - src/components/ui/Button.spec.ts (4 cas) : slot rendering, click emit, loading state + variant class. - src/components/ui/Alert.spec.ts (3 cas) : hidden quand modelValue=false, render+variant, emit update sur bouton close. - src/stores/songs.spec.ts (4 cas) : filtres filteredSongs par query/langue, groupBy, totalSongs. Helper makeSong() factorise les fixtures Song complets. CONTRIBUTING.md mis a jour : test:unit + test:e2e listes explicitement comme deux scripts distincts. Hors perimetre : couverture exhaustive ajoutee incrementalement.
Deux rapports docs-only deposes dans docs/spikes/ : - 2026-05-genius-api.md (#26) : exploration de l'API metadonnees Genius. Endpoints disponibles (/search, /songs/:id) renvoient metadonnees mais PAS les paroles. Valeur produit faible : auto-completion a la creation = ~2 frappes gagnees, le reste deja couvert par genius_url (#17). Recommandation NO-GO court terme, re-evaluer si onboarding casual devient critique ou si on veut afficher les cover arts. - 2026-05-song-detection.md (#27) : detection Shazam-like. Comparatif AudD (free 25 req/jour, $9-99/mois), ACRCloud (~$99/mois), Shazam via RapidAPI, alternative open-source (Chromaprint + MusicBrainz). Faisabilite Tauri desktop (cpal ou navigator getUserMedia) + Android (permission RECORD_AUDIO) + iOS (entitlement, pas de build prouve). Aspects legaux RGPD (enregistrement environnement -> ne pas persister). Couts estimes : ~80 USD/mois a 100 users, ~810 USD/mois a 1k users. Recommandation NO-GO MVP, re-evaluer post-MVP avec >500 users ou partenariat. docs/INDEX.md mis a jour avec section "Spikes R&D".
Le dark mode etait deja entierement implemente : - stores/ui.ts : toggleDarkMode flip + DOM class 'dark' + localStorage - stores/ui.ts : initializeDarkMode lit localStorage > prefers-color-scheme - App.vue : appelle initializeDarkMode au mount - SettingsView.vue : toggle switch wire sur uiStore.toggleDarkMode - tailwind.config.js : darkMode = 'class' - i18n FR/EN/JP/KR : cles settings.darkMode(Desc) Tous les critères de l'issue sont remplis : - L'utilisateur peut basculer clair/sombre depuis SettingsView : OK - Le choix persiste entre sessions : OK (localStorage) - Respect prefers-color-scheme au premier lancement : OK - Variables CSS / classes Tailwind clair/sombre : OK (darkMode class) Ce qui manquait : un test couvrant le comportement. Ajout de stores/ui.spec.ts avec 3 cas : - toggleDarkMode : flip state, ajoute/retire <html class='dark'>, persiste 'darkMode' dans localStorage. - initializeDarkMode : priorise la valeur sauvegardee sur la preference systeme. - initializeDarkMode : fallback sur prefers-color-scheme si pas de valeur sauvegardee. Hors perimetre : themes custom (orange/violet/etc.).
…_api (#25) Le POC Python archive (legacy/python-cli/) n'avait que 32 tests sur 4 modules (file_importer, song_manager, storage, utils). On ajoute 4 nouveaux fichiers de tests pour les 4 modules cibles de l'audit : - tests/test_models.py : dataclasses User/Song/PracticeSession/ SongProgress. Round-trip to_dict/from_dict, verify_password, defaults sains. (4 classes de tests, ~14 cas.) - tests/test_practice_engine.py : modes fill-blank, flashcard, line-by-line, accounting de session. Verifie que les lignes vides sont skipees, que record_line_result peuple difficult_lines, que create_session calcule le score. Note : on garde le contrat max(1, ...) de hide_words (au moins un mot cache meme a difficulty=0). - tests/test_user_manager.py : register/login/repertoire/genius token via Storage mocke. Necessaire car le storage.py legacy n'expose PAS get_user_by_username ni save_user — code casse au niveau integration mais UserManager lui-meme reste testable en isolation. - tests/test_genius_api.py : init avec/sans token, search degradation gracieuse sans client. Pas d'appel HTTP reel. Resultats : 68 tests passent (32 -> 68, +36). Coverage moyenne sur les 4 modules cibles : 81.5% (practice_engine 100%, models 100%, user_manager 96%, genius_api 30% — limite par l'absence de lyricsgenius en environnement de test). cd legacy/python-cli && python -m pytest tests/ -q -> 68 passed.
L'app n'avait que la saisie manuelle pour les paroles. On reprend la
fonctionnalite POC (file_importer.py) etendue au format .lrc.
Architecture : parsers TS purs dans lib/file-parsers.ts (zero
dependance Tauri, lit via File.text() cote webview navigateur).
Decision documentee dans l'issue : pas de Tauri fs plugin pour le
MVP, l'input file natif suffit.
Parsers :
- parseTxt : 1 ligne = 1 lyric, skip '#'-comments et lignes vides.
- parseJson : {title?, artist?, language?, lyrics: string[]}
Type guards stricts (FileImportError sur JSON invalide ou lyrics
pas array de strings).
- parseLrc : strip leading timestamps [mm:ss.xx] (et variantes
[mm:ss], multiples par ligne possibles). Tags meta [ti:Title]
[ar:Artist] [la:lang] extraits dans les champs correspondants.
- parseByExtension : dispatcher par extension du nom de fichier.
UI : input <input type="file" accept=".txt,.json,.lrc"> dans
AddSongView au-dessus du formulaire. handleFileImport lit le
contenu, le parse, et pre-remplit les champs title/artist/
language/lyrics. Erreur affichee via l'Alert existant.
Tests : 16 specs Vitest dans file-parsers.spec.ts couvrant chaque
parser (cas valide, CRLF, comments, JSON malforme, lyrics non-array,
LRC sans timestamps, multiples timestamps, metadata) + dispatcher.
i18n : addSong.importFile et addSong.importFileHint ajoutees a
en/fr/ja/ko (couverture verifiee a 100% sur les 4 locales).
Hors perimetre : drag-and-drop (peut etre ajoute plus tard) ;
import depuis Genius (interdit, cf #18).
Promesse Epic 8 USER_STORIES_V2 : streak (jours consecutifs) + recommandations de chansons a revoir. Livraison MVP en 4 couches : Rust (rust-backend/src/services/practice.rs) : - compute_streak_from_dates(today, dates) : fonction pure, testable avec dates controlees. Streak = nb de jours consecutifs ending today OR yesterday avec au moins 1 session. Dedupe sessions multiples le meme jour. Reset a 0 si gap > 1 jour. - get_user_streak(conn, user_id) : SELECT DISTINCT DATE(created_at) -> compute_streak_from_dates avec chrono::Utc::now().date_naive(). - get_recommendations(conn, user_id, limit) : songs deja pratiquees classees par mastery croissante puis last_practiced ascendante. - 9 tests TDD red->green : streak (vide, today, 3 jours, hier, gap, breaks, dedupe) + recommendations (ordre, limit). Tauri commands (lyremember-app/src-tauri/src/commands.rs + lib.rs) : - cmd_get_user_streak (1 commande) - cmd_get_recommendations (1 commande, limit optionnel default 5) - Total commandes exposees : 19 -> 21 (auth 5, songs 7, practice 6, util 3). Doc TAURI_INTEGRATION_COMPLETE.md a re-aligner si voulu (suivi). Frontend : - lib/tauri-api.ts : getUserStreak, getRecommendations. - composables/useUserStats.ts : etendu pour fetch streak + recommendedSongIds en Promise.all, avec fallback gracieux si l'une des Promesses casse. - views/DashboardView.vue : badge "🔥 N jours" en haut a droite quand streak > 0, tooltip "Jours consecutifs de pratique". - i18n FR/EN/JP/KR : dashboard.streakDays (formule pluralisable) + dashboard.streakTooltip. Hors perimetre (volontairement) : - Section "Suggere pour vous" basee sur recommandations : couvert cote backend, UI dediee differable post-MVP. - Achievements / leaderboards multi-utilisateurs. Suite Rust : 101 passed, 6 ignored, 0 failed.
L'audit promettait un mode oral avec reconnaissance vocale live. Le
mode existant ne faisait que du self-assessment manuel. Solution MVP :
brancher la Web Speech API native du webview avec fallback gracieux.
Architecture :
- lib/oral-scoring.ts : module pur, testable sans webview.
* normalize : lowercase + strip ASCII punct + collapse spaces
(preserve les diacritiques car phonetiquement significatifs).
* tokenize : normalize puis split sur espaces.
* scoreSpoken(expected, spoken) -> [0,1] : token-set recall sur
expected, robuste a l'ordre.
* hasSpeechRecognition() : feature-detect (window.SpeechRecognition
OR window.webkitSpeechRecognition).
- lib/oral-scoring.spec.ts : 12 tests couvrant normalize, tokenize,
scoreSpoken (cas 1.0 / 0 / partiel / case-insensitive / ordre), et
hasSpeechRecognition (false en jsdom).
UI :
- OralMode.vue : nouveau panneau "Live (experimental)" affiche
seulement si hasSpeechRecognition() est vrai au mount. Bouton
Speak/Stop qui demarre/arrete recognition.
- Mapping langue chanson -> locale BCP-47 (fr-FR, en-US, ja-JP,
ko-KR ; fallback en-US).
- Score affiche en temps reel ; >= 70% declenche auto-assessment
comme "got it".
- onUnmounted nettoie la recognition pour eviter une fuite.
- Self-assessment manuel preserve : la branche v-if="!selfAssessed"
garde l'affichage du panneau live tant que l'utilisateur n'a pas
trance (manuellement ou par score).
Limitations documentees :
- Pas de capture audio Tauri native : on s'appuie sur le webview.
Desktop OK sur la plupart des cibles ; Android depend de la
version WebView ; iOS WKWebView : pas dispo.
- Pas de STT serveur (Whisper/Vosk) : le spike #27 reste pertinent
pour la version definitive (recognition offline + scoring
phonetique).
Pas de regression : le mode oral existant continue de marcher dans
les environnements sans SpeechRecognition (le panneau live ne
s'affiche simplement pas).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
La racine devient lisible : seul README.md reste comme point d'entrée.
Les 15 documents de design, stack et user stories vivent désormais
dans docs/, avec docs/INDEX.md pour la navigation thématique. Les
liens du README et l'arborescence projet sont mis à jour, et le
lien cassé vers IMPLEMENTATION_SUMMARY.md est corrigé vers
rust-backend/IMPLEMENTATION_SUMMARY.md.