feat: feedback hub widget in Settings Logs card #100

Merged
maximus merged 1 commit from issue-67-feedback-widget into main 2026-04-17 14:36:27 +00:00
Owner

Fixes #67

Summary

Adds an opt-in Feedback Hub widget to Simpl'Résultat, integrated inside the existing Logs card on the Settings page. The same card now covers both diagnostics capture (logs) and diagnostics forwarding (feedback).

Implementation follows the cross-app conventions documented in la-compagnie-maximus/docs/feedback-hub-ops.md — wording, payload shape, context whitelist, Loi 25 opt-in checkboxes.

Design choices

  • Placement (deviation from cross-app norm): no FAB, no topbar icon. A Send feedback button sits inside LogViewerCard next to Copy/Clear. Justified by the app's minimalist privacy-first UI and the fact that logs — the exact thing a user might want to attach — live right next to the button.
  • Transport: proxy via Rust (send_feedback command): bypasses CORS (the Tauri origin is not in FEEDBACK_CORS_ORIGINS by design), aligns with the existing OAuth / license / updater patterns, and centralises the audit of what leaves the machine. Zero server-side change required.
  • Consent gate: first submission shows a one-time dialog explaining that this is the only feature that talks to a server besides updates and Maximus sign-in. Flag feedbackConsentAccepted persisted in localStorage.
  • Three opt-in checkboxes (all unchecked by default): navigation context, recent error logs (appended as a suffix to the content, not a custom context key), identify with the Maximus account (hidden if not logged in).
  • Context keys limited to the server whitelist (page, locale, theme, viewport, userAgent, timestamp). app_version + OS packed into userAgent via a get_feedback_user_agent helper so we don't add a new Tauri plugin.
  • Stable error codes returned from Rust (invalid, rate_limit, server_error, network_error) mapped to i18n toasts on the frontend.
  • CSP connect-src extended with https://feedback.lacompagniemaximus.com (unused today since we proxy via Rust, but kept for forward compatibility).

Files

Category Path
Rust cmd src-tauri/src/commands/feedback_commands.rs (new), src-tauri/src/commands/mod.rs, src-tauri/src/lib.rs
CSP src-tauri/tauri.conf.json
Services src/services/feedbackService.ts (new), src/services/logService.ts (added getRecentErrorLogs)
Hook src/hooks/useFeedback.ts (new)
UI src/components/settings/FeedbackDialog.tsx (new), src/components/settings/FeedbackConsentDialog.tsx (new), src/components/settings/LogViewerCard.tsx
i18n src/i18n/locales/{fr,en}.json — new feedback.* namespace + updated docs.settings to disclose the external channel
Docs docs/guide-utilisateur.md, CHANGELOG.md, CHANGELOG.fr.md

Test plan

  • cargo test --lib — 3 new Rust tests (context serde skip-none, userAgent camelCase, empty-content short-circuit) + 34 total passing
  • npm test — 13 new TS tests (feedbackService error-code type guard, getRecentErrorLogs formatting & filtering, feedbackReducer state machine) + 100 total passing
  • npm run build — tsc + vite build green
  • cargo check — green
  • Manual e2e: submit a feedback from the dev app, verify it lands in feedback_hub.feedbacks with app_id=simpl-resultat and only whitelisted context keys
  • Manual UX: confirm consent dialog shows only once; verify the identify checkbox is hidden when signed out; verify 429 and network errors display the right toast

Follow-ups

  • maximus/la-compagnie-maximus#100 — add simpl-resultat to the docs/feedback-hub-ops.md apps registry once this PR lands.
  • maximus/simpl-liste#68 — sibling issue for the mobile version, independent.
