From 1bc487cc4ee23b3fffaad42918332b4e3ed2b3e3 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Sat, 10 Oct 2026 19:57:43 +1100 Subject: [PATCH] fix(identity): complete email setup from every account entry --- layers/identity/app/components/LikeButton.vue | 15 +++---- layers/identity/app/pages/me/index.vue | 15 +++++-- .../identity/app/pages/onboarding/email.vue | 15 ++++--- layers/identity/server/api/me/email.patch.ts | 10 ++++- .../identity/server/utils/account-settings.ts | 5 +++ test/unit/account-email-completion.test.ts | 41 +++++++++++++++++++ test/unit/digest-opt-in-address.test.ts | 17 ++++++++ test/unit/email-form-submit.nuxt.test.ts | 24 ++++++++++- test/unit/like-button.nuxt.test.ts | 18 +++++++- 9 files changed, 142 insertions(+), 18 deletions(-) create mode 100644 test/unit/account-email-completion.test.ts diff --git a/layers/identity/app/components/LikeButton.vue b/layers/identity/app/components/LikeButton.vue index b4161d1a..adb01ded 100644 --- a/layers/identity/app/components/LikeButton.vue +++ b/layers/identity/app/components/LikeButton.vue @@ -36,11 +36,8 @@ const nudgeDismissed = ref(true) /** * Shown once, to a signed-in user who has not finished onboarding. * - * It used to invite them to "set up digest", which asked for configuration the - * weekly does not need: the send already falls back to the GitHub address on - * the account. So it now confirms what the like just did instead of asking for - * anything, and only offers the email step when there is genuinely no address - * to send to. + * A like saves the Skill. It does not grant email consent. The weekly carries + * trending Skills; changes to liked Skills belong in the monthly digest. */ const showNudge = computed(() => variant === 'detail' @@ -154,10 +151,13 @@ const revealClass = computed(() => {

- Liked. Its changes land in your next weekly. + Liked. Email updates are optional. + + Email updates + { color="neutral" variant="ghost" aria-label="Dismiss" + class="min-h-11 min-w-11" @click="dismissNudge" />

diff --git a/layers/identity/app/pages/me/index.vue b/layers/identity/app/pages/me/index.vue index be5f3010..062f9efc 100644 --- a/layers/identity/app/pages/me/index.vue +++ b/layers/identity/app/pages/me/index.vue @@ -36,6 +36,7 @@ const { const actionFailed = useActionFailure() const rpc = useNuxtRpc() +const { fetchSession } = useAuth() async function refreshAccount() { me.value = await rpc.query(identityAccountQueries.me()) @@ -58,6 +59,7 @@ const removeSubscriptionMutation = useNuxtMutation({ mutation: async (body) => { const result = await rpc.execute(identityAccountQueries.saveEmail(), body) + await fetchSession() await refreshAccount() return result }, @@ -179,7 +181,7 @@ onMounted(() => { if (route.query.welcome === '1') { toast.add({ title: 'You\'re all set', - description: 'Watched repos send a digest when they change.', + description: 'Your email choices are saved. You can change them in Email updates.', color: 'success', icon: 'i-lucide-check-circle', }) @@ -273,7 +275,7 @@ async function deleteAccount() { Your skills

- Like a skill to keep it here. We track meaningful changes and include them in your digest. + Like a skill to keep it here. Email updates are optional.

@@ -284,6 +286,13 @@ async function deleteAccount() {
+
+

+ Choose which emails you receive. You can change this later. +

+ +
+
diff --git a/layers/identity/app/pages/onboarding/email.vue b/layers/identity/app/pages/onboarding/email.vue index 7e4782d9..a7715b62 100644 --- a/layers/identity/app/pages/onboarding/email.vue +++ b/layers/identity/app/pages/onboarding/email.vue @@ -8,12 +8,17 @@ const { data: me, error: accountError, status: accountStatus, refresh: retryAcco const { fetchSession } = useAuth() const email = ref(me.value?.digest_email || me.value?.email || '') +// A stored address records a deliberate choice, including a previous opt-out. +const hasEmailChoices = computed(() => !!me.value?.onboarded_at || !!me.value?.digest_email) +const optIn = ref(hasEmailChoices.value ? !!me.value?.email_opt_in : true) +const weeklyOptIn = ref(hasEmailChoices.value ? !!me.value?.weekly_opt_in : true) watch(me, (account, previous) => { - if (account && !previous) + if (account && !previous) { email.value = account.digest_email || account.email || '' + optIn.value = hasEmailChoices.value ? account.email_opt_in : true + weeklyOptIn.value = hasEmailChoices.value ? account.weekly_opt_in : true + } }) -const optIn = ref(true) -const weeklyOptIn = ref(true) const actionFailed = useActionFailure() const rpc = useNuxtRpc() @@ -95,14 +100,14 @@ useSeoMeta({ title: 'Email opt-in', robots: 'noindex' })