feat(feedback): integrate Feedback Hub widget in Settings #101

Merged
maximus merged 1 commit from issue-68-feedback-widget into master 2026-07-28 00:54:15 +00:00
Owner

Fixes #68

Intègre le widget de feedback (Feedback Hub) dans l'app mobile.

Ce qui est fait

  • Bouton « Envoyer un feedback » dans Réglages > À propos (au-dessus du bouton mailto, conservé)
  • Modal RN (bottom-sheet) : champ texte multiligne + compteur 2000, opt-in contexte de navigation, opt-in identité
  • POST vers feedback.lacompagniemaximus.com/api/feedback avec app_id: simpl-liste
  • Contexte whitelisté côté client (page, locale, theme, viewport, userAgent, timestamp)
  • Gestion des erreurs (400 / 429 rate-limit / 5xx / réseau) + confirmation visuelle (succès auto-close 2s)
  • i18n fr/en (bloc feedback)

Décisions produit (validées)

  • Feedback in-app ajouté ET mailto conservé (deux entrées distinctes)
  • Identité opt-in décochée par défaut (Loi 25) : sub Logto envoyé seulement si connecté ET coché, sinon anonyme

Tests

  • tests/feedback.test.mjs (node:test) : exécution réelle des helpers purs + sendFeedback avec fetch stubbé (11 tests, verts)
  • tests/smoke.test.cjs : guards statiques (app_id exact simpl-liste, endpoint /api/feedback, cap 2000, service sans import)
  • npx tsc --noEmit : vert