Fixes #67 ## Summary Adds an opt-in Feedback Hub widget to `Simpl'Résultat`, integrated inside the existing Logs card on the Settings page. The same card now covers both diagnostics capture (logs) and diagnostics forwarding (feedback). Implementation follows the cross-app conventions documented in [`la-compagnie-maximus/docs/feedback-hub-ops.md`](https://git.lacompagniemaximus.com/maximus/la-compagnie-maximus/src/branch/main/docs/feedback-hub-ops.md) — wording, payload shape, context whitelist, Loi 25 opt-in checkboxes. ## Design choices - **Placement (deviation from cross-app norm)**: no FAB, no topbar icon. A *Send feedback* button sits inside `LogViewerCard` next to Copy/Clear. Justified by the app's minimalist privacy-first UI and the fact that logs — the exact thing a user might want to attach — live right next to the button. - **Transport: proxy via Rust (`send_feedback` command)**: bypasses CORS (the Tauri origin is not in `FEEDBACK_CORS_ORIGINS` by design), aligns with the existing OAuth / license / updater patterns, and centralises the audit of what leaves the machine. **Zero server-side change required.** - **Consent gate**: first submission shows a one-time dialog explaining that this is the only feature that talks to a server besides updates and Maximus sign-in. Flag `feedbackConsentAccepted` persisted in `localStorage`. - **Three opt-in checkboxes (all unchecked by default)**: navigation context, recent error logs (appended as a suffix to the content, not a custom context key), identify with the Maximus account (hidden if not logged in). - **Context keys limited to the server whitelist** (`page`, `locale`, `theme`, `viewport`, `userAgent`, `timestamp`). `app_version` + OS packed into `userAgent` via a `get_feedback_user_agent` helper so we don't add a new Tauri plugin. - **Stable error codes** returned from Rust (`invalid`, `rate_limit`, `server_error`, `network_error`) mapped to i18n toasts on the frontend. - **CSP `connect-src` extended** with `https://feedback.lacompagniemaximus.com` (unused today since we proxy via Rust, but kept for forward compatibility). ## Files | Category | Path | |---|---| | Rust cmd | `src-tauri/src/commands/feedback_commands.rs` (new), `src-tauri/src/commands/mod.rs`, `src-tauri/src/lib.rs` | | CSP | `src-tauri/tauri.conf.json` | | Services | `src/services/feedbackService.ts` (new), `src/services/logService.ts` (added `getRecentErrorLogs`) | | Hook | `src/hooks/useFeedback.ts` (new) | | UI | `src/components/settings/FeedbackDialog.tsx` (new), `src/components/settings/FeedbackConsentDialog.tsx` (new), `src/components/settings/LogViewerCard.tsx` | | i18n | `src/i18n/locales/{fr,en}.json` — new `feedback.*` namespace + updated `docs.settings` to disclose the external channel | | Docs | `docs/guide-utilisateur.md`, `CHANGELOG.md`, `CHANGELOG.fr.md` | ## Test plan - [x] `cargo test --lib` — 3 new Rust tests (context serde skip-none, userAgent camelCase, empty-content short-circuit) + 34 total passing - [x] `npm test` — 13 new TS tests (feedbackService error-code type guard, `getRecentErrorLogs` formatting & filtering, `feedbackReducer` state machine) + 100 total passing - [x] `npm run build` — tsc + vite build green - [x] `cargo check` — green - [ ] **Manual e2e**: submit a feedback from the dev app, verify it lands in `feedback_hub.feedbacks` with `app_id=simpl-resultat` and only whitelisted context keys - [ ] **Manual UX**: confirm consent dialog shows only once; verify the identify checkbox is hidden when signed out; verify 429 and network errors display the right toast ## Follow-ups - `maximus/la-compagnie-maximus#100` — add `simpl-resultat` to the `docs/feedback-hub-ops.md` apps registry once this PR lands. - `maximus/simpl-liste#68` — sibling issue for the mobile version, independent.
maximus added 1 commit 2026-04-17 14:18:51 +00:00
feat: feedback hub widget in Settings Logs card (#67)
All checks were successful
PR Check / rust (push) Successful in 23m3s
PR Check / frontend (push) Successful in 2m19s
PR Check / rust (pull_request) Successful in 22m25s
PR Check / frontend (pull_request) Successful in 2m18s
3b3b6d9a32
Add an opt-in feedback submission flow that posts to the central
feedback-api service. Integrated into the existing LogViewerCard so
the same card now covers diagnostics capture (logs) and diagnostics
forwarding (feedback).

- Rust command `send_feedback` forwards the payload via reqwest, so
  the Tauri origin never needs a CORS whitelist entry server-side
- First submission shows a one-time consent dialog explaining that
  this is the only app feature that talks to a server besides updates
  and Maximus sign-in
- Three opt-in checkboxes (all unchecked by default): navigation
  context, recent error logs (appended as a suffix to the content),
  identify with the Maximus account
- Context keys are limited to the server whitelist (page, locale,
  theme, viewport, userAgent, timestamp); app_version + OS are packed
  into userAgent via `get_feedback_user_agent` so we don't pull in an
  extra Tauri plugin
- Error codes are stable strings (invalid, rate_limit, server_error,
  network_error) mapped to i18n messages on the frontend
- Wording follows the cross-app convention documented in
  `la-compagnie-maximus/docs/feedback-hub-ops.md`
- CSP `connect-src` extended with feedback.lacompagniemaximus.com
- Docs: CHANGELOG (EN + FR), guide utilisateur, docs.settings
  (features/steps/tips) updated in both locales

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Author
Owner

Review adversariale — PR #100

Verdict : APPROVE

Summary

PR bien architecturée qui ajoute un widget Feedback Hub opt-in dans la carte Logs des Settings. Le code respecte les principes privacy-first (consent gate one-time, opt-in par défaut, proxy via Rust), la couverture de tests est solide (3 tests Rust + 13 tests TS), et la doc/i18n sont à jour dans les deux langues. Aucun problème bloquant.

Checklist

  • Security : CSP étendu correctement, pas de secrets, stable error codes côté Rust, context whitelist respectée, pas d'injection (les valeurs passent en JSON). reqwest déjà présent dans Cargo.toml.
  • Correctness : consent gate persistée via localStorage, opt-in par défaut, le checkbox identify est caché quand déconnecté, auto-close après succès, désactivation des contrôles pendant sending.
  • Tests : 3 tests Rust (serde skip-none, camelCase userAgent, empty-content), 13 tests TS (reducer state machine, type guard, logService formatting). PR body mentionne cargo test --lib, npm test, npm run build, cargo check tous verts. Aucun .skip/.only.
  • Quality : conventions projet respectées (services/hooks/components séparés), commentaires en anglais, pas de dead code.
  • i18n : tout en i18n, namespace feedback.* ajouté dans FR et EN. Le remaniement reports.trends (déplacement dans la section reports) est cohérent avec les usages existants (ReportsTrendsPage.tsx utilise déjà reports.trends.subviewGlobal).
  • Data : aucune migration SQL — non applicable.
  • CHANGELOG : entrée dans les deux fichiers FR/EN sous ## [Unreleased] / Added.

Suggestions (non bloquantes)

  1. src-tauri/src/commands/feedback_commands.rs:78-82 — le test empty_content_is_rejected_locally fait un vrai appel send_feedback et s'appuie sur le fait que le check is_empty() arrive avant la construction du client. Extraire la validation dans une fonction pure (fn validate_content(s: &str) -> Result<&str, &'static str>) rendrait le test indépendant de l'ordre d'exécution.

  2. src-tauri/src/commands/feedback_commands.rs:95,103map_err(|_| "network_error") avale toutes les causes (timeout, DNS, TLS, reset). Un log::warn!("feedback transport failed: {e}") avant le mapping permettrait de diagnostiquer via les logs in-app — d'autant plus utile que c'est précisément le flow utilisé pour remonter des problèmes.

  3. src/components/settings/FeedbackDialog.tsx:82rawLogs.slice(-LOGS_SUFFIX_MAX) tronque au milieu d'une ligne. Tronquer au dernier \n avant la limite rendrait le suffixe plus lisible côté reviewer Feedback Hub.

  4. src/components/settings/FeedbackDialog.tsx:96-100 — en cas d'échec de getFeedbackUserAgent(), la string fallback "Simpl'Résultat" est envoyée sans version ni OS. Mieux vaudrait laisser userAgent absent du contexte (il est Optional<String> côté Rust donc propre).

  5. src/components/settings/LogViewerCard.tsx:20-22localStorage.getItem(...) n'est pas entouré d'un try/catch. OK en Tauri aujourd'hui, mais peu coûteux à défendre.

  6. src-tauri/src/commands/feedback_commands.rs:16-19feedback_endpoint() lit FEEDBACK_HUB_URL à chaque appel. Un OnceLock<String> éviterait la re-lecture. Cosmétique.


