# Search UX — Steps status **All 14 steps: DONE (100%)** ✅ ## Done - [x] Step 1: Audit current codebase for existing search and provider logic on frontend and server - [x] Step 2: Design and scaffold Provider Registry on frontend and server - [x] Step 3: Implement SearchService with multi-provider params and HTTP request - [x] Step 4: Build SearchBoxComponent with chips, @autocomplete, and keyboard shortcuts - [x] Step 5: Build ProviderPickerComponent (Ctrl/⌘+K modal) with filtering, History column and apply - [x] Step 6: Build SearchSuggestionsComponent with grouped sections, per-provider unsupported rows and deep-links - [x] Step 7: Add router query-param sync and default provider preference loading (Search page fallback to user preference) - [x] Step 8: Implement backend /api/search with providers param and fan-out to server/providers handlers - [x] Step 9: Persist default providers preference in user profile (client + server endpoints wired, tested) - [x] Step 10: Add keyboard accessibility and a11y behaviors (Tab/Shift+Tab focus trap, Esc, aria, focus restore) - [x] Step 11: Write unit tests (SearchService, ProviderPicker toggle, @ parsing) - [x] Step 12: Write basic e2e scenarios for providers filtering and deep-link - [x] Step 13: Add minimal telemetry hooks and events - [x] Step 14: Update README with search UX section, keyboard shortcuts and usage notes - [x] Step 15: Auto-complétion de la **requête** (typeahead) dans la barre de recherche - [x] Step 16: **Transcripts** de vidéos sur la page Watch (API + UI, Phase 1 du doc) --- ## Step 15 — Suggestions de requêtes dans la barre de recherche ### Ce que ça veut dire (et ce que ce n'est pas) Quand l'utilisateur tape `tutoriel an`, la barre affiche **avant qu'il valide** une liste de requêtes complètes probables : `tutoriel angular`, `tutoriel android`… + ses recherches passées. > ⚠️ À ne pas confondre avec `SearchSuggestionsComponent` (`src/components/search/search-suggestions.component.ts`), > qui lui affiche les **résultats de recherche** groupés par provider **après** la validation sur `/search`. > Le Step 15 ajoute un nouveau panneau de **suggestions de texte** sous l'``. ### Ce qui existe déjà (à réutiliser) | Élément | Fichier | État | |---|---|---| | `@Output() searchChange` (query debouncée 300 ms) | `src/components/search/search-box.component.ts:46` | Émis mais **aucun abonné** — le header ne branche que `(submitted)` (`header.component.html:19`) | | Popover `@provider` + navigation clavier | `search-box.component.ts:16` (`AT_QUERY_RE`), `.html:56-76` | ✅ mais ne couvre que les ids de providers | | Historique de recherches | `HistoryService.getSearchHistory(n)` | ✅ utilisé dans le Quick Menu | | Cache in-memory TTL 60 s | `src/app/search/search.service.ts:33` | Pattern à dupliquer | | Fan-out multi-providers | `GET /api/search` (`server/index.mjs:2116`) | Pattern à dupliquer | ### Livrables 1. **Backend** — `GET /api/search/suggest?q=…&providers=yt,dm&limit=10` → `{ q, groups: { yt: string[], dm: string[], … } }` - Un `suggest(q, { limit })` optionnel par handler dans `server/providers/*.mjs` + déclaré dans `server/providers/registry.mjs` (`ProviderAdapter`). - Providers sans API de suggestions native → `[]` (dégradation propre, jamais d'erreur 500). - Cache + rate-limit sur le même modèle que `/api/search`. 2. **Front service** — `SuggestService` (ou méthode `suggest()` sur `SearchService`) : `min length 2`, `debounce 250-300 ms`, `switchMap` (annulation de la requête précédente), cache 5 min, dédoublonnage + tri. 3. **UI** — panneau sous l'input, sections : - 🔎 « Recherches récentes » (History, avec icône horloge) - 📊 Suggestions providers, groupées par provider (badges `YT/DM/…`) ou fusionnées - Sous-chaîne commune mise en évidence 4. **Interactions** — clic = remplir + soumettre ; ↑/↓/Enter/Tab/Esc ; **priorité au popover `@`** quand il est ouvert ; `aria-listbox` + `aria-activedescendant` (le `role="combobox"` est déjà sur l'input). 5. **Télémétrie** — `suggest_shown`, `suggest_used` (à ajouter à la whitelist serveur de l'étape 13). 6. **Tests** — `npm run test:suggest` (unitaires parsing/dédup) + scénario e2e : `providers=yt` ne renvoie que des suggestions `yt`, provider inconnu → fallback registry complet. ### Critères d'acceptation - [x] Aucune requête réseau tant que `q.trim().length < 2` - [x] frappe rapide = une seule requête en vol (pas d'empilement / de réponse qui écrase la dernière) - [x] Esc ferme le panneau sans effacer le texte ; Tab complète sans lancer la recherche - [x] Un provider sans suggestions ou en échec n'affiche ni trou ni erreur dans le panneau - [x] Focus clavier et lecteurs d'écran cohérents avec l'existant (a11y Step 10) --- ## Step 16 — Transcripts (Phase 1 du `docs/transcript_architecture_dev.md`) ### Principe directeur > **Un endpoint, un module de parsing, une UI.** La disponibilité dépend de la plateforme, mais le > **code ne change pas** selon le provider (le doc appelle ça « Rung 2 : réutiliser, pas réinventer »). Source des données : `yt-dlp --dump-single-json --skip-download` (déjà utilisé côté serveur, ex. `server/index.mjs:1012`) → on lit `subtitles` / `automatic_captions` → on télécharge la piste `json3` ou `vtt` → on normalise en `{ lang, available, languages, lines: [{ t, dur, text }] }`. ### Périmètre Phase 1 (à faire) **Backend** - [x] `server/transcript.mjs` : fonctions **pures** `pickTrack(json, lang)`, `parseJson3(data)`, `parseVtt(text)` (testables sans réseau ni DB) - [x] Route `GET /api/transcript/:provider/:videoId?lang=&instance=&slug=&sourceUrl=` dans `server/index.mjs` (`instance/slug/sourceUrl` nécessaires pour PeerTube multi-instances ; réutiliser `providerUrlFrom()`). - [x] `transcriptCache` : `Map` + TTL long (24 h), clé `transcript:${provider}:${videoId}:${lang}`. - [x] Rate-limit (~10 req/min/IP) sur le motif `channelsLimiter`. - [x] Aucun sous-titre → **HTTP 200** `{ available: false }` (pas une erreur). - [x] Gestion 429 persistant + fallback « temp dir » (`writeAutoSub`) et variables d'env (`TRANSCRIPT_CACHE_TTL`, `TRANSCRIPT_RATE_LIMIT`, `TRANSCRIPT_FALLBACK_TMP`). **Frontend (variante UI #1 du doc)** - [x] Bouton « Transcript » à côté du bouton Download (`watch.component.html:159`). - [x] Panneau copié sur le motif du Download Panel (`watch.component.html:185`) : `toggleTranscript()` + `loadTranscript()`, chargement/absent/lines, `