Notes techniques

  • src/services/feedback.ts volontairement sans aucun import : chargé par node:test via le type-stripping TS natif (Node 22.18+), qui ne résout pas l'alias @/. Le smoke test verrouille cette invariante.
  • API publique sans auth ; CORS non-bloquant en natif (pas d'en-tête Origin)
Fixes #68 Intègre le widget de feedback (Feedback Hub) dans l'app mobile. ## Ce qui est fait - Bouton « Envoyer un feedback » dans Réglages > À propos (au-dessus du bouton mailto, conservé) - Modal RN (bottom-sheet) : champ texte multiligne + compteur 2000, opt-in contexte de navigation, opt-in identité - POST vers `feedback.lacompagniemaximus.com/api/feedback` avec `app_id: simpl-liste` - Contexte whitelisté côté client (page, locale, theme, viewport, userAgent, timestamp) - Gestion des erreurs (400 / 429 rate-limit / 5xx / réseau) + confirmation visuelle (succès auto-close 2s) - i18n fr/en (bloc `feedback`) ## Décisions produit (validées) - Feedback in-app ajouté ET mailto conservé (deux entrées distinctes) - Identité opt-in décochée par défaut (Loi 25) : sub Logto envoyé seulement si connecté ET coché, sinon anonyme ## Tests - `tests/feedback.test.mjs` (node:test) : exécution réelle des helpers purs + `sendFeedback` avec `fetch` stubbé (11 tests, verts) - `tests/smoke.test.cjs` : guards statiques (app_id exact `simpl-liste`, endpoint `/api/feedback`, cap 2000, service sans import) - `npx tsc --noEmit` : vert ## Notes techniques - `src/services/feedback.ts` volontairement sans aucun import : chargé par node:test via le type-stripping TS natif (Node 22.18+), qui ne résout pas l'alias `@/`. Le smoke test verrouille cette invariante. - API publique sans auth ; CORS non-bloquant en natif (pas d'en-tête Origin)
maximus added 1 commit 2026-07-27 23:44:05 +00:00
Add an in-app feedback form that POSTs to the centralized Feedback Hub
(feedback.lacompagniemaximus.com/api/feedback) with app_id "simpl-liste".

- src/services/feedback.ts: dependency-free client (pure helpers + fetch),
  strict context whitelist, 2000-char cap, stable error codes
- src/hooks/useFeedback.ts: idle/sending/success/error state machine
- src/components/FeedbackModal.tsx: RN bottom-sheet with char counter,
  opt-in navigation context and opt-in identity (Loi 25: both unchecked)
- Settings > About: new "Send feedback" button above the kept mailto entry
- i18n fr/en feedback block
- tests/feedback.test.mjs: node:test execution coverage of the client;
  smoke.test.cjs static guards (app_id, endpoint, cap, import-free)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
maximus added the
status:review
label 2026-07-27 23:44:39 +00:00
Author
Owner

Review PR #101 — feat(feedback): integrate Feedback Hub widget

Verdict : APPROVE

Résumé

Intégration propre et bien testée du Feedback Hub dans Réglages > À propos. Aucun problème bloquant : la conformité Loi 25 (identité opt-in décochée par défaut), la whitelist de contexte, la gestion d'erreurs exhaustive et l'i18n fr/en sont tous corrects. Quelques suggestions non bloquantes ci-dessous.

Points vérifiés

  • Loi 25 : user_id (sub Logto) envoyé uniquement si connecté ET coché. userId provient bien du store, alimenté par setUserId(claims.sub) au sign-in (settings.tsx:111). Le checkbox d'identité ne s'affiche que si userId existe. Conforme.
  • Contexte : whitelist client (sanitizeContext) alignée sur le serveur, valeurs tronquées à 500, champ omis si vide.
  • Erreurs : sendFeedback ne throw jamais ; 400/429/5xx/réseau mappés vers des codes stables, l'UI ne tombe jamais dans un état indéfini.
  • Couleurs : colors.priority.low, colors.terracotta.DEFAULT, colors.bleu.DEFAULT existent tous ; classes Tailwind (bg-bleu, etc.) déjà utilisées ailleurs.
  • i18n : bloc feedback complet dans fr.json et en.json, toutes les clés consommées existent.
  • Tests : exécution réelle des helpers purs + sendFeedback avec fetch stubbé (succès, omission contexte, user_id, statuts d'erreur, réseau) + 4 guards statiques (endpoint, app_id, cap 2000, import-free).
  • Commit : conventionnel, référence #68.

Suggestions (non bloquantes)

  1. Robustesse du timer d'auto-close (latent) — l'effet d'auto-close dépend de onClose, passé par le parent en arrow inline () => setShowFeedback(false) (nouvelle référence à chaque render). Aujourd'hui l'écran Réglages n'a pas de re-render périodique, donc le timer 2s se déclenche correctement. Mais si Réglages adopte un jour le polling 500ms documenté (présent sur d'autres écrans), chaque re-render parent réinitialiserait le timer et l'écran de succès cesserait de se fermer seul. Durcissement bon marché : useCallback sur onClose côté parent, ou capturer onClose dans une ref pour que l'effet ne dépende que de isSuccess.

  2. Plancher Node non déclarénpm test dépend désormais de Node ≥ 22.18 (type-stripping TS natif pour charger feedback.ts depuis le .mjs). Pas de champ engines, pas de .nvmrc, pas de CI pour l'imposer. Sur un Node plus ancien, node --test tests/feedback.test.mjs échoue à l'import du .ts. Ajouter "engines": { "node": ">=22.18" } (ou un .nvmrc) rendrait la contrainte découvrable.

  3. Guard import-free plus étroit que l'invariantstaticImportsOf ne matche que import … from. Le chemin type-stripping casse aussi sur la syntaxe TS non-effaçable (enum, namespace, parameter properties, import x = require()) — aucune présente aujourd'hui. Le guard attrape la régression la plus probable (ajout d'un import @/), donc simple note.

  4. Double-submit possible sur un double-tap très rapide avant que l'état sending désactive le bouton (même pattern que la modal de tags existante). Le rate-limit serveur (5/h) atténue. Pas de garde nécessaire.

## Review PR #101 — feat(feedback): integrate Feedback Hub widget **Verdict : APPROVE** ### Résumé Intégration propre et bien testée du Feedback Hub dans Réglages > À propos. Aucun problème bloquant : la conformité Loi 25 (identité opt-in décochée par défaut), la whitelist de contexte, la gestion d'erreurs exhaustive et l'i18n fr/en sont tous corrects. Quelques suggestions non bloquantes ci-dessous. ### Points vérifiés - **Loi 25** : `user_id` (sub Logto) envoyé uniquement si connecté ET coché. `userId` provient bien du store, alimenté par `setUserId(claims.sub)` au sign-in (`settings.tsx:111`). Le checkbox d'identité ne s'affiche que si `userId` existe. Conforme. - **Contexte** : whitelist client (`sanitizeContext`) alignée sur le serveur, valeurs tronquées à 500, champ omis si vide. - **Erreurs** : `sendFeedback` ne throw jamais ; 400/429/5xx/réseau mappés vers des codes stables, l'UI ne tombe jamais dans un état indéfini. - **Couleurs** : `colors.priority.low`, `colors.terracotta.DEFAULT`, `colors.bleu.DEFAULT` existent tous ; classes Tailwind (`bg-bleu`, etc.) déjà utilisées ailleurs. - **i18n** : bloc `feedback` complet dans `fr.json` et `en.json`, toutes les clés consommées existent. - **Tests** : exécution réelle des helpers purs + `sendFeedback` avec `fetch` stubbé (succès, omission contexte, user_id, statuts d'erreur, réseau) + 4 guards statiques (endpoint, app_id, cap 2000, import-free). - **Commit** : conventionnel, référence `#68`. ### Suggestions (non bloquantes) 1. **Robustesse du timer d'auto-close (latent)** — l'effet d'auto-close dépend de `onClose`, passé par le parent en arrow inline `() => setShowFeedback(false)` (nouvelle référence à chaque render). Aujourd'hui l'écran Réglages n'a pas de re-render périodique, donc le timer 2s se déclenche correctement. Mais si Réglages adopte un jour le polling 500ms documenté (présent sur d'autres écrans), chaque re-render parent réinitialiserait le timer et l'écran de succès cesserait de se fermer seul. Durcissement bon marché : `useCallback` sur `onClose` côté parent, ou capturer `onClose` dans une ref pour que l'effet ne dépende que de `isSuccess`. 2. **Plancher Node non déclaré** — `npm test` dépend désormais de Node ≥ 22.18 (type-stripping TS natif pour charger `feedback.ts` depuis le `.mjs`). Pas de champ `engines`, pas de `.nvmrc`, pas de CI pour l'imposer. Sur un Node plus ancien, `node --test tests/feedback.test.mjs` échoue à l'import du `.ts`. Ajouter `"engines": { "node": ">=22.18" }` (ou un `.nvmrc`) rendrait la contrainte découvrable. 3. **Guard import-free plus étroit que l'invariant** — `staticImportsOf` ne matche que `import … from`. Le chemin type-stripping casse aussi sur la syntaxe TS non-effaçable (enum, namespace, parameter properties, `import x = require()`) — aucune présente aujourd'hui. Le guard attrape la régression la plus probable (ajout d'un import `@/`), donc simple note. 4. **Double-submit possible** sur un double-tap très rapide avant que l'état `sending` désactive le bouton (même pattern que la modal de tags existante). Le rate-limit serveur (5/h) atténue. Pas de garde nécessaire.
maximus merged commit a45860e5fd into master 2026-07-28 00:54:15 +00:00
maximus deleted branch issue-68-feedback-widget 2026-07-28 00:54:15 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: maximus/simpl-liste#101
No description provided.