+ {pushStatus === "success" && t("routes.sendToWahooBanner.success")}
+ {pushStatus === "needs_permission" && t("routes.sendToWahooBanner.needsPermission")}
+ {pushStatus === "no_connection" && t("routes.sendToWahooBanner.noConnection")}
+ {pushStatus === "no_geometry" && t("routes.sendToWahooBanner.noGeometry")}
+ {pushStatus === "error" && pushErrorCode === "validation" &&
+ t("routes.sendToWahooBanner.validation")}
+ {pushStatus === "error" && pushErrorCode === "rate_limit" &&
+ t("routes.sendToWahooBanner.rateLimit")}
+ {pushStatus === "error" && pushErrorCode === "token_expired" &&
+ t("routes.sendToWahooBanner.tokenExpired")}
+ {pushStatus === "error" && (!pushErrorCode || pushErrorCode === "generic") &&
+ t("routes.sendToWahooBanner.generic")}
+
+ )}
+
{route.distance != null && (
diff --git a/openspec/changes/wahoo-route-push/tasks.md b/openspec/changes/wahoo-route-push/tasks.md
index 0749fa1..5e81a45 100644
--- a/openspec/changes/wahoo-route-push/tasks.md
+++ b/openspec/changes/wahoo-route-push/tasks.md
@@ -45,25 +45,25 @@
## 7. Route detail page UI
-- [ ] 7.1 In the route detail loader, include the latest `sync_pushes` row for the current version and the user's Wahoo connection presence + scopes
-- [ ] 7.2 Render the "Send to Wahoo" button next to the existing Export GPX action when conditions in spec §1 are met
-- [ ] 7.3 Render the post-push status: "Sent to Wahoo on " replacing the button when a successful push exists
-- [ ] 7.4 Render the failed-push state: "Last attempt failed: " with the button still active
-- [ ] 7.5 Add the button copy "This sends your route to Wahoo" as a tooltip or helper text near the button (privacy disclosure at the action point)
-- [ ] 7.6 Add i18n strings for all new UI copy (English + German) per the i18n convention in CLAUDE.md
+- [x] 7.1 In the route detail loader, include the latest `sync_pushes` row for the current version and the user's Wahoo connection presence + scopes
+- [x] 7.2 Render the "Send to Wahoo" button next to the existing Export GPX action when conditions in spec §1 are met
+- [x] 7.3 Render the post-push status: "Sent to Wahoo on " replacing the button when a successful push exists
+- [x] 7.4 Render the failed-push state: "Last attempt failed: " with the button still active
+- [x] 7.5 Add the button copy "This sends your route to Wahoo" as a tooltip or helper text near the button (privacy disclosure at the action point)
+- [x] 7.6 Add i18n strings for all new UI copy (English + German) per the i18n convention in CLAUDE.md
## 8. Privacy manifest
-- [ ] 8.1 Update `docs/privacy.md` (or whichever file holds the privacy manifest) to declare that route geometry, name, and description are sent to Wahoo when the user clicks "Send to Wahoo"
-- [ ] 8.2 Cross-link the privacy entry from the proposal and from this change's README in `openspec/changes/wahoo-route-push/`
+- [x] 8.1 Privacy disclosure added to `apps/journal/app/routes/legal.privacy.tsx` (the live in-app /legal/privacy page — this repo doesn't have a separate `docs/privacy.md` manifest). The new bullet describes that route geometry, name, and description are transmitted to Wahoo on opt-in.
+- [x] 8.2 Cross-linked: proposal.md already references the privacy disclosure, and `legal.privacy.tsx` is the canonical disclosure surface.
## 9. Tests
- [x] 9.1 Unit tests for `gpxToFitCourse` — already covered in §1.7
- [x] 9.2 Unit tests for the action route's idempotency logic (mock Wahoo HTTP layer): fresh push, re-push of pushed version, retry of failed push, push of new version after edit
- [x] 9.3 Unit tests for the scope-mismatch redirect flow
-- [ ] 9.4 E2E test in `e2e/`: log in as a user with a (mocked) Wahoo connection, open a route detail page, click "Send to Wahoo", assert the success toast and the "Sent to Wahoo on …" status appear
-- [ ] 9.5 E2E test for the re-auth flow: user without `routes_write` clicks Send to Wahoo, redirects to OAuth (mocked), returns, push completes, status renders
+- [ ] 9.4 E2E test for the success path — deferred. Needs new infra: a Wahoo HTTP mock alongside the dev server (intercepting `api.wahooligan.com/v1/routes` server-side, not via Playwright `page.route`). The unit-test layer in slice 4 already covers the pipeline against a mocked provider; promoting these to full-stack E2E should land with a shared `wahoo-mock.ts` fixture once we have a second push provider or a real-API smoke harness.
+- [ ] 9.5 E2E test for the re-auth flow — deferred for the same reason as 9.4.
## 10. Manual verification before merge
diff --git a/packages/i18n/src/locales/de.ts b/packages/i18n/src/locales/de.ts
index f0315e9..e9c4568 100644
--- a/packages/i18n/src/locales/de.ts
+++ b/packages/i18n/src/locales/de.ts
@@ -204,6 +204,21 @@ export default {
importGpx: "GPX importieren",
exportGpx: "GPX exportieren",
delete: "Route löschen",
+ sendToWahoo: "An Wahoo senden",
+ sendToWahooHelp: "Sendet die Route an Wahoo, damit sie auf deinem Bike-Computer erscheint.",
+ sentToWahoo: "Am {{date}} an Wahoo gesendet",
+ sendToWahooFailed: "Letzter Versuch fehlgeschlagen: {{error}}",
+ sendToWahooSending: "Sende…",
+ sendToWahooBanner: {
+ success: "Route an Wahoo gesendet. Schau in der Wahoo-App oder auf deinem Bike-Computer nach.",
+ needsPermission: "Wir brauchen deine Erlaubnis, Routen an Wahoo zu senden. Bitte neu verbinden.",
+ noConnection: "Verbinde zuerst dein Wahoo-Konto in den Einstellungen.",
+ noGeometry: "Diese Route hat noch keine Geometrie — plane zuerst einen Weg.",
+ validation: "Senden fehlgeschlagen — versuche, Name oder Beschreibung anzupassen.",
+ rateLimit: "Zu viele Anfragen an Wahoo. Bitte in einer Minute erneut versuchen.",
+ tokenExpired: "Deine Wahoo-Verbindung ist abgelaufen. Bitte in den Einstellungen neu verbinden.",
+ generic: "Senden an Wahoo fehlgeschlagen. Bitte später erneut versuchen.",
+ },
distance: "Strecke",
elevationGain: "Höhenmeter",
dayBreakdown: "Tagesübersicht",
diff --git a/packages/i18n/src/locales/en.ts b/packages/i18n/src/locales/en.ts
index f563991..9a48901 100644
--- a/packages/i18n/src/locales/en.ts
+++ b/packages/i18n/src/locales/en.ts
@@ -204,6 +204,21 @@ export default {
importGpx: "Import GPX",
exportGpx: "Export GPX",
delete: "Delete Route",
+ sendToWahoo: "Send to Wahoo",
+ sendToWahooHelp: "This sends your route to Wahoo so it appears on your bike computer.",
+ sentToWahoo: "Sent to Wahoo on {{date}}",
+ sendToWahooFailed: "Last attempt failed: {{error}}",
+ sendToWahooSending: "Sending…",
+ sendToWahooBanner: {
+ success: "Route sent to Wahoo. Check your Wahoo App or bike computer.",
+ needsPermission: "We need your permission to send routes to Wahoo. Please reconnect.",
+ noConnection: "Connect your Wahoo account in Settings first.",
+ noGeometry: "This route has no geometry yet — plan a path before sending.",
+ validation: "We couldn't send this route — try editing the name or description.",
+ rateLimit: "Too many requests to Wahoo. Try again in a minute.",
+ tokenExpired: "Your Wahoo connection expired. Please reconnect in Settings.",
+ generic: "Couldn't send to Wahoo. Try again later.",
+ },
distance: "Distance",
elevationGain: "Elevation Gain",
dayBreakdown: "Day Breakdown",