diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml
index 6868782b2..b55ba0e06 100644
--- a/docs/api/openapi.yaml
+++ b/docs/api/openapi.yaml
@@ -6749,6 +6749,30 @@ paths:
"401": *a1
"422": *a3
"429": *a4
+ /api/onboarding/tour:
+ post:
+ tags:
+ - Onboarding
+ summary: Update module-tour completion + resume point
+ description: Persists the module-tour state. The client posts a fire-and-forget `progress` checkpoint on each step so a
+ reload resumes at the right module, and a terminal `completed:true` with `outcome` when the tour ends.
+ `completed:false` is a replay reset that also clears the resume point.
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/TourUpdateRequest"
+ responses:
+ "200":
+ description: Tour state updated.
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/TourUpdateEnvelope"
+ "401": *a1
+ "422": *a3
+ "429": *a4
components:
schemas:
PasskeyLoginVerifyRequest:
@@ -8079,6 +8103,55 @@ components:
type: string
required:
- ids
+ TourUpdateRequest:
+ type: object
+ properties:
+ completed:
+ description: Flip the coarse completion flag. `false` is a replay reset and clears the stored progress point.
+ type: boolean
+ outcome:
+ description: Informational — distinguishes reaching the end from a skip.
+ type: string
+ enum:
+ - completed
+ - skipped
+ progress:
+ description: Mid-tour resume checkpoint. May arrive alone or with `completed`.
+ $ref: "#/components/schemas/TourProgress"
+ description: Update the module-tour state. Provide `completed` and/or `progress`.
+ TourProgress:
+ type: object
+ properties:
+ lastStopId:
+ anyOf:
+ - type: string
+ minLength: 1
+ maxLength: 64
+ - type: "null"
+ completedStopIds:
+ default: []
+ maxItems: 64
+ type: array
+ items:
+ type: string
+ minLength: 1
+ maxLength: 64
+ status:
+ type: string
+ enum:
+ - in_progress
+ - completed
+ - skipped
+ updatedAt:
+ type: string
+ format: date-time
+ pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$
+ required:
+ - lastStopId
+ - status
+ - updatedAt
+ description: Resumable module-tour progress point. `lastStopId` seeds the resume index; `status` is the running/terminal
+ state.
MedicationTreatmentClass:
type: string
enum:
@@ -19363,6 +19436,37 @@ components:
description: "Cross-episode retrospective summary over the trailing window: episode + resolved counts, the typical
(median) recovery gap (null below the min-sample floor — withholds a thin claim), a recurrence-by-month tally,
and a per-type breakdown. Retrospective only."
+ TourUpdateEnvelope:
+ type: object
+ properties:
+ data:
+ $ref: "#/components/schemas/TourUpdateResponse"
+ error:
+ type: "null"
+ meta:
+ type: object
+ properties:
+ requestId:
+ type: string
+ additionalProperties: false
+ required:
+ - data
+ - error
+ additionalProperties: false
+ TourUpdateResponse:
+ type: object
+ properties:
+ onboardingTourCompleted:
+ type: boolean
+ progress:
+ anyOf:
+ - $ref: "#/components/schemas/TourProgressOutput"
+ - type: "null"
+ required:
+ - onboardingTourCompleted
+ - progress
+ additionalProperties: false
+ description: The persisted completion flag and resume point after the write.
MedicationScheduleOutput:
type: object
properties:
@@ -19634,6 +19738,41 @@ components:
compatibility and are normalised to their English equivalents before persisting; GET responses always carry the
canonical English ids. Section ids are English-only. The legacy tile ids are deprecated and will be removed in a
future major version."
+ TourProgressOutput:
+ type: object
+ properties:
+ lastStopId:
+ anyOf:
+ - type: string
+ minLength: 1
+ maxLength: 64
+ - type: "null"
+ completedStopIds:
+ default: []
+ maxItems: 64
+ type: array
+ items:
+ type: string
+ minLength: 1
+ maxLength: 64
+ status:
+ type: string
+ enum:
+ - in_progress
+ - completed
+ - skipped
+ updatedAt:
+ type: string
+ format: date-time
+ pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$
+ required:
+ - lastStopId
+ - completedStopIds
+ - status
+ - updatedAt
+ additionalProperties: false
+ description: Resumable module-tour progress point. `lastStopId` seeds the resume index; `status` is the running/terminal
+ state.
Medication:
type: object
properties:
diff --git a/messages/de.json b/messages/de.json
index 21ee4c4f2..d9fab76e8 100644
--- a/messages/de.json
+++ b/messages/de.json
@@ -3533,31 +3533,72 @@
"back": "Zurück",
"skip": "Tour überspringen",
"done": "Verstanden",
- "restart": "Tour neu starten",
- "restartHint": "Zeige die Dashboard-Tour beim nächsten Aufruf erneut.",
- "restartConfirmation": "Die Tour startet bei deinem nächsten Dashboard-Besuch erneut.",
+ "restart": "Modul-Tour neu starten",
+ "restartHint": "Zeigt die geführte Modul-Tour beim nächsten Dashboard-Besuch von vorne.",
+ "restartConfirmation": "Die Modul-Tour startet bei deinem nächsten Dashboard-Besuch von vorne.",
"steps": {
- "tileStrip": {
- "title": "Deine Tageswerte",
- "body": "Die Kachel-Leiste zeigt den letzten Wert und den 7-Tage-Trend für jede Metrik — Gewicht, Blutdruck, Puls, Stimmung. Was du nicht brauchst, blendest du in Einstellungen → Dashboard aus."
+ "dashboardOverview": {
+ "title": "Dein Dashboard",
+ "body": "Das Dashboard bündelt alle Module auf einen Blick. Halte eine Kachel lange gedrückt, um sie neu anzuordnen oder auszublenden (Einstellungen → Dashboard-Layout). Jede Kachel ist nur so aussagekräftig wie die Daten dahinter — die folgenden Module füllen sie."
},
"quickAdd": {
- "title": "In zwei Taps erfasst",
- "body": "Über den Hinzufügen-Button kannst du Messungen oder Stimmungseinträge anlegen, ohne das Dashboard zu verlassen. Das Formular merkt sich deinen letzten Kontext und Tags."
+ "title": "In Sekunden erfasst",
+ "body": "Über „+ Hinzufügen“ trägst du jeden Messwert in Sekunden ein — Gewicht, Blutdruck, Puls, Glukose. Versteckte Stärke: Werte lassen sich nachträglich datieren. Alles, was du hier einträgst, fließt in Trends, Health-Score und die KI-Insights."
+ },
+ "measurements": {
+ "title": "Messwerte & Zielbereiche",
+ "body": "Messwerte zeigt Verlauf und Zielbereiche pro Metrik. Hidden Gem: Zielbereiche pro Wert definieren, damit Abweichungen farblich auffallen. Diese Reihen sind die Grundlage für Korrelationen im Coach und den Arztbericht."
+ },
+ "medications": {
+ "title": "Medikamente & Einnahmen",
+ "body": "Medikamente verwaltet Zeitpläne, Einnahmen, Bestand und Titration. Versteckt: wiederkehrende Pläne (RRULE/rollierend) und Bestandswarnungen. Deine Einnahmequote speist die Compliance-Säule im Dashboard und den Kontext der KI."
+ },
+ "labs": {
+ "title": "Laborwerte",
+ "body": "Labore erfasst Biomarker mit Referenzbereichen — Blutbild, Cholesterin, HbA1c. Stärke: Import und FHIR-Export. Auffällige Laborwerte tauchen als Belege in den Insights und im Arztbericht wieder auf."
+ },
+ "illness": {
+ "title": "Krankheits-Journal",
+ "body": "Das Krankheits-Journal dokumentiert akute Episoden und chronische Diagnosen samt Schüben. Versteckt: die Korrelation einer Episode mit deinen Messwerten und der Ruhemodus, der Reminder pausiert. So versteht der Coach, warum Werte zeitweise abweichen."
+ },
+ "vorsorge": {
+ "title": "Vorsorge",
+ "body": "Vorsorge erinnert an Untersuchungen und Impfungen mit eigenem Intervall. Tipp: „Erledigt“ kann direkt eine Messung oder ein Laborergebnis anlegen. Fällige Vorsorgen erscheinen als Reminder und im Arztbericht."
+ },
+ "cycle": {
+ "title": "Zyklus",
+ "body": "Die Zyklus-Verfolgung protokolliert Phasen und Symptome. Versteckt: Symptome lassen sich Stimmung und Schlaf gegenüberstellen. Der Zyklus liefert Kontext für Schwankungen, die sonst unerklärlich wären."
+ },
+ "mood": {
+ "title": "Stimmung",
+ "body": "Stimmung erfasst dein Befinden und Tags in zwei Sekunden. Stärke: eigene Tags. Stimmungsverläufe korrelieren mit Schlaf, Medikamenten und Zyklus — der Coach erkennt Muster."
},
"insights": {
- "title": "Automatische Einblicke",
- "body": "Insights verwandelt deine Werte in eine klare Gesundheits-Zusammenfassung, mit Einordnung von Blutdruck, Gewicht und Stimmung. Eigener Provider-Key oder Standard — du entscheidest."
+ "title": "Insights",
+ "body": "Insights ist das Herzstück: belegbasierte, dynamische Auswertungen statt generischer Tipps — jede Aussage verweist auf deine echten Daten. Versteckt: das Layout ist anpassbar. Insights zieht alles zusammen, was die Module oben liefern."
+ },
+ "coach": {
+ "title": "Coach",
+ "body": "Der Coach beantwortet Fragen zu deinen Daten im Dialog und nutzt Anamnese, Medikamente und Laborwerte als Kontext. Den KI-Anbieter wählst du selbst (eigener Schlüssel oder lokal). Er rechnet nichts neu — er liest dieselben Zahlen wie das Dashboard."
},
"integrations": {
- "title": "Geräte verbinden",
- "body": "In den Einstellungen verbindest du Withings (Auto-Sync für Gewicht, Blutdruck, Puls), moodLog oder deinen Auswertungs-Provider. Alles bleibt optional und verschlüsselt."
+ "title": "Integrationen",
+ "body": "Integrationen synchronisieren Withings, WHOOP, Polar, Oura und Apple Health automatisch. So füllen sich Gewicht, Schlaf und Erholung ohne manuelles Tippen — und nähren wiederum Insights und Coach."
+ },
+ "export": {
+ "title": "Arztbericht",
+ "body": "Der Arztbericht bündelt Messwerte, Medikamente, Laborwerte und Vorsorge als PDF oder FHIR für den Termin. Hier laufen alle Module zusammen — ein vollständiger Bericht entsteht nur bei gepflegten Daten."
},
"achievements": {
- "title": "Streaks und Badges sammeln",
- "body": "Die Achievements-Seite trackt tägliche Logging-Streaks, Blutdruck-Meilensteine und Konsistenz-Abzeichen. Ein kleiner Anreiz, dranzubleiben."
+ "title": "Erfolge",
+ "body": "Erfolge belohnen Konsistenz — Streaks und Meilensteine. Ein kleiner Motivations-Loop, der das Dranbleiben leichter macht."
+ },
+ "wrapUp": {
+ "title": "Das war die Tour",
+ "body": "Du kannst jedes Modul jederzeit einzeln erneut ansehen („Diese Tour zeigen“ auf jeder Seite) oder die komplette Tour unter Einstellungen → Erweitert neu starten."
}
- }
+ },
+ "moduleTrigger": "Diese Tour zeigen"
},
"shell": {
"back": "Zurück",
@@ -4469,9 +4510,7 @@
"changelog": "Changelog",
"docs": "Dokumentation",
"linksHeading": "Quellen & Dokumentation",
- "newerAvailable": "Neue Version: {tag}",
- "tourReplay": "Tour ansehen",
- "tourReplayHint": "Zeige die Dashboard-Tour — praktisch, falls du sie beim ersten Besuch übersprungen hast."
+ "newerAvailable": "Neue Version: {tag}"
},
"testConnection": {
"test": "Verbindung testen",
diff --git a/messages/en.json b/messages/en.json
index e91951fe9..eed712aac 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -3533,31 +3533,72 @@
"back": "Back",
"skip": "Skip tour",
"done": "Got it",
- "restart": "Restart onboarding tour",
- "restartHint": "Replay the dashboard walkthrough from the start.",
- "restartConfirmation": "Tour will restart on your next dashboard visit.",
+ "restart": "Restart module tour",
+ "restartHint": "Replays the guided module tour from the start on your next dashboard visit.",
+ "restartConfirmation": "The module tour will restart from the beginning on your next dashboard visit.",
"steps": {
- "tileStrip": {
- "title": "Your daily numbers",
- "body": "The tile strip shows the latest reading and a 7-day trend for every metric you log — weight, blood pressure, pulse, mood. Hide what you don't need from Settings → Dashboard."
+ "dashboardOverview": {
+ "title": "Your dashboard",
+ "body": "The dashboard pulls every module together at a glance. Long-press a tile to reorder or hide it (Settings → Dashboard layout). Each tile is only as telling as the data behind it — the modules ahead fill them."
},
"quickAdd": {
- "title": "Log anything in two taps",
- "body": "Use the Add button to drop a measurement or mood entry without leaving the dashboard. The form remembers your last context and tags."
+ "title": "Logged in seconds",
+ "body": "Use “+ Add” to log any reading in seconds — weight, blood pressure, pulse, glucose. Hidden strength: backdate readings you forgot to enter. Everything you log here feeds trends, the health score, and the AI insights."
+ },
+ "measurements": {
+ "title": "Readings & target ranges",
+ "body": "Measurements shows the history and target range for each metric. Hidden gem: set a target range per value so deviations stand out in colour. These series are the basis for the Coach’s correlations and the doctor report."
+ },
+ "medications": {
+ "title": "Medications & intakes",
+ "body": "Medications manages schedules, intakes, stock, and titration. Hidden: recurring plans (RRULE/rolling) and low-stock warnings. Your intake rate feeds the compliance pillar on the dashboard and the AI’s context."
+ },
+ "labs": {
+ "title": "Lab values",
+ "body": "Labs captures biomarkers with reference ranges — blood count, cholesterol, HbA1c. Strength: import and FHIR export. Out-of-range lab values resurface as evidence in the insights and the doctor report."
+ },
+ "illness": {
+ "title": "Illness journal",
+ "body": "The illness journal records acute episodes and chronic diagnoses with their flares. Hidden: correlate an episode with your readings, and Rest Mode that pauses reminders. That’s how the Coach understands why values drift for a while."
+ },
+ "vorsorge": {
+ "title": "Preventive care",
+ "body": "Preventive care reminds you of check-ups and vaccinations on their own interval. Tip: “Done” can create a reading or a lab result directly. Due items appear as reminders and in the doctor report."
+ },
+ "cycle": {
+ "title": "Cycle",
+ "body": "Cycle tracking logs phases and symptoms. Hidden: compare symptoms against mood and sleep. The cycle gives context for swings that would otherwise look unexplained."
+ },
+ "mood": {
+ "title": "Mood",
+ "body": "Mood captures how you feel plus tags in two seconds. Strength: your own tags. Mood trends correlate with sleep, medications, and cycle — the Coach spots the patterns."
},
"insights": {
- "title": "Automatic insights",
- "body": "Insights turns your readings into a plain-English health summary, with classifications for blood pressure, weight trend and mood. Bring your own provider key or use the built-in default."
+ "title": "Insights",
+ "body": "Insights is the heart of it: evidence-based, dynamic analysis instead of generic tips — every statement points at your real data. Hidden: the layout is customizable. Insights pulls together everything the modules above provide."
+ },
+ "coach": {
+ "title": "Coach",
+ "body": "The Coach answers questions about your data in a dialogue, using your history, medications, and lab values as context. Pick your AI provider yourself (your own key or local). It recomputes nothing — it reads the same numbers as the dashboard."
},
"integrations": {
- "title": "Connect your devices",
- "body": "Open Settings to link Withings (auto-sync weight, BP and pulse), moodLog, or your Insights provider. Everything stays optional and encrypted — and under Settings → Modules you can turn whole areas like sleep, mood or achievements on or off to keep HealthLog focused on what you track."
+ "title": "Integrations",
+ "body": "Integrations sync Withings, WHOOP, Polar, Oura, and Apple Health automatically. Weight, sleep, and recovery fill in without manual typing — and feed the insights and Coach in turn."
+ },
+ "export": {
+ "title": "Doctor report",
+ "body": "The doctor report bundles readings, medications, lab values, and preventive care as PDF or FHIR for the appointment. This is where every module converges — a complete report only comes from well-kept data."
},
"achievements": {
- "title": "Earn streaks and badges",
- "body": "The Achievements page tracks daily logging streaks, blood-pressure milestones and consistency badges. A small nudge to keep going."
+ "title": "Achievements",
+ "body": "Achievements reward consistency — streaks and milestones. A small motivation loop that makes sticking with it easier."
+ },
+ "wrapUp": {
+ "title": "That’s the tour",
+ "body": "You can revisit any module on its own at any time (“Show this tour” on every page) or restart the whole tour under Settings → Advanced."
}
- }
+ },
+ "moduleTrigger": "Show this tour"
},
"shell": {
"back": "Back",
@@ -4469,9 +4510,7 @@
"changelog": "Changelog",
"docs": "Documentation",
"linksHeading": "Sources & docs",
- "newerAvailable": "Newer version: {tag}",
- "tourReplay": "Replay the tour",
- "tourReplayHint": "Watch the dashboard walkthrough — handy if you skipped it on first visit."
+ "newerAvailable": "Newer version: {tag}"
},
"testConnection": {
"test": "Test connection",
diff --git a/messages/es.json b/messages/es.json
index 6f2928108..2bafb2f5c 100644
--- a/messages/es.json
+++ b/messages/es.json
@@ -3533,31 +3533,72 @@
"back": "Atrás",
"skip": "Saltar tour",
"done": "Entendido",
- "restart": "Reiniciar tour",
- "restartHint": "Vuelve a mostrar el tour del panel la próxima vez.",
- "restartConfirmation": "El tour se reiniciará en tu próxima visita al panel.",
+ "restart": "Reiniciar la visita de módulos",
+ "restartHint": "Vuelve a reproducir la visita guiada de módulos desde el principio en tu próxima visita al panel.",
+ "restartConfirmation": "La visita de módulos se reiniciará desde el principio en tu próxima visita al panel.",
"steps": {
- "tileStrip": {
- "title": "Tus valores del día",
- "body": "La fila de tarjetas muestra el último valor y la tendencia de 7 días de cada métrica — peso, tensión arterial, pulso, ánimo. Oculta lo que no necesites en Ajustes → Panel."
+ "dashboardOverview": {
+ "title": "Tu panel",
+ "body": "El panel reúne todos los módulos de un vistazo. Mantén pulsada una tarjeta para reordenarla u ocultarla (Ajustes → Diseño del panel). Cada tarjeta vale tanto como los datos que tiene detrás: los módulos siguientes los rellenan."
},
"quickAdd": {
- "title": "Registrado en dos toques",
- "body": "Con el botón Añadir puedes registrar mediciones o entradas de ánimo sin salir del panel. El formulario recuerda tu último contexto y etiquetas."
+ "title": "Registrado en segundos",
+ "body": "Con «+ Añadir» registras cualquier medición en segundos: peso, tensión, pulso, glucosa. Fuerza oculta: fechar valores olvidados. Todo lo que registras aquí alimenta las tendencias, la puntuación de salud y las ideas de IA."
+ },
+ "measurements": {
+ "title": "Mediciones y rangos objetivo",
+ "body": "Mediciones muestra el historial y el rango objetivo de cada métrica. Joya oculta: define un rango por valor para que las desviaciones resalten en color. Estas series son la base de las correlaciones del Coach y del informe médico."
+ },
+ "medications": {
+ "title": "Medicación y tomas",
+ "body": "Medicación gestiona horarios, tomas, existencias y titulación. Oculto: planes recurrentes (RRULE/rotativos) y avisos de poco stock. Tu tasa de tomas alimenta el pilar de cumplimiento del panel y el contexto de la IA."
+ },
+ "labs": {
+ "title": "Valores de laboratorio",
+ "body": "Laboratorio recoge biomarcadores con rangos de referencia: hemograma, colesterol, HbA1c. Fuerza: importación y exportación FHIR. Los valores fuera de rango reaparecen como pruebas en las ideas y en el informe médico."
+ },
+ "illness": {
+ "title": "Diario de enfermedades",
+ "body": "El diario de enfermedades registra episodios agudos y diagnósticos crónicos con sus brotes. Oculto: correlacionar un episodio con tus mediciones y el Modo descanso que pausa los recordatorios. Así el Coach entiende por qué los valores varían un tiempo."
+ },
+ "vorsorge": {
+ "title": "Prevención",
+ "body": "Prevención te recuerda revisiones y vacunas con su propio intervalo. Consejo: «Hecho» puede crear directamente una medición o un resultado de laboratorio. Lo pendiente aparece como recordatorio y en el informe médico."
+ },
+ "cycle": {
+ "title": "Ciclo",
+ "body": "El seguimiento del ciclo registra fases y síntomas. Oculto: comparar síntomas con el ánimo y el sueño. El ciclo aporta contexto a variaciones que de otro modo parecerían inexplicables."
+ },
+ "mood": {
+ "title": "Ánimo",
+ "body": "Ánimo capta cómo te sientes y etiquetas en dos segundos. Fuerza: tus propias etiquetas. Las tendencias de ánimo se correlacionan con el sueño, la medicación y el ciclo: el Coach detecta los patrones."
},
"insights": {
- "title": "Insights automáticos",
- "body": "Insights convierte tus valores en un resumen claro de salud, con interpretación de tensión arterial, peso y ánimo. Clave propia o por defecto — tú eliges."
+ "title": "Ideas",
+ "body": "Ideas es el corazón: análisis dinámico y basado en pruebas en lugar de consejos genéricos; cada afirmación apunta a tus datos reales. Oculto: el diseño es personalizable. Ideas reúne todo lo que aportan los módulos anteriores."
+ },
+ "coach": {
+ "title": "Coach",
+ "body": "El Coach responde preguntas sobre tus datos en diálogo, usando tu historial, medicación y valores de laboratorio como contexto. Tú eliges el proveedor de IA (tu propia clave o local). No recalcula nada: lee las mismas cifras que el panel."
},
"integrations": {
- "title": "Conectar dispositivos",
- "body": "En Ajustes conectas Withings (sincronización automática de peso, tensión y pulso), moodLog o tu proveedor de análisis. Todo es opcional y queda cifrado."
+ "title": "Integraciones",
+ "body": "Las integraciones sincronizan Withings, WHOOP, Polar, Oura y Apple Health automáticamente. Peso, sueño y recuperación se rellenan sin teclear, y a su vez alimentan las ideas y el Coach."
+ },
+ "export": {
+ "title": "Informe médico",
+ "body": "El informe médico reúne mediciones, medicación, valores de laboratorio y prevención en PDF o FHIR para la cita. Aquí convergen todos los módulos: un informe completo solo sale de datos bien cuidados."
},
"achievements": {
- "title": "Acumula rachas e insignias",
- "body": "La página de Logros lleva la cuenta de rachas de registro diario, hitos de tensión arterial e insignias de constancia. Un pequeño empujón para no abandonar."
+ "title": "Logros",
+ "body": "Los logros premian la constancia: rachas e hitos. Un pequeño bucle de motivación que facilita seguir adelante."
+ },
+ "wrapUp": {
+ "title": "Esto es la visita",
+ "body": "Puedes volver a ver cualquier módulo por separado cuando quieras («Mostrar esta visita» en cada página) o reiniciar la visita completa en Ajustes → Avanzado."
}
- }
+ },
+ "moduleTrigger": "Mostrar esta visita"
},
"shell": {
"back": "Atrás",
@@ -4469,9 +4510,7 @@
"changelog": "Changelog",
"docs": "Documentación",
"linksHeading": "Fuentes y documentación",
- "newerAvailable": "Nueva versión: {tag}",
- "tourReplay": "Volver a ver el recorrido",
- "tourReplayHint": "Mira el recorrido del panel — útil si lo saltaste en la primera visita."
+ "newerAvailable": "Nueva versión: {tag}"
},
"testConnection": {
"test": "Probar conexión",
diff --git a/messages/fr.json b/messages/fr.json
index d2a4076fe..591813a41 100644
--- a/messages/fr.json
+++ b/messages/fr.json
@@ -3533,31 +3533,72 @@
"back": "Précédent",
"skip": "Passer la visite",
"done": "Compris",
- "restart": "Relancer la visite",
- "restartHint": "Affiche à nouveau la visite du tableau de bord à la prochaine ouverture.",
- "restartConfirmation": "La visite redémarrera lors de ta prochaine visite du tableau de bord.",
+ "restart": "Relancer la visite des modules",
+ "restartHint": "Rejoue la visite guidée des modules depuis le début lors de votre prochaine ouverture du tableau de bord.",
+ "restartConfirmation": "La visite des modules redémarrera depuis le début à votre prochaine ouverture du tableau de bord.",
"steps": {
- "tileStrip": {
- "title": "Tes valeurs du jour",
- "body": "La barre de tuiles affiche la dernière valeur et la tendance 7 jours de chaque métrique — poids, tension, pouls, humeur. Masque ce qui ne te sert pas dans Paramètres → Tableau de bord."
+ "dashboardOverview": {
+ "title": "Votre tableau de bord",
+ "body": "Le tableau de bord rassemble tous les modules d’un coup d’œil. Appuyez longuement sur une tuile pour la réorganiser ou la masquer (Réglages → Disposition du tableau de bord). Une tuile ne vaut que par les données derrière elle — les modules suivants les remplissent."
},
"quickAdd": {
- "title": "Saisi en deux tapotements",
- "body": "Via le bouton Ajouter, tu saisis des mesures ou des humeurs sans quitter le tableau de bord. Le formulaire mémorise le dernier contexte et les étiquettes."
+ "title": "Saisi en quelques secondes",
+ "body": "Avec « + Ajouter », saisissez n’importe quelle mesure en quelques secondes — poids, tension, pouls, glycémie. Atout caché : antidater les valeurs oubliées. Tout ce que vous saisissez ici alimente les tendances, le score santé et les analyses IA."
+ },
+ "measurements": {
+ "title": "Mesures et plages cibles",
+ "body": "Mesures affiche l’historique et la plage cible de chaque métrique. Astuce cachée : définir une plage par valeur pour que les écarts ressortent en couleur. Ces séries sont la base des corrélations du Coach et du rapport médical."
+ },
+ "medications": {
+ "title": "Médicaments et prises",
+ "body": "Médicaments gère plannings, prises, stock et titration. Caché : plans récurrents (RRULE/glissants) et alertes de stock bas. Votre taux de prise alimente le pilier d’observance du tableau de bord et le contexte de l’IA."
+ },
+ "labs": {
+ "title": "Valeurs de laboratoire",
+ "body": "Laboratoire recense les biomarqueurs avec plages de référence — hémogramme, cholestérol, HbA1c. Atout : import et export FHIR. Les valeurs hors plage réapparaissent comme preuves dans les analyses et le rapport médical."
+ },
+ "illness": {
+ "title": "Journal des maladies",
+ "body": "Le journal des maladies consigne les épisodes aigus et les diagnostics chroniques avec leurs poussées. Caché : corréler un épisode à vos mesures et le Mode repos qui suspend les rappels. Le Coach comprend ainsi pourquoi les valeurs dérivent un temps."
+ },
+ "vorsorge": {
+ "title": "Prévention",
+ "body": "Prévention vous rappelle examens et vaccins selon leur propre intervalle. Astuce : « Fait » peut créer directement une mesure ou un résultat de laboratoire. Les échéances apparaissent en rappels et dans le rapport médical."
+ },
+ "cycle": {
+ "title": "Cycle",
+ "body": "Le suivi du cycle consigne phases et symptômes. Caché : comparer les symptômes à l’humeur et au sommeil. Le cycle donne du contexte aux variations qui paraîtraient autrement inexpliquées."
+ },
+ "mood": {
+ "title": "Humeur",
+ "body": "Humeur saisit votre ressenti et des tags en deux secondes. Atout : vos propres tags. Les tendances d’humeur se corrèlent au sommeil, aux médicaments et au cycle — le Coach repère les schémas."
},
"insights": {
- "title": "Insights automatiques",
- "body": "Insights transforme tes valeurs en un résumé santé clair, avec un classement de la tension, du poids et de l’humeur. Clé personnelle ou par défaut — c’est toi qui choisis."
+ "title": "Analyses",
+ "body": "Analyses, c’est le cœur : une analyse dynamique et fondée sur des preuves plutôt que des conseils génériques — chaque affirmation pointe vos données réelles. Caché : la disposition est personnalisable. Analyses réunit tout ce que les modules ci-dessus fournissent."
+ },
+ "coach": {
+ "title": "Coach",
+ "body": "Le Coach répond à vos questions sur vos données en dialogue, en utilisant antécédents, médicaments et résultats comme contexte. Vous choisissez le fournisseur IA (votre clé ou local). Il ne recalcule rien — il lit les mêmes chiffres que le tableau de bord."
},
"integrations": {
- "title": "Connecter des appareils",
- "body": "Dans les paramètres, tu connectes Withings (synchronisation automatique du poids, de la tension et du pouls), moodLog ou ton fournisseur d’analyse. Tout reste optionnel et chiffré."
+ "title": "Intégrations",
+ "body": "Les intégrations synchronisent Withings, WHOOP, Polar, Oura et Apple Santé automatiquement. Poids, sommeil et récupération se remplissent sans saisie, et alimentent à leur tour les analyses et le Coach."
+ },
+ "export": {
+ "title": "Rapport médical",
+ "body": "Le rapport médical regroupe mesures, médicaments, valeurs de laboratoire et prévention en PDF ou FHIR pour le rendez-vous. Tous les modules convergent ici — un rapport complet ne vient que de données bien tenues."
},
"achievements": {
- "title": "Collecter séries et badges",
- "body": "La page Succès suit les séries de saisie quotidienne, les paliers de tension et les badges de constance. Un petit coup de pouce pour rester régulier."
+ "title": "Réussites",
+ "body": "Les réussites récompensent la régularité — séries et jalons. Une petite boucle de motivation qui aide à tenir bon."
+ },
+ "wrapUp": {
+ "title": "C’est la fin de la visite",
+ "body": "Vous pouvez revoir chaque module séparément à tout moment (« Afficher cette visite » sur chaque page) ou relancer la visite complète dans Réglages → Avancé."
}
- }
+ },
+ "moduleTrigger": "Afficher cette visite"
},
"shell": {
"back": "Retour",
@@ -4469,9 +4510,7 @@
"changelog": "Changelog",
"docs": "Documentation",
"linksHeading": "Sources et documentation",
- "newerAvailable": "Nouvelle version : {tag}",
- "tourReplay": "Revoir la visite",
- "tourReplayHint": "Voir la visite guidée du tableau de bord — pratique si tu l'as sautée à la première visite."
+ "newerAvailable": "Nouvelle version : {tag}"
},
"testConnection": {
"test": "Tester la connexion",
diff --git a/messages/it.json b/messages/it.json
index 8873b3ec7..2a850296e 100644
--- a/messages/it.json
+++ b/messages/it.json
@@ -3533,31 +3533,72 @@
"back": "Indietro",
"skip": "Salta tour",
"done": "Ho capito",
- "restart": "Riavvia tour",
- "restartHint": "Mostra di nuovo il tour della dashboard alla prossima apertura.",
- "restartConfirmation": "Il tour ripartirà alla tua prossima visita alla dashboard.",
+ "restart": "Riavvia la visita ai moduli",
+ "restartHint": "Riproduce la visita guidata ai moduli dall’inizio al tuo prossimo accesso alla dashboard.",
+ "restartConfirmation": "La visita ai moduli ripartirà dall’inizio al tuo prossimo accesso alla dashboard.",
"steps": {
- "tileStrip": {
- "title": "I tuoi valori di oggi",
- "body": "La striscia di tile mostra l’ultimo valore e il trend a 7 giorni di ogni metrica — peso, pressione, pulsazioni, umore. Nascondi ciò che non ti serve in Impostazioni → Dashboard."
+ "dashboardOverview": {
+ "title": "La tua dashboard",
+ "body": "La dashboard riunisce tutti i moduli a colpo d’occhio. Tieni premuta una scheda per riordinarla o nasconderla (Impostazioni → Layout dashboard). Una scheda vale quanto i dati che ha dietro: i moduli successivi li riempiono."
},
"quickAdd": {
- "title": "Registrato in due tap",
- "body": "Con il pulsante Aggiungi registri misurazioni o voci di umore senza lasciare la dashboard. Il modulo ricorda il contesto e i tag più recenti."
+ "title": "Registrato in pochi secondi",
+ "body": "Con « + Aggiungi » registri qualsiasi misurazione in pochi secondi — peso, pressione, polso, glicemia. Forza nascosta: retrodatare i valori dimenticati. Tutto ciò che inserisci qui alimenta tendenze, punteggio salute e analisi IA."
+ },
+ "measurements": {
+ "title": "Misurazioni e intervalli obiettivo",
+ "body": "Misurazioni mostra lo storico e l’intervallo obiettivo per ogni metrica. Chicca nascosta: definire un intervallo per valore così gli scostamenti risaltano a colori. Queste serie sono la base delle correlazioni del Coach e del referto medico."
+ },
+ "medications": {
+ "title": "Farmaci e assunzioni",
+ "body": "Farmaci gestisce orari, assunzioni, scorte e titolazione. Nascosto: piani ricorrenti (RRULE/scorrevoli) e avvisi di scorte basse. La tua aderenza alimenta il pilastro di compliance nella dashboard e il contesto dell’IA."
+ },
+ "labs": {
+ "title": "Valori di laboratorio",
+ "body": "Laboratorio raccoglie i biomarcatori con intervalli di riferimento — emocromo, colesterolo, HbA1c. Forza: importazione ed esportazione FHIR. I valori fuori intervallo riemergono come prove nelle analisi e nel referto medico."
+ },
+ "illness": {
+ "title": "Diario delle malattie",
+ "body": "Il diario delle malattie registra episodi acuti e diagnosi croniche con le loro riacutizzazioni. Nascosto: correlare un episodio alle tue misurazioni e la Modalità riposo che mette in pausa i promemoria. Così il Coach capisce perché i valori variano per un periodo."
+ },
+ "vorsorge": {
+ "title": "Prevenzione",
+ "body": "Prevenzione ti ricorda controlli e vaccinazioni con un proprio intervallo. Suggerimento: «Fatto» può creare direttamente una misurazione o un risultato di laboratorio. Le scadenze compaiono come promemoria e nel referto medico."
+ },
+ "cycle": {
+ "title": "Ciclo",
+ "body": "Il monitoraggio del ciclo registra fasi e sintomi. Nascosto: confrontare i sintomi con umore e sonno. Il ciclo dà contesto alle variazioni che altrimenti sembrerebbero inspiegabili."
+ },
+ "mood": {
+ "title": "Umore",
+ "body": "Umore cattura come ti senti e dei tag in due secondi. Forza: i tuoi tag personalizzati. Gli andamenti dell’umore si correlano con sonno, farmaci e ciclo — il Coach coglie gli schemi."
},
"insights": {
- "title": "Insights automatici",
- "body": "Insights trasforma i tuoi valori in un riepilogo chiaro della salute, con inquadramento di pressione, peso e umore. Chiave personale o predefinita — scegli tu."
+ "title": "Analisi",
+ "body": "Analisi è il cuore: analisi dinamica e basata su prove invece di consigli generici — ogni affermazione punta ai tuoi dati reali. Nascosto: il layout è personalizzabile. Analisi raccoglie tutto ciò che forniscono i moduli sopra."
+ },
+ "coach": {
+ "title": "Coach",
+ "body": "Il Coach risponde alle domande sui tuoi dati in dialogo, usando anamnesi, farmaci e valori di laboratorio come contesto. Scegli tu il fornitore IA (chiave tua o locale). Non ricalcola nulla: legge gli stessi numeri della dashboard."
},
"integrations": {
- "title": "Collegare dispositivi",
- "body": "Dalle impostazioni colleghi Withings (sincronizzazione automatica di peso, pressione e pulsazioni), moodLog o il tuo provider di analisi. Tutto resta opzionale e cifrato."
+ "title": "Integrazioni",
+ "body": "Le integrazioni sincronizzano Withings, WHOOP, Polar, Oura e Apple Salute automaticamente. Peso, sonno e recupero si riempiono senza digitare e a loro volta alimentano analisi e Coach."
+ },
+ "export": {
+ "title": "Referto medico",
+ "body": "Il referto medico raccoglie misurazioni, farmaci, valori di laboratorio e prevenzione in PDF o FHIR per la visita. Qui convergono tutti i moduli: un referto completo nasce solo da dati ben curati."
},
"achievements": {
- "title": "Accumula serie e badge",
- "body": "La pagina Traguardi tiene traccia delle serie di registrazioni giornaliere, dei traguardi di pressione e dei badge di costanza. Un piccolo incentivo per non mollare."
+ "title": "Traguardi",
+ "body": "I traguardi premiano la costanza — serie e tappe. Un piccolo ciclo di motivazione che rende più facile non mollare."
+ },
+ "wrapUp": {
+ "title": "Questa è la visita guidata",
+ "body": "Puoi rivedere ogni modulo singolarmente in qualsiasi momento («Mostra questa visita» su ogni pagina) o riavviare l’intera visita da Impostazioni → Avanzate."
}
- }
+ },
+ "moduleTrigger": "Mostra questa visita"
},
"shell": {
"back": "Indietro",
@@ -4469,9 +4510,7 @@
"changelog": "Changelog",
"docs": "Documentazione",
"linksHeading": "Fonti e documentazione",
- "newerAvailable": "Nuova versione: {tag}",
- "tourReplay": "Rivedi il tour",
- "tourReplayHint": "Guarda il tour della dashboard — utile se l'hai saltato alla prima visita."
+ "newerAvailable": "Nuova versione: {tag}"
},
"testConnection": {
"test": "Prova connessione",
diff --git a/messages/pl.json b/messages/pl.json
index 0f4eb78f3..345078605 100644
--- a/messages/pl.json
+++ b/messages/pl.json
@@ -3533,31 +3533,72 @@
"back": "Wstecz",
"skip": "Pomiń tour",
"done": "Rozumiem",
- "restart": "Uruchom tour ponownie",
- "restartHint": "Pokaż tour panelu przy następnym wejściu.",
- "restartConfirmation": "Tour ruszy ponownie przy twojej następnej wizycie w panelu.",
+ "restart": "Uruchom przewodnik modułów ponownie",
+ "restartHint": "Odtwarza przewodnik po modułach od początku przy następnej wizycie na pulpicie.",
+ "restartConfirmation": "Przewodnik po modułach uruchomi się od początku przy następnej wizycie na pulpicie.",
"steps": {
- "tileStrip": {
- "title": "Twoje wartości dzienne",
- "body": "Pasek kafelków pokazuje ostatnią wartość i 7-dniowy trend dla każdej metryki — waga, ciśnienie, tętno, nastrój. Niepotrzebne wyłączysz w Ustawienia → Panel."
+ "dashboardOverview": {
+ "title": "Twój pulpit",
+ "body": "Pulpit zbiera wszystkie moduły na jednym ekranie. Przytrzymaj kafelek, aby go przestawić lub ukryć (Ustawienia → Układ pulpitu). Kafelek jest wart tyle, ile dane za nim — kolejne moduły je wypełniają."
},
"quickAdd": {
- "title": "Zarejestrowane w dwóch stuknięciach",
- "body": "Przyciskiem Dodaj zapisujesz pomiary lub nastroje bez opuszczania panelu. Formularz zapamiętuje ostatni kontekst i tagi."
+ "title": "Zapisane w kilka sekund",
+ "body": "Przez „+ Dodaj” zapiszesz każdy pomiar w sekundy — wagę, ciśnienie, tętno, glukozę. Ukryta moc: datowanie wstecz zapomnianych wartości. Wszystko, co tu wpiszesz, zasila trendy, wynik zdrowia i analizy AI."
+ },
+ "measurements": {
+ "title": "Pomiary i zakresy docelowe",
+ "body": "Pomiary pokazują historię i zakres docelowy każdej metryki. Ukryta perełka: ustaw zakres na wartość, aby odchylenia rzucały się w oczy kolorem. Te serie są podstawą korelacji Coacha i raportu dla lekarza."
+ },
+ "medications": {
+ "title": "Leki i przyjęcia",
+ "body": "Leki zarządzają harmonogramami, przyjęciami, zapasem i titracją. Ukryte: plany cykliczne (RRULE/przesuwne) i ostrzeżenia o niskim zapasie. Twój wskaźnik przyjęć zasila filar stosowania na pulpicie oraz kontekst AI."
+ },
+ "labs": {
+ "title": "Wyniki laboratoryjne",
+ "body": "Laboratorium gromadzi biomarkery z zakresami referencyjnymi — morfologia, cholesterol, HbA1c. Atut: import i eksport FHIR. Wartości poza zakresem wracają jako dowody w analizach i w raporcie dla lekarza."
+ },
+ "illness": {
+ "title": "Dziennik chorób",
+ "body": "Dziennik chorób zapisuje ostre epizody i przewlekłe diagnozy wraz z zaostrzeniami. Ukryte: korelacja epizodu z pomiarami oraz Tryb odpoczynku wstrzymujący przypomnienia. Dzięki temu Coach rozumie, czemu wartości przez chwilę odbiegają."
+ },
+ "vorsorge": {
+ "title": "Profilaktyka",
+ "body": "Profilaktyka przypomina o badaniach i szczepieniach z własnym interwałem. Wskazówka: „Wykonane” może od razu utworzyć pomiar lub wynik laboratoryjny. Terminy pojawiają się jako przypomnienia i w raporcie dla lekarza."
+ },
+ "cycle": {
+ "title": "Cykl",
+ "body": "Śledzenie cyklu zapisuje fazy i objawy. Ukryte: zestawienie objawów z nastrojem i snem. Cykl daje kontekst wahaniom, które inaczej wyglądałyby na niewyjaśnione."
+ },
+ "mood": {
+ "title": "Nastrój",
+ "body": "Nastrój zapisuje samopoczucie i tagi w dwie sekundy. Atut: własne tagi. Trendy nastroju korelują ze snem, lekami i cyklem — Coach dostrzega wzorce."
},
"insights": {
- "title": "Automatyczne insights",
- "body": "Insights zamienia twoje wartości w przejrzysty przegląd zdrowia, z oceną ciśnienia, wagi i nastroju. Własny klucz lub domyślny — ty decydujesz."
+ "title": "Analizy",
+ "body": "Analizy to serce aplikacji: dynamiczna analiza oparta na dowodach zamiast ogólnych porad — każde stwierdzenie wskazuje na Twoje realne dane. Ukryte: układ można dostosować. Analizy łączą wszystko, co dostarczają moduły powyżej."
+ },
+ "coach": {
+ "title": "Coach",
+ "body": "Coach odpowiada na pytania o Twoje dane w dialogu, korzystając z wywiadu, leków i wyników jako kontekstu. Dostawcę AI wybierasz sam (własny klucz lub lokalnie). Niczego nie przelicza — czyta te same liczby co pulpit."
},
"integrations": {
- "title": "Połącz urządzenia",
- "body": "W ustawieniach łączysz Withings (auto-sync wagi, ciśnienia i tętna), moodLog lub dostawcę analizy. Wszystko zostaje opcjonalne i zaszyfrowane."
+ "title": "Integracje",
+ "body": "Integracje synchronizują Withings, WHOOP, Polar, Oura i Apple Health automatycznie. Waga, sen i regeneracja uzupełniają się bez wpisywania, a z kolei zasilają analizy i Coacha."
+ },
+ "export": {
+ "title": "Raport dla lekarza",
+ "body": "Raport dla lekarza łączy pomiary, leki, wyniki laboratoryjne i profilaktykę w PDF lub FHIR na wizytę. Tu zbiegają się wszystkie moduły — pełny raport powstaje tylko z zadbanych danych."
},
"achievements": {
- "title": "Zbieraj serie i odznaki",
- "body": "Strona Osiągnięć śledzi codzienne serie zapisów, kamienie milowe ciśnienia i odznaki konsekwencji. Drobna motywacja do trzymania się planu."
+ "title": "Osiągnięcia",
+ "body": "Osiągnięcia nagradzają konsekwencję — passy i kamienie milowe. Mała pętla motywacji, dzięki której łatwiej wytrwać."
+ },
+ "wrapUp": {
+ "title": "To koniec przewodnika",
+ "body": "Każdy moduł możesz obejrzeć osobno w dowolnej chwili („Pokaż ten przewodnik” na każdej stronie) lub uruchomić cały przewodnik ponownie w Ustawienia → Zaawansowane."
}
- }
+ },
+ "moduleTrigger": "Pokaż ten przewodnik"
},
"shell": {
"back": "Wstecz",
@@ -4469,9 +4510,7 @@
"changelog": "Changelog",
"docs": "Dokumentacja",
"linksHeading": "Źródła i dokumentacja",
- "newerAvailable": "Nowa wersja: {tag}",
- "tourReplay": "Obejrzyj samouczek",
- "tourReplayHint": "Zobacz przewodnik po panelu — przydatne, jeśli pominąłeś go przy pierwszej wizycie."
+ "newerAvailable": "Nowa wersja: {tag}"
},
"testConnection": {
"test": "Testuj połączenie",
diff --git a/prisma/migrations/0173_v1186_tour_progress/migration.sql b/prisma/migrations/0173_v1186_tour_progress/migration.sql
new file mode 100644
index 000000000..498a859f5
--- /dev/null
+++ b/prisma/migrations/0173_v1186_tour_progress/migration.sql
@@ -0,0 +1,22 @@
+-- v1.18.6 — resumable module-tour progress.
+--
+-- A single nullable JSONB column on `users` carrying the fine-grained
+-- resume point for the module-by-module guided tour. Follows the
+-- established per-user JSON-column convention (`dashboard_widgets_json`,
+-- `insights_layout_json`, `coach_prefs_json`).
+--
+-- Shape (validated by `src/lib/onboarding/tour-progress.ts`):
+-- { "lastStopId": string | null,
+-- "completedStopIds": string[],
+-- "status": "in_progress" | "completed" | "skipped",
+-- "updatedAt": string (ISO 8601) }
+--
+-- NULL = the user has not started the module tour. The coarse
+-- `onboarding_tour_completed` boolean stays the launcher's auto-launch
+-- gate; this column is purely the resume point and is cleared back to
+-- NULL on a Settings replay so the tour genuinely restarts.
+--
+-- Additive + nullable: every existing account keeps NULL and the
+-- application resolver treats NULL as "not started".
+
+ALTER TABLE "users" ADD COLUMN "onboarding_tour_progress_json" JSONB;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index a69270678..948d0db58 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -256,6 +256,22 @@ model User {
// Settings → Account.
onboardingTourCompleted Boolean @default(false) @map("onboarding_tour_completed")
+ // v1.18.6 — resumable module-tour progress. The coarse boolean above
+ // stays as the "did the user ever finish/dismiss" flag the launcher
+ // gates on; this column carries the fine-grained resume point so a
+ // mid-tour reload reopens at the right module. Follows the per-user
+ // JSON-column convention (`dashboardWidgetsJson` / `insightsLayoutJson`
+ // / `coachPrefsJson`). Shape (validated by
+ // `src/lib/onboarding/tour-progress.ts`):
+ // { lastStopId: string | null,
+ // completedStopIds: string[],
+ // status: "in_progress" | "completed" | "skipped",
+ // updatedAt: string (ISO) }
+ // Null = the user has not started the module tour (fresh account or a
+ // legacy account that only carries the boolean flag). Cleared back to
+ // null on a Settings replay so the tour genuinely restarts.
+ onboardingTourProgressJson Json? @map("onboarding_tour_progress_json")
+
// HealthKit (iOS) integration config — list of per-metric direction toggles.
// Shape: { entries: [{ id, kind, direction, enabled }], lastSyncedAt }
healthKitConfigJson Json? @map("healthkit_config_json")
diff --git a/src/app/achievements/page.tsx b/src/app/achievements/page.tsx
index 513927772..f0a9448c3 100644
--- a/src/app/achievements/page.tsx
+++ b/src/app/achievements/page.tsx
@@ -34,6 +34,7 @@ import { Badge } from "@/components/ui/badge";
import { EmptyState } from "@/components/ui/empty-state";
import { useAuth } from "@/hooks/use-auth";
import { useTranslations } from "@/lib/i18n/context";
+import { ModuleTourTrigger } from "@/components/onboarding/module-tour-trigger";
import { formatDate } from "@/lib/format";
import { useAchievementsQuery } from "@/lib/queries/use-achievements-query";
import {
@@ -317,13 +318,21 @@ export default function AchievementsPage() {
return (
-
-
- {t("achievements.title")}
-
-
- {t("achievements.subtitle")}
-
+
+
+
+ {t("achievements.title")}
+
+
+ {t("achievements.subtitle")}
+
+
+
+
+
diff --git a/src/app/admin/[section]/renderer.tsx b/src/app/admin/[section]/renderer.tsx
index 8bd9e34be..aeea25435 100644
--- a/src/app/admin/[section]/renderer.tsx
+++ b/src/app/admin/[section]/renderer.tsx
@@ -194,9 +194,9 @@ export function AdminSectionRenderer({
// v1.4.36 W4e — About section reused as-is from the settings
// surface. The component owns its own heading + cards layout
// so no SectionFrame wrapper.
- // v1.18.1 E5 — hide the onboarding "Replay tour" card on the admin
- // surface; it's a user-facing affordance with no admin-side use.
- return ;
+ // v1.18.6 — the About surface no longer carries the tour-replay card
+ // (consolidated onto Settings → Advanced), so there's nothing to hide.
+ return ;
default:
slug satisfies never;
return null;
diff --git a/src/app/api/auth/me/route.ts b/src/app/api/auth/me/route.ts
index f7fec1a52..d23f63b8e 100644
--- a/src/app/api/auth/me/route.ts
+++ b/src/app/api/auth/me/route.ts
@@ -10,6 +10,7 @@ import {
resolveModuleMap,
getOperatorModuleAvailability,
} from "@/lib/modules/gate";
+import { parseTourProgress } from "@/lib/onboarding/tour-progress";
export const dynamic = "force-dynamic";
@@ -70,6 +71,11 @@ export const GET = apiHandler(async () => {
timezone: user.timezone,
onboardingCompletedAt: user.onboardingCompletedAt,
onboardingTourCompleted: user.onboardingTourCompleted,
+ // v1.18.6 — resumable module-tour progress. Null when the user has
+ // not started the tour; otherwise the resume point the launcher
+ // seeds its index from. Fail-soft parse: a corrupt blob degrades to
+ // null ("start from the top") rather than 500-ing the /me read.
+ onboardingTourProgress: parseTourProgress(user.onboardingTourProgressJson),
// v1.5.5 — self-hosted avatar. Replaces the Gravatar leak; the
// URL is relative so PWA + native clients render identically
// and the `?v={updatedAtMs}` suffix busts the browser cache on
diff --git a/src/app/api/onboarding/tour/route.ts b/src/app/api/onboarding/tour/route.ts
index 9e9b72a4a..8d88d408a 100644
--- a/src/app/api/onboarding/tour/route.ts
+++ b/src/app/api/onboarding/tour/route.ts
@@ -5,30 +5,45 @@ import { prisma } from "@/lib/db";
import { apiHandler, requireAuth } from "@/lib/api-handler";
import { annotate } from "@/lib/logging/context";
import { apiSuccess, apiError, safeJson } from "@/lib/api-response";
+import { tourProgressSchema } from "@/lib/onboarding/tour-progress";
+import { Prisma } from "@/generated/prisma/client";
/**
* v1.4.15 Phase B5 — onboarding tour completion endpoint.
+ * v1.18.6 — extended with the resumable module-tour progress point.
*
- * Two operations on a single resource:
+ * Operations on a single resource:
*
* POST { completed: true, outcome: "completed" | "skipped" }
* → flips `users.onboarding_tour_completed` to true. Auto-fires
- * when the user reaches the end of the spotlight tour OR
- * explicitly dismisses it.
+ * when the user reaches the end of the module tour OR explicitly
+ * dismisses it.
*
* POST { completed: false }
* → resets the flag so the user can replay the tour from
- * Settings → Account → "Restart onboarding tour".
+ * Settings → Advanced → "Modul-Tour neu starten". Also CLEARS
+ * `onboarding_tour_progress_json` back to null so the replay
+ * genuinely starts from the first module.
*
- * The `outcome` field is informational — it lands in the structured
- * Wide Event annotation so analytics can tell completion from
- * dismiss without writing two columns. Same DB write either way.
+ * POST { progress: { lastStopId, completedStopIds, status, updatedAt } }
+ * → fire-and-forget mid-tour checkpoint so a reload resumes at the
+ * right module. May arrive WITH `completed` (the terminal
+ * checkpoint sends both) or alone (every `nextStep`).
+ *
+ * `outcome` is informational — it lands in the structured Wide Event
+ * annotation so analytics can tell completion from dismiss without a
+ * second column. Same DB write either way.
*/
-const tourBodySchema = z.object({
- completed: z.boolean(),
- outcome: z.enum(["completed", "skipped"]).optional(),
-});
+const tourBodySchema = z
+ .object({
+ completed: z.boolean().optional(),
+ outcome: z.enum(["completed", "skipped"]).optional(),
+ progress: tourProgressSchema.optional(),
+ })
+ .refine((b) => b.completed !== undefined || b.progress !== undefined, {
+ message: "Provide `completed` and/or `progress`.",
+ });
export const POST = apiHandler(async (request: NextRequest) => {
const { user } = await requireAuth();
@@ -43,18 +58,36 @@ export const POST = apiHandler(async (request: NextRequest) => {
return apiError("Invalid input", 422);
}
- await prisma.user.update({
- where: { id: user.id },
- data: { onboardingTourCompleted: parsed.data.completed },
- });
+ // Build the update field-by-field (no mass assignment). `completed`
+ // drives the coarse boolean; a replay (`completed:false`) wipes the
+ // resume point; a progress checkpoint writes the JSON column.
+ const data: Prisma.UserUpdateInput = {};
+ if (parsed.data.completed !== undefined) {
+ data.onboardingTourCompleted = parsed.data.completed;
+ if (parsed.data.completed === false) {
+ // Replay — start the resume point over.
+ data.onboardingTourProgressJson = Prisma.JsonNull;
+ }
+ }
+ if (parsed.data.progress !== undefined) {
+ data.onboardingTourProgressJson = parsed.data.progress;
+ }
+
+ await prisma.user.update({ where: { id: user.id }, data });
annotate({
action: { name: "onboarding.tour.update" },
meta: {
- completed: parsed.data.completed,
+ completed: parsed.data.completed ?? null,
outcome: parsed.data.outcome ?? null,
+ progressStatus: parsed.data.progress?.status ?? null,
+ lastStopId: parsed.data.progress?.lastStopId ?? null,
},
});
- return apiSuccess({ onboardingTourCompleted: parsed.data.completed });
+ return apiSuccess({
+ onboardingTourCompleted:
+ parsed.data.completed ?? user.onboardingTourCompleted,
+ progress: parsed.data.progress ?? null,
+ });
});
diff --git a/src/app/coach/page.tsx b/src/app/coach/page.tsx
index 23363d12e..d8c0d5402 100644
--- a/src/app/coach/page.tsx
+++ b/src/app/coach/page.tsx
@@ -6,6 +6,7 @@ import { Minimize2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { CoachConversation } from "@/components/insights/coach-panel/coach-conversation";
+import { ModuleTourTrigger } from "@/components/onboarding/module-tour-trigger";
import { useTranslations } from "@/lib/i18n/context";
import { useCoachLaunch } from "@/lib/insights/coach-launch-context";
import { useFeatureFlags } from "@/hooks/use-feature-flags";
@@ -61,6 +62,7 @@ export default function CoachPage() {
return (