🤖 Review réalisée via /pr-review

## Review adversariale — PR #100 **Verdict : APPROVE** ### Summary PR bien architecturée qui ajoute un widget Feedback Hub opt-in dans la carte Logs des Settings. Le code respecte les principes privacy-first (consent gate one-time, opt-in par défaut, proxy via Rust), la couverture de tests est solide (3 tests Rust + 13 tests TS), et la doc/i18n sont à jour dans les deux langues. Aucun problème bloquant. ### Checklist - **Security** : CSP étendu correctement, pas de secrets, stable error codes côté Rust, context whitelist respectée, pas d'injection (les valeurs passent en JSON). `reqwest` déjà présent dans `Cargo.toml`. - **Correctness** : consent gate persistée via `localStorage`, opt-in par défaut, le checkbox `identify` est caché quand déconnecté, auto-close après succès, désactivation des contrôles pendant `sending`. - **Tests** : 3 tests Rust (serde skip-none, camelCase userAgent, empty-content), 13 tests TS (reducer state machine, type guard, logService formatting). PR body mentionne `cargo test --lib`, `npm test`, `npm run build`, `cargo check` tous verts. Aucun `.skip`/`.only`. - **Quality** : conventions projet respectées (services/hooks/components séparés), commentaires en anglais, pas de dead code. - **i18n** : tout en i18n, namespace `feedback.*` ajouté dans FR et EN. Le remaniement `reports.trends` (déplacement dans la section `reports`) est cohérent avec les usages existants (`ReportsTrendsPage.tsx` utilise déjà `reports.trends.subviewGlobal`). - **Data** : aucune migration SQL — non applicable. - **CHANGELOG** : entrée dans les deux fichiers FR/EN sous `## [Unreleased] / Added`. ### Suggestions (non bloquantes) 1. **`src-tauri/src/commands/feedback_commands.rs:78-82`** — le test `empty_content_is_rejected_locally` fait un vrai appel `send_feedback` et s'appuie sur le fait que le check `is_empty()` arrive avant la construction du client. Extraire la validation dans une fonction pure (`fn validate_content(s: &str) -> Result<&str, &'static str>`) rendrait le test indépendant de l'ordre d'exécution. 2. **`src-tauri/src/commands/feedback_commands.rs:95,103`** — `map_err(|_| "network_error")` avale toutes les causes (timeout, DNS, TLS, reset). Un `log::warn!("feedback transport failed: {e}")` avant le mapping permettrait de diagnostiquer via les logs in-app — d'autant plus utile que c'est *précisément* le flow utilisé pour remonter des problèmes. 3. **`src/components/settings/FeedbackDialog.tsx:82`** — `rawLogs.slice(-LOGS_SUFFIX_MAX)` tronque au milieu d'une ligne. Tronquer au dernier `\n` avant la limite rendrait le suffixe plus lisible côté reviewer Feedback Hub. 4. **`src/components/settings/FeedbackDialog.tsx:96-100`** — en cas d'échec de `getFeedbackUserAgent()`, la string fallback `"Simpl'Résultat"` est envoyée sans version ni OS. Mieux vaudrait laisser `userAgent` absent du contexte (il est `Optional<String>` côté Rust donc propre). 5. **`src/components/settings/LogViewerCard.tsx:20-22`** — `localStorage.getItem(...)` n'est pas entouré d'un try/catch. OK en Tauri aujourd'hui, mais peu coûteux à défendre. 6. **`src-tauri/src/commands/feedback_commands.rs:16-19`** — `feedback_endpoint()` lit `FEEDBACK_HUB_URL` à chaque appel. Un `OnceLock<String>` éviterait la re-lecture. Cosmétique. --- 🤖 Review réalisée via `/pr-review`
maximus merged commit 4f4ab87bea into main 2026-04-17 14:36:27 +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-Resultat#100
No description provided.