diff --git a/CHANGELOG.md b/CHANGELOG.md
index b926b4820..eaf17ff3f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,29 @@
## [Unreleased]
+### Fixed
+
+- The mood sliders no longer read their value fused with the reset control, so a
+ slider set to 4 shows the number on its own instead of "4 leeren". The reset
+ is a small icon beside the value now.
+- Date and time fields wear the same focus highlight as every other input. The
+ ring hugs the field instead of painting wider than it or jumping to the left
+ when you click in, and the fields no longer set off the zoom on iOS.
+- The add-mood sheet keeps its scrollbar clear of the endpoint labels, so the
+ longest wording is never clipped by the scroll track.
+- The health-score configuration lists its topics as one flat set with dividers
+ instead of grouping them under headings.
+- In Settings, under Security, password reset now sits last, after the
+ authenticator app, security keys and passkeys.
+- Sharing a record again with someone whose access had expired works. The old
+ lapsed grant no longer blocks a fresh invitation with a false report that the
+ person still has access.
+- The scope options when inviting someone start with nothing preselected, so the
+ choice between the whole record and single sections is one you make rather than
+ one already made for you.
+- The write and manage descriptions on the invite form are one line each now,
+ with the full detail behind the info icon instead of a wall of text.
+
## [1.37.2] — 2026-08-08
### Added
diff --git a/e2e/account-sharing.spec.ts b/e2e/account-sharing.spec.ts
index 618ff1ee5..2ed1ab6ce 100644
--- a/e2e/account-sharing.spec.ts
+++ b/e2e/account-sharing.spec.ts
@@ -191,6 +191,16 @@ test.describe("account sharing", () => {
'[data-slot="grant-invite-identifier"]',
);
const submit = ownerPage.locator('[data-slot="grant-invite-submit"]');
+
+ // Nothing under the scope question is preselected now, so the form blocks
+ // until the owner picks one. This invitation is unscoped, so choose the
+ // whole record before asserting the button can enable.
+ const wholeRecord = ownerPage.locator(
+ '[data-slot="grant-invite-scope-option"][data-scope="all"]',
+ );
+ await wholeRecord.click();
+ await expect(wholeRecord).toHaveAttribute("data-selected", "true");
+
await expect(async () => {
await identifier.fill(E2E_USER.username);
await expect(submit).toBeEnabled({ timeout: 1000 });
diff --git a/e2e/delegated-writes.spec.ts b/e2e/delegated-writes.spec.ts
index 2957dc2d7..d55d40d49 100644
--- a/e2e/delegated-writes.spec.ts
+++ b/e2e/delegated-writes.spec.ts
@@ -106,6 +106,15 @@ test.describe("delegated writes", () => {
'[data-slot="grant-invite-identifier"]',
);
const submit = ownerPage.locator('[data-slot="grant-invite-submit"]');
+
+ // The scope question preselects nothing, so the form blocks until the owner
+ // picks one. This invitation is unscoped — choose the whole record.
+ const wholeRecord = ownerPage.locator(
+ '[data-slot="grant-invite-scope-option"][data-scope="all"]',
+ );
+ await wholeRecord.click();
+ await expect(wholeRecord).toHaveAttribute("data-selected", "true");
+
// The controlled input keeps the submit disabled until React has attached,
// so retry the pair rather than waiting a fixed time and hoping.
await expect(async () => {
diff --git a/e2e/settings-score.spec.ts b/e2e/settings-score.spec.ts
index 399bd9f2e..f8e57e90a 100644
--- a/e2e/settings-score.spec.ts
+++ b/e2e/settings-score.spec.ts
@@ -130,14 +130,13 @@ test.describe("Settings → Health Score", () => {
1,
);
- // The cardiometabolic triple, visibly under one heading.
+ // The cardiometabolic triple. The list is flat (no group headings) — the
+ // rows carry their domain, so the three cardiometabolic pillars are found
+ // on the row itself.
const cardio = page.locator(
- '[data-slot="score-config-group"][data-domain="cardiometabolic"]',
- );
- await expect(cardio).toHaveCount(1);
- await expect(cardio.locator('[data-slot="score-pillar-row"]')).toHaveCount(
- 3,
+ '[data-slot="score-pillar-row"][data-domain="cardiometabolic"]',
);
+ await expect(cardio).toHaveCount(3);
// The rows say nothing about recording or showing — the card says it
// once, above them, and the modules screen owns those two questions.
diff --git a/messages/de.json b/messages/de.json
index 22e52f6b7..a925e5193 100644
--- a/messages/de.json
+++ b/messages/de.json
@@ -5207,13 +5207,6 @@
"axis": {
"countsFor": "{pillar} in den Score einrechnen"
},
- "domain": {
- "cardiometabolic": "Herz und Stoffwechsel",
- "activity": "Aktivität",
- "sleep": "Schlaf",
- "adiposity": "Körperform",
- "wellbeing": "Wohlbefinden"
- },
"state": {
"counting": "Zählt in deinen Score",
"waiting": "Ausgewählt, wartet auf Daten",
@@ -9764,9 +9757,11 @@
"accessReadLabel": "Darf lesen",
"accessReadCapability": "Sie kann alles lesen, was von dieser Akte geteilt wird. Hinzufügen oder ändern kann sie nichts.",
"accessWriteLabel": "Darf lesen und hinzufügen",
- "accessWriteCapability": "Sie kann außerdem etwas zur Akte hinzufügen: Messwerte, Laborergebnisse, einen neuen Laborwert, einen Krankheitseintrag, eine Nebenwirkung zu einem Medikament, ein neues Medikament und eine Dosis als genommen oder ausgelassen markieren. Ändern oder löschen kann sie nichts, auch nicht das, was sie selbst eingetragen hat, und eine deiner Erinnerungen verschieben kann sie auch nicht. Das kannst nur du.",
+ "accessWriteCapability": "Sie kann Einträge hinzufügen, aber nichts ändern oder löschen, auch nicht das, was sie selbst eingetragen hat.",
+ "accessWriteDetail": "Sie kann Messwerte, Laborergebnisse, einen neuen Laborwert, einen Krankheitseintrag, eine Nebenwirkung zu einem Medikament und ein neues Medikament hinzufügen sowie eine Dosis als genommen oder ausgelassen markieren. Ändern oder löschen kann sie nichts, auch nicht das Eigene, und eine deiner Erinnerungen verschieben kann sie nicht. Das kannst nur du.",
"accessManageLabel": "Darf verwalten",
- "accessManageCapability": "Sie kann Einträge in deiner Akte anlegen, ändern und löschen, auch solche, die du selbst erfasst hast, und sie kann die daraus erzeugten Auswertungen lesen. Deine Anmeldung, deine Sicherheitseinstellungen, deine Verbindungen und den Zugriff anderer Personen kann sie nicht ändern. Du kannst das jederzeit beenden.",
+ "accessManageCapability": "Sie kann Einträge anlegen, ändern und löschen, auch solche, die du selbst erfasst hast.",
+ "accessManageDetail": "Sie kann außerdem die daraus erzeugten Auswertungen lesen. Deine Anmeldung, deine Sicherheitseinstellungen, deine Verbindungen und den Zugriff anderer Personen kann sie nicht ändern. Du kannst das jederzeit beenden.",
"manageStepUpNote": "Dafür wird dein zweiter Faktor abgefragt, deshalb geht das nur im Browser.",
"scopeLegend": "Was die Person öffnen darf",
"scopeAllLabel": "Gesamte Akte",
@@ -9778,6 +9773,7 @@
"scopeAggregateNote": "Die Übersicht auf dem Dashboard, der Health Score und die Tageszusammenfassung erscheinen nur, wenn die gesamte Akte geteilt ist.",
"scopeDocumentsNote": "Ein Dokument kann alles enthalten. Teile diesen Bereich nur, wenn die Person alles davon lesen darf.",
"scopeEmptyHint": "Wähle mindestens einen Bereich aus oder teile wieder die gesamte Akte.",
+ "scopeChooseHint": "Wähle, ob die gesamte Akte oder nur einzelne Bereiche geteilt werden.",
"expiresLabel": "Zugriff endet am (optional)",
"expiresHint": "Leer lassen für Zugriff, der gilt, bis ihn jemand beendet. Ein Datum wird bei jeder Anfrage geprüft, der Zugriff endet also am Ende dieses Tages von selbst.",
"submit": "Einladung senden",
diff --git a/messages/en.json b/messages/en.json
index 8998e4fad..12f18a262 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -5207,13 +5207,6 @@
"axis": {
"countsFor": "Count {pillar} toward the score"
},
- "domain": {
- "cardiometabolic": "Heart and metabolism",
- "activity": "Activity",
- "sleep": "Sleep",
- "adiposity": "Body shape",
- "wellbeing": "Wellbeing"
- },
"state": {
"counting": "Counting toward your score",
"waiting": "Selected, waiting for data",
@@ -9764,9 +9757,11 @@
"accessReadLabel": "Can view",
"accessReadCapability": "They can read everything this record shares. They cannot add or change anything.",
"accessWriteLabel": "Can view and add",
- "accessWriteCapability": "They can also add to the record: readings, lab results, a new analyte to track, an illness entry, a side effect against a drug, a new medication, and marking a dose taken or skipped. They cannot edit or delete anything, not even what they added, and they cannot defer one of your reminders. Only you can.",
+ "accessWriteCapability": "They can add to the record but cannot edit or delete anything, not even what they added.",
+ "accessWriteDetail": "They can add readings, lab results, a new analyte to track, an illness entry, a side effect against a drug, a new medication, and mark a dose taken or skipped. They cannot edit or delete anything, not even what they added, and they cannot defer one of your reminders. Only you can.",
"accessManageLabel": "Can manage",
- "accessManageCapability": "They can add, change and remove entries in your record, including ones you entered yourself, and they can read the insights generated from it. They cannot change your login, your security settings, your connections, or who else has access. You can end this at any time.",
+ "accessManageCapability": "They can add, change and remove entries, including ones you entered yourself.",
+ "accessManageDetail": "They can also read the insights generated from your record. They cannot change your login, your security settings, your connections, or who else has access. You can end this at any time.",
"manageStepUpNote": "Offering this asks for your second factor, so it can only be done in a browser.",
"scopeLegend": "What they can open",
"scopeAllLabel": "Entire record",
@@ -9778,6 +9773,7 @@
"scopeAggregateNote": "The dashboard overview, health score and daily digest only appear when the entire record is shared.",
"scopeDocumentsNote": "A document can contain anything. Share this only if they may read all of it.",
"scopeEmptyHint": "Pick at least one section, or go back to sharing the entire record.",
+ "scopeChooseHint": "Choose whether to share the entire record or only selected sections.",
"expiresLabel": "Access ends on (optional)",
"expiresHint": "Leave this empty for access that lasts until somebody ends it. A date here is checked on every request, so access stops on its own at the end of that day.",
"submit": "Send invitation",
diff --git a/messages/es.json b/messages/es.json
index f8b57316b..3ec016e1c 100644
--- a/messages/es.json
+++ b/messages/es.json
@@ -5207,13 +5207,6 @@
"axis": {
"countsFor": "Contar {pillar} para la puntuación"
},
- "domain": {
- "cardiometabolic": "Corazón y metabolismo",
- "activity": "Actividad",
- "sleep": "Sueño",
- "adiposity": "Forma corporal",
- "wellbeing": "Bienestar"
- },
"state": {
"counting": "Cuenta para tu puntuación",
"waiting": "Seleccionado, esperando datos",
@@ -9764,9 +9757,11 @@
"accessReadLabel": "Puede ver",
"accessReadCapability": "Puede leer todo lo que este historial comparte. No puede añadir ni cambiar nada.",
"accessWriteLabel": "Puede ver y añadir",
- "accessWriteCapability": "También puede añadir a la ficha: mediciones, resultados de laboratorio, un nuevo analito a seguir, una entrada de enfermedad, un efecto secundario de un medicamento, un medicamento nuevo, y marcar una dosis como tomada u omitida. No puede editar ni borrar nada, ni siquiera lo que añadió, ni aplazar uno de tus recordatorios. Solo tú puedes.",
+ "accessWriteCapability": "Puede añadir a la ficha, pero no puede editar ni borrar nada, ni siquiera lo que añadió.",
+ "accessWriteDetail": "Puede añadir mediciones, resultados de laboratorio, un nuevo analito a seguir, una entrada de enfermedad, un efecto secundario de un medicamento y un medicamento nuevo, y marcar una dosis como tomada u omitida. No puede editar ni borrar nada, ni siquiera lo que añadió, ni aplazar uno de tus recordatorios. Solo tú puedes.",
"accessManageLabel": "Puede gestionar",
- "accessManageCapability": "Puede añadir, cambiar y eliminar entradas de tu ficha, incluidas las que has escrito tú, y puede leer los análisis generados a partir de ella. No puede cambiar tu inicio de sesión, tus ajustes de seguridad, tus servicios conectados ni quién más tiene acceso. Puedes terminarlo cuando quieras.",
+ "accessManageCapability": "Puede añadir, cambiar y eliminar entradas, incluidas las que has escrito tú.",
+ "accessManageDetail": "También puede leer los análisis generados a partir de tu ficha. No puede cambiar tu inicio de sesión, tus ajustes de seguridad, tus servicios conectados ni quién más tiene acceso. Puedes terminarlo cuando quieras.",
"manageStepUpNote": "Ofrecerlo pide tu segundo factor, así que solo se puede hacer en un navegador.",
"scopeLegend": "Qué puede abrir",
"scopeAllLabel": "Todo el historial",
@@ -9778,6 +9773,7 @@
"scopeAggregateNote": "El resumen del panel, la puntuación de salud y el resumen diario solo aparecen cuando se comparte todo el historial.",
"scopeDocumentsNote": "Un documento puede contener cualquier cosa. Comparte esta sección solo si esa persona puede leerlo todo.",
"scopeEmptyHint": "Elige al menos una sección o vuelve a compartir todo el historial.",
+ "scopeChooseHint": "Elige si compartir todo el historial o solo algunas secciones.",
"expiresLabel": "El acceso termina el (opcional)",
"expiresHint": "Déjalo vacío para un acceso que dure hasta que alguien lo termine. Una fecha aquí se comprueba en cada petición, así que el acceso se detiene solo al final de ese día.",
"submit": "Enviar invitación",
diff --git a/messages/fr.json b/messages/fr.json
index 7daddbd8f..0a07a45b3 100644
--- a/messages/fr.json
+++ b/messages/fr.json
@@ -5207,13 +5207,6 @@
"axis": {
"countsFor": "Compter {pillar} dans le score"
},
- "domain": {
- "cardiometabolic": "Cœur et métabolisme",
- "activity": "Activité",
- "sleep": "Sommeil",
- "adiposity": "Silhouette",
- "wellbeing": "Bien-être"
- },
"state": {
"counting": "Compte dans votre score",
"waiting": "Sélectionné, en attente de données",
@@ -9764,9 +9757,11 @@
"accessReadLabel": "Peut consulter",
"accessReadCapability": "Elle peut lire tout ce que ce dossier partage. Elle ne peut rien ajouter ni rien modifier.",
"accessWriteLabel": "Peut consulter et ajouter",
- "accessWriteCapability": "Elle peut aussi ajouter au dossier : des mesures, des résultats de laboratoire, un nouvel analyte à suivre, une entrée de maladie, un effet indésirable lié à un médicament, un nouveau médicament, et marquer une dose comme prise ou sautée. Elle ne peut rien modifier ni supprimer, pas même ce qu'elle a ajouté, et elle ne peut pas reporter un de vos rappels. Vous seul le pouvez.",
+ "accessWriteCapability": "Elle peut ajouter au dossier, mais ne peut rien modifier ni supprimer, pas même ce qu'elle a ajouté.",
+ "accessWriteDetail": "Elle peut ajouter des mesures, des résultats de laboratoire, un nouvel analyte à suivre, une entrée de maladie, un effet indésirable lié à un médicament et un nouveau médicament, et marquer une dose comme prise ou sautée. Elle ne peut rien modifier ni supprimer, pas même ce qu'elle a ajouté, et elle ne peut pas reporter un de vos rappels. Vous seul le pouvez.",
"accessManageLabel": "Peut gérer",
- "accessManageCapability": "Elle peut ajouter, modifier et supprimer des entrées de votre dossier, y compris celles que vous avez saisies vous-même, et lire les analyses qui en sont tirées. Elle ne peut modifier ni votre connexion, ni vos paramètres de sécurité, ni vos services connectés, ni la liste des personnes qui ont accès. Vous pouvez y mettre fin à tout moment.",
+ "accessManageCapability": "Elle peut ajouter, modifier et supprimer des entrées, y compris celles que vous avez saisies vous-même.",
+ "accessManageDetail": "Elle peut aussi lire les analyses tirées de votre dossier. Elle ne peut modifier ni votre connexion, ni vos paramètres de sécurité, ni vos services connectés, ni la liste des personnes qui ont accès. Vous pouvez y mettre fin à tout moment.",
"manageStepUpNote": "Cette offre demande votre deuxième facteur, elle n'est donc possible que dans un navigateur.",
"scopeLegend": "Ce que cette personne peut ouvrir",
"scopeAllLabel": "Dossier complet",
@@ -9778,6 +9773,7 @@
"scopeAggregateNote": "La vue d'ensemble du tableau de bord, le score de santé et le résumé quotidien n'apparaissent que si le dossier entier est partagé.",
"scopeDocumentsNote": "Un document peut contenir n'importe quoi. Ne partagez cette rubrique que si la personne peut tout lire.",
"scopeEmptyHint": "Choisissez au moins une rubrique, ou revenez au partage du dossier complet.",
+ "scopeChooseHint": "Choisissez de partager le dossier complet ou seulement certaines rubriques.",
"expiresLabel": "L'accès prend fin le (facultatif)",
"expiresHint": "Laissez ce champ vide pour un accès qui dure jusqu'à ce que quelqu'un y mette fin. Une date ici est vérifiée à chaque requête, l'accès s'arrête donc de lui-même à la fin de cette journée.",
"submit": "Envoyer l'invitation",
diff --git a/messages/it.json b/messages/it.json
index 471e33147..a820bcf1e 100644
--- a/messages/it.json
+++ b/messages/it.json
@@ -5207,13 +5207,6 @@
"axis": {
"countsFor": "Conta {pillar} nel punteggio"
},
- "domain": {
- "cardiometabolic": "Cuore e metabolismo",
- "activity": "Attività",
- "sleep": "Sonno",
- "adiposity": "Forma corporea",
- "wellbeing": "Benessere"
- },
"state": {
"counting": "Conta nel tuo punteggio",
"waiting": "Selezionato, in attesa di dati",
@@ -9764,9 +9757,11 @@
"accessReadLabel": "Può vedere",
"accessReadCapability": "Può leggere tutto ciò che questa cartella condivide. Non può aggiungere né modificare nulla.",
"accessWriteLabel": "Può vedere e aggiungere",
- "accessWriteCapability": "Può anche aggiungere alla cartella: misurazioni, risultati di laboratorio, un nuovo analita da seguire, una voce di malattia, un effetto collaterale di un farmaco, un nuovo farmaco e segnare una dose come presa o saltata. Non può modificare né eliminare nulla, nemmeno ciò che ha aggiunto, e non può rimandare uno dei tuoi promemoria. Solo tu puoi.",
+ "accessWriteCapability": "Può aggiungere alla cartella, ma non può modificare né eliminare nulla, nemmeno ciò che ha aggiunto.",
+ "accessWriteDetail": "Può aggiungere misurazioni, risultati di laboratorio, un nuovo analita da seguire, una voce di malattia, un effetto collaterale di un farmaco e un nuovo farmaco, e segnare una dose come presa o saltata. Non può modificare né eliminare nulla, nemmeno ciò che ha aggiunto, e non può rimandare uno dei tuoi promemoria. Solo tu puoi.",
"accessManageLabel": "Può gestire",
- "accessManageCapability": "Può aggiungere, modificare ed eliminare voci della tua cartella, comprese quelle inserite da te, e può leggere le analisi generate a partire da essa. Non può cambiare il tuo accesso, le tue impostazioni di sicurezza, i tuoi servizi collegati né chi altro ha accesso. Puoi terminare tutto questo in qualsiasi momento.",
+ "accessManageCapability": "Può aggiungere, modificare ed eliminare voci, comprese quelle inserite da te.",
+ "accessManageDetail": "Può anche leggere le analisi generate dalla tua cartella. Non può cambiare il tuo accesso, le tue impostazioni di sicurezza, i tuoi servizi collegati né chi altro ha accesso. Puoi terminare tutto questo in qualsiasi momento.",
"manageStepUpNote": "Per offrirlo viene richiesto il tuo secondo fattore, quindi si può fare solo da browser.",
"scopeLegend": "Che cosa può aprire",
"scopeAllLabel": "Intera cartella",
@@ -9778,6 +9773,7 @@
"scopeAggregateNote": "La panoramica della dashboard, il punteggio di salute e il riepilogo giornaliero compaiono solo se viene condivisa l'intera cartella.",
"scopeDocumentsNote": "Un documento può contenere qualsiasi cosa. Condividi questa sezione solo se quella persona può leggere tutto.",
"scopeEmptyHint": "Scegli almeno una sezione oppure torna a condividere l'intera cartella.",
+ "scopeChooseHint": "Scegli se condividere l'intera cartella o solo alcune sezioni.",
"expiresLabel": "L'accesso termina il (facoltativo)",
"expiresHint": "Lascia vuoto per un accesso che dura finché qualcuno non lo termina. Una data qui viene controllata a ogni richiesta, quindi l'accesso si interrompe da solo alla fine di quel giorno.",
"submit": "Invia invito",
diff --git a/messages/pl.json b/messages/pl.json
index b4245f672..724882a79 100644
--- a/messages/pl.json
+++ b/messages/pl.json
@@ -5207,13 +5207,6 @@
"axis": {
"countsFor": "Licz {pillar} do wyniku"
},
- "domain": {
- "cardiometabolic": "Serce i metabolizm",
- "activity": "Aktywność",
- "sleep": "Sen",
- "adiposity": "Sylwetka",
- "wellbeing": "Samopoczucie"
- },
"state": {
"counting": "Liczy się do Twojego wyniku",
"waiting": "Wybrany, czeka na dane",
@@ -9764,9 +9757,11 @@
"accessReadLabel": "Może przeglądać",
"accessReadCapability": "Może czytać wszystko, co ta karta udostępnia. Nie może niczego dodać ani zmienić.",
"accessWriteLabel": "Może przeglądać i dodawać",
- "accessWriteCapability": "Może też dodawać do kartoteki: pomiary, wyniki badań, nowy analit do śledzenia, wpis o chorobie, działanie niepożądane leku, nowy lek oraz oznaczyć dawkę jako przyjętą lub pominiętą. Nie może niczego edytować ani usuwać, nawet tego, co sama dodała, ani odłożyć żadnego z Twoich przypomnień. Tylko Ty możesz.",
+ "accessWriteCapability": "Może dodawać do kartoteki, ale nie może niczego edytować ani usuwać, nawet tego, co sama dodała.",
+ "accessWriteDetail": "Może dodawać pomiary, wyniki badań, nowy analit do śledzenia, wpis o chorobie, działanie niepożądane leku i nowy lek oraz oznaczyć dawkę jako przyjętą lub pominiętą. Nie może niczego edytować ani usuwać, nawet tego, co sama dodała, ani odłożyć żadnego z Twoich przypomnień. Tylko Ty możesz.",
"accessManageLabel": "Może zarządzać",
- "accessManageCapability": "Może dodawać, zmieniać i usuwać wpisy w Twojej kartotece, także te, które wprowadziłeś samodzielnie, oraz czytać wygenerowane na jej podstawie analizy. Nie może zmienić Twojego logowania, ustawień bezpieczeństwa, połączonych usług ani tego, kto jeszcze ma dostęp. Możesz to zakończyć w dowolnym momencie.",
+ "accessManageCapability": "Może dodawać, zmieniać i usuwać wpisy, także te, które wprowadziłeś samodzielnie.",
+ "accessManageDetail": "Może też czytać analizy wygenerowane na podstawie Twojej kartoteki. Nie może zmienić Twojego logowania, ustawień bezpieczeństwa, połączonych usług ani tego, kto jeszcze ma dostęp. Możesz to zakończyć w dowolnym momencie.",
"manageStepUpNote": "Przy takim zaproszeniu prosimy o drugi składnik logowania, dlatego można to zrobić tylko w przeglądarce.",
"scopeLegend": "Co ta osoba może otworzyć",
"scopeAllLabel": "Cała kartoteka",
@@ -9778,6 +9773,7 @@
"scopeAggregateNote": "Przegląd na pulpicie, wynik zdrowia i dzienne podsumowanie pojawiają się tylko wtedy, gdy udostępniona jest cała kartoteka.",
"scopeDocumentsNote": "Dokument może zawierać wszystko. Udostępnij tę sekcję tylko wtedy, gdy ta osoba może przeczytać całość.",
"scopeEmptyHint": "Wybierz co najmniej jedną sekcję albo wróć do udostępniania całej kartoteki.",
+ "scopeChooseHint": "Wybierz, czy udostępnić całą kartotekę, czy tylko wybrane sekcje.",
"expiresLabel": "Dostęp kończy się (opcjonalnie)",
"expiresHint": "Zostaw puste, aby dostęp trwał, dopóki ktoś go nie zakończy. Data jest sprawdzana przy każdym żądaniu, więc dostęp kończy się sam z końcem tego dnia.",
"submit": "Wyślij zaproszenie",
diff --git a/src/__tests__/sharing-recoverable-errors.test.tsx b/src/__tests__/sharing-recoverable-errors.test.tsx
index 43da41195..05e6618cf 100644
--- a/src/__tests__/sharing-recoverable-errors.test.tsx
+++ b/src/__tests__/sharing-recoverable-errors.test.tsx
@@ -353,8 +353,9 @@ describe("a refused invitation keeps what was typed", () => {
const split = INVITE_CARD.indexOf("onError:", start);
// The options object ends at the first ` },` after the error arm, so
// the slice stops at the mutation rather than running into the JSX below,
- // where a `setNarrowed(false)` on a radio would satisfy the matcher for
- // the wrong reason.
+ // where a `setNarrowed(false)` / `setNarrowed(true)` on a radio would
+ // satisfy the matcher for the wrong reason. The success reset returns the
+ // scope to unset (`null`), which the JSX never writes.
const end = INVITE_CARD.indexOf("\n },", split);
const onSuccess = INVITE_CARD.slice(start, split);
const onError = INVITE_CARD.slice(split, end);
@@ -367,7 +368,7 @@ describe("a refused invitation keeps what was typed", () => {
'setIdentifier("")',
'setExpiresOn("")',
'setAccess("READ")',
- "setNarrowed(false)",
+ "setNarrowed(null)",
"setSections([])",
]) {
expect(onSuccess, reset).toContain(reset);
diff --git a/src/app/api/account/grants/__tests__/invite-access-level.test.ts b/src/app/api/account/grants/__tests__/invite-access-level.test.ts
index 2507d194e..3798cc52f 100644
--- a/src/app/api/account/grants/__tests__/invite-access-level.test.ts
+++ b/src/app/api/account/grants/__tests__/invite-access-level.test.ts
@@ -38,7 +38,12 @@ vi.mock("@/lib/api-handler", async () => {
vi.mock("@/lib/db", () => ({
prisma: {
user: { findFirst: vi.fn() },
- accountGrant: { create: vi.fn() },
+ accountGrant: {
+ create: vi.fn(),
+ // inviteGrant sweeps expired live-pair rows before the create; nothing to
+ // close in these fixtures.
+ updateMany: vi.fn().mockResolvedValue({ count: 0 }),
+ },
},
}));
diff --git a/src/app/api/account/grants/__tests__/invite-scope-and-manage.test.ts b/src/app/api/account/grants/__tests__/invite-scope-and-manage.test.ts
index 1c290d0a5..265283687 100644
--- a/src/app/api/account/grants/__tests__/invite-scope-and-manage.test.ts
+++ b/src/app/api/account/grants/__tests__/invite-scope-and-manage.test.ts
@@ -43,7 +43,12 @@ vi.mock("@/lib/api-handler", async () => {
vi.mock("@/lib/db", () => ({
prisma: {
user: { findFirst: vi.fn() },
- accountGrant: { create: vi.fn() },
+ accountGrant: {
+ create: vi.fn(),
+ // inviteGrant sweeps expired live-pair rows before the create. No such
+ // row exists in these unit fixtures, so it closes nothing.
+ updateMany: vi.fn().mockResolvedValue({ count: 0 }),
+ },
},
}));
diff --git a/src/components/settings/__tests__/password-card-placement.test.tsx b/src/components/settings/__tests__/password-card-placement.test.tsx
index ec428f6f3..de120af1e 100644
--- a/src/components/settings/__tests__/password-card-placement.test.tsx
+++ b/src/components/settings/__tests__/password-card-placement.test.tsx
@@ -72,14 +72,30 @@ describe("change-password card placement", () => {
expect(render()).toContain(SLOT);
});
- it("is mounted by Settings → Security before the factor status lands", () => {
+ it("is mounted by Settings → Security even before the factor status lands", () => {
// The mocked query never resolves, which is the case that matters: the
// MFA read is still in flight (or failing) and the password card is
- // reachable regardless.
+ // reachable regardless — it sits outside the status conditional.
const html = render();
expect(html).toContain(SLOT);
});
+ it("orders the password card last, after the passkey list", () => {
+ // The section reads authenticator → security keys → passkeys → password.
+ // The order lives in one component and the resolved cards only render once
+ // the status query settles (the render mock keeps it in flight), so the
+ // guard reads the source: the password card must come after the passkey
+ // list. Re-reported once as an order regression; this pins it.
+ const source = readFileSync(
+ join(ROOT, "src/components/settings/security-section/index.tsx"),
+ "utf8",
+ );
+ const passkeyIdx = source.indexOf(" {
const owners = walk(join(ROOT, "src"))
.filter((file) => readFileSync(file, "utf8").includes("PasswordCard"))
diff --git a/src/components/settings/access/__tests__/grant-access-level.test.tsx b/src/components/settings/access/__tests__/grant-access-level.test.tsx
index d8ee6185a..b1c4f27f0 100644
--- a/src/components/settings/access/__tests__/grant-access-level.test.tsx
+++ b/src/components/settings/access/__tests__/grant-access-level.test.tsx
@@ -16,11 +16,20 @@
* a rewrite that quietly drops it should fail here.
*/
import { renderToStaticMarkup } from "react-dom/server";
+import { readFileSync } from "node:fs";
+import { join } from "node:path";
import { describe, expect, it, vi } from "vitest";
import { I18nProvider } from "@/lib/i18n/context";
import type { GrantList, GrantRow } from "@/lib/queries/use-account-grants";
+/** The shipped English invite copy, the source of truth for the consent text. */
+const inviteBundle = (
+ JSON.parse(readFileSync(join(process.cwd(), "messages/en.json"), "utf8")) as {
+ recordSharing: { invite: Record };
+ }
+).recordSharing.invite;
+
const grantsRef: { value: GrantList } = { value: grantList() };
/**
* A grant payload, with the audit window every real one carries.
@@ -110,8 +119,11 @@ describe("offering a level", () => {
// second group of radios in the same idiom, and a group that lost the ring
// would be the same defect shipped again one section further down.
const html = render();
+ // Access options carry an (i) sibling now, so the row is a `