From ee8fb80cfee177319a4ffe658c03d3c6bff821a8 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Sun, 19 Jul 2026 20:23:34 -0600 Subject: [PATCH 1/5] start the SMS notifications, beginning with the UI --- src/constants/accountView.ts | 9 +- src/views/AccountViewView.vue | 201 ++++++++++++++++++++++-- src/views/HelpNotificationTypesView.vue | 25 +-- 3 files changed, 210 insertions(+), 25 deletions(-) diff --git a/src/constants/accountView.ts b/src/constants/accountView.ts index 3953b264..6fee50ff 100644 --- a/src/constants/accountView.ts +++ b/src/constants/accountView.ts @@ -57,9 +57,12 @@ export const ACCOUNT_VIEW_CONSTANTS = { // Notification messages NOTIFICATIONS: { NEW_ACTIVITY_INFO: ` - This will only notify you when there is new relevant activity for you personally. - Note that it runs on your device and many factors may affect delivery, - so if you want a reliable but simple daily notification then choose a 'Reminder'. + This will notify you with an app message when there is new relevant activity for you personally. + Do you want more details? + `, + NEW_ACTIVITY_SMS_INFO: ` + This is opt-in: by turning it on you agree to receive text messages when there is new relevant activity for you personally. + Standard message and data rates may apply, and you can turn it off at any time. Do you want more details? `, REMINDER_INFO: ` diff --git a/src/views/AccountViewView.vue b/src/views/AccountViewView.vue index 15918df9..affc3938 100644 --- a/src/views/AccountViewView.vue +++ b/src/views/AccountViewView.vue @@ -143,24 +143,48 @@ -
- -
+ + +
+
New Activity Notification
+

+ Get told about new relevant activity for you. Choose a delivery + channel below -- you can turn on either one, or both. +

+
+ + +
+ +
+
@@ -178,7 +202,7 @@ >
-
+
@@ -191,10 +215,75 @@ class="w-full text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md" @click="editNewActivityNotification" > - Edit New Activity Notification… + Edit In-App Time…
+ + +
+ +
+
+ +
+ +
+
+
+
+
+
+
+ Time: + {{ notifyingNewActivitySmsTime.replace(" ", " ") }} +
+
+ Text to: + {{ notifyingNewActivitySmsPhone || "(number not set)" }} +
+
+
+ +
+
+
Troubleshoot your notifications… @@ -546,7 +635,8 @@
-
+ +

Notification Push Server

@@ -931,6 +1021,12 @@ export default class AccountViewView extends Vue { // Notification properties notifyingNewActivity: boolean = false; notifyingNewActivityTime: string = ""; + // SMS delivery channel for New Activity (opt-in). MOCK-UP: state is currently + // component-local. TODO: persist via settings + register the number and time + // with the notify-api (SMS registration/verification flow). + notifyingNewActivitySms: boolean = false; + notifyingNewActivitySmsTime: string = ""; + notifyingNewActivitySmsPhone: string = ""; notifyingReminder: boolean = false; notifyingReminderMessage: string = ""; notifyingReminderTime: string = ""; @@ -1216,9 +1312,11 @@ export default class AccountViewView extends Vue { } } - async showNewActivityNotificationInfo(): Promise { + async showNewActivityNotificationInfo(isSms: boolean): Promise { this.notify.confirm( - ACCOUNT_VIEW_CONSTANTS.NOTIFICATIONS.NEW_ACTIVITY_INFO, + isSms + ? ACCOUNT_VIEW_CONSTANTS.NOTIFICATIONS.NEW_ACTIVITY_SMS_INFO + : ACCOUNT_VIEW_CONSTANTS.NOTIFICATIONS.NEW_ACTIVITY_INFO, async () => { await (this.$router as Router).push({ name: "help-notification-types", @@ -1266,6 +1364,89 @@ export default class AccountViewView extends Vue { } } + /** + * Toggle the SMS delivery channel for New Activity notifications. + * + * MOCK-UP: reuses the time-picker dialog so the UI behaves like the in-app + * channel, but the picked time is only held in component state for now. + * + * TODO (notify-api wiring): on enable, ensure the user has a verified phone + * number, then POST the SMS-notification preference to the notify-api with + * SMS-specific params (channel="sms", phoneNumber, sendTime) -- the same + * alert as the in-app channel, delivered by a different transport. On + * disable, tell the notify-api to remove the SMS preference. + */ + async showNewActivitySmsNotificationChoice(): Promise { + if (!this.notifyingNewActivitySms) { + ( + this.$refs.pushNotificationPermission as PushNotificationPermission + ).open( + DAILY_CHECK_TITLE, + async (success: boolean, timeText: string) => { + if (success) { + // TODO: verify phone number + register SMS preference with notify-api + this.notifyingNewActivitySms = true; + this.notifyingNewActivitySmsTime = timeText; + } + }, + { skipSchedule: true }, + ); + } else { + // TODO: remove SMS preference from notify-api + this.notifyingNewActivitySms = false; + this.notifyingNewActivitySmsTime = ""; + } + } + + /** + * Edit the time for the SMS delivery channel. + * + * MOCK-UP: updates component state only. TODO: PUT the updated sendTime to + * the notify-api SMS preference. + */ + async editNewActivitySmsNotification(): Promise { + const dialog = this.$refs + .pushNotificationPermission as PushNotificationPermission; + + dialog.open( + DAILY_CHECK_TITLE, + async (success: boolean, timeText: string) => { + if (!success) return; + // TODO: PUT updated sendTime to notify-api SMS preference + this.notifyingNewActivitySmsTime = timeText; + this.notify.success( + "New Activity SMS time updated.", + TIMEOUTS.STANDARD, + ); + }, + { skipSchedule: true }, + ); + + // Pre-populate the dialog with the current SMS time + setTimeout(() => { + const timeMatch = this.notifyingNewActivitySmsTime.match( + /(\d+):(\d+)\s*(AM|PM)/i, + ); + if (timeMatch) { + let hour = parseInt(timeMatch[1], 10); + const minute = timeMatch[2]; + const isAm = timeMatch[3].toUpperCase() === "AM"; + if (hour === 12) { + hour = 12; + } else if (hour > 12) { + hour = hour - 12; + } + const dialogComponent = + dialog as unknown as PushNotificationPermissionRef; + if (dialogComponent) { + dialogComponent.hourInput = hour.toString(); + dialogComponent.minuteInput = minute; + dialogComponent.hourAm = isAm; + } + } + }, 150); + } + /** * Configure native fetcher, sync starred plans, and schedule API-driven dual notification. */ diff --git a/src/views/HelpNotificationTypesView.vue b/src/views/HelpNotificationTypesView.vue index 35242a35..9516b851 100644 --- a/src/views/HelpNotificationTypesView.vue +++ b/src/views/HelpNotificationTypesView.vue @@ -39,31 +39,32 @@ something, so you can record thanks in here.

- This is a reliable message, but it doesn't contain any details about + This is your own message activated in the app, but it doesn't contain any details about activity that might be especially interesting to you. + Note that the timing is not precise: the device may deliver it a bit later than scheduled.

-

New Activity Notifications

+

New Activity Notifications -- In App

- The New Activity Notification will be sent to you when there is new, relevant activity + The New Activity Notification will be activated in your app when there is new, relevant activity for you. It will only trigger if something involves you or a project of interest; it will not bug you for other, general activity.

+
+ +

New Activity Notifications -- Texts From A Server

+

- This type is not as reliable as a Reminder Notification because mobile devices often - suppress such notifications to save battery. (If you want to quickly check for relevant - activity daily, use the Reminder Notification and open the app and look for a large green - button that points out new activity that is personal to you. We are working on other - ways to notify you more reliably. - - go here to follow us or contact us - - .) + This New Activity Notification SMS will be sent to you when there is new, relevant activity + for you. + It will only trigger if something involves you or a project of interest; it will not + bug you for other, general activity.

+
From 3f291b0d63dc02f2ec070d09ceb172f20a990f90 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Thu, 13 Aug 2026 09:45:16 -0600 Subject: [PATCH 2/5] add a more comprehensive demo UI workflow for SMS signup --- CHANGELOG.md | 5 + src/components/SmsVerificationDialog.vue | 284 +++++++++++++++++++++++ src/constants/accountView.ts | 2 + src/views/AccountViewView.vue | 232 +++++++++++++----- src/views/HelpNotificationTypesView.vue | 36 ++- 5 files changed, 502 insertions(+), 57 deletions(-) create mode 100644 src/components/SmsVerificationDialog.vue diff --git a/CHANGELOG.md b/CHANGELOG.md index 9514f07d..f055705d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [?] - 2026 +### Added +- Full flow for setting up SMS notifications + + ## [1.3.8] - 2026 ### Added - Device wake-up for notifications diff --git a/src/components/SmsVerificationDialog.vue b/src/components/SmsVerificationDialog.vue new file mode 100644 index 00000000..aafb27f7 --- /dev/null +++ b/src/components/SmsVerificationDialog.vue @@ -0,0 +1,284 @@ + + + diff --git a/src/constants/accountView.ts b/src/constants/accountView.ts index 6fee50ff..fb5b5029 100644 --- a/src/constants/accountView.ts +++ b/src/constants/accountView.ts @@ -65,6 +65,8 @@ export const ACCOUNT_VIEW_CONSTANTS = { Standard message and data rates may apply, and you can turn it off at any time. Do you want more details? `, + SMS_FORGET_PHONE_CONFIRM: + "This will remove your phone number and turn off all text messages. You will have to verify a number again to re-enable them. Are you sure?", REMINDER_INFO: ` This will notify you at a specific time each day. Note that it does not give you personalized notifications, diff --git a/src/views/AccountViewView.vue b/src/views/AccountViewView.vue index affc3938..1f4b2734 100644 --- a/src/views/AccountViewView.vue +++ b/src/views/AccountViewView.vue @@ -220,7 +220,7 @@
- +
@@ -229,26 +229,23 @@ class="fa-fw text-slate-400" aria-hidden="true" /> - SMS Text Message - + {{ + notifyingNewActivitySmsPhone + ? "SMS Text Message" + : "Enable Text Messages" + }}
- +
-
+
+
- Time: - {{ notifyingNewActivitySmsTime.replace(" ", " ") }} + Texting: {{ notifyingNewActivitySmsPhone }}
+ +
+
+ +
+
- Text to: - {{ notifyingNewActivitySmsPhone || "(number not set)" }} + New Activity Text + +
+ +
+ +
+
-
- +
+ Time: + {{ notifyingNewActivitySmsTime.replace(" ", " ") }} +
+
+
+ +
@@ -291,6 +341,7 @@ +
{ - if (!this.notifyingNewActivitySms) { - ( - this.$refs.pushNotificationPermission as PushNotificationPermission - ).open( - DAILY_CHECK_TITLE, - async (success: boolean, timeText: string) => { - if (success) { - // TODO: verify phone number + register SMS preference with notify-api - this.notifyingNewActivitySms = true; - this.notifyingNewActivitySmsTime = timeText; - } - }, - { skipSchedule: true }, - ); - } else { - // TODO: remove SMS preference from notify-api + async toggleSmsEnabled(): Promise { + if (this.smsEnabled) { + // Pause the channel; the verified number stays for easy re-enable. + // TODO(notify-api): disable all SMS preferences server-side. + this.smsEnabled = false; this.notifyingNewActivitySms = false; this.notifyingNewActivitySmsTime = ""; + return; } + + if (this.notifyingNewActivitySmsPhone) { + // Number already verified: just switch the channel back on. + this.smsEnabled = true; + return; + } + + (this.$refs.smsVerificationDialog as SmsVerificationDialog).open( + async (success: boolean, phoneNumber?: string) => { + if (success && phoneNumber) { + this.notifyingNewActivitySmsPhone = phoneNumber; + this.smsEnabled = true; + } + }, + ); + } + + /** + * Forget the verified phone number entirely: the master toggle turns off + * and every SMS notification becomes unavailable until a number is + * verified again. + */ + async forgetSmsPhoneNumber(): Promise { + this.notify.confirm( + ACCOUNT_VIEW_CONSTANTS.NOTIFICATIONS.SMS_FORGET_PHONE_CONFIRM, + async () => { + // TODO(notify-api): DELETE /api/sms/register to remove the number + // and all SMS preferences server-side. + this.notifyingNewActivitySmsPhone = ""; + this.smsEnabled = false; + this.notifyingNewActivitySms = false; + this.notifyingNewActivitySmsTime = ""; + }, + ); + } + + /** + * Toggle the "New Activity Text" notification (only reachable while the + * SMS channel is enabled). + * + * TODO (notify-api wiring): on enable, POST the SMS-notification preference + * with SMS-specific params (channel="sms", phoneNumber, sendTime) -- the + * same alert as the in-app channel, delivered by a different transport. On + * disable, tell the notify-api to remove it. + */ + async showNewActivitySmsNotificationChoice(): Promise { + if (this.notifyingNewActivitySms) { + this.notifyingNewActivitySms = false; + this.notifyingNewActivitySmsTime = ""; + return; + } + await this.promptSmsTimeAndEnable(); + } + + /** + * Prompt for the SMS send time (reusing the time-picker dialog) and enable + * the SMS channel on success. + * + * MOCK-UP: the chosen time is held in component state only. + */ + async promptSmsTimeAndEnable(): Promise { + (this.$refs.pushNotificationPermission as PushNotificationPermission).open( + DAILY_CHECK_TITLE, + async (success: boolean, timeText: string) => { + if (success) { + // TODO: register SMS preference with notify-api (phone + time) + this.notifyingNewActivitySms = true; + this.notifyingNewActivitySmsTime = timeText; + } + }, + { skipSchedule: true }, + ); } /** @@ -1922,6 +2040,10 @@ export default class AccountViewView extends Vue { this.notifyingReminder = false; this.notifyingReminderMessage = ""; this.notifyingReminderTime = ""; + // SMS channel (mock-up, component-local). TODO: also tell the notify-api. + // Leaves the verified phone number in place intentionally. + this.notifyingNewActivitySms = false; + this.notifyingNewActivitySmsTime = ""; } /** diff --git a/src/views/HelpNotificationTypesView.vue b/src/views/HelpNotificationTypesView.vue index 9516b851..a42c0835 100644 --- a/src/views/HelpNotificationTypesView.vue +++ b/src/views/HelpNotificationTypesView.vue @@ -58,11 +58,43 @@

New Activity Notifications -- Texts From A Server

- This New Activity Notification SMS will be sent to you when there is new, relevant activity - for you. + This New Activity Notification SMS will be sent to you when there is new, relevant activity + for you. It will only trigger if something involves you or a project of interest; it will not bug you for other, general activity.

+

Notes and caveats for text messages:

+
    +
  • + Text messages are opt-in: they are only sent after you enter your + phone number and confirm it with a verification code. +
  • +
  • + Standard message and data rates from your carrier may apply. +
  • +
  • + Message frequency is at most one New Activity text per day, at the + time you choose. +
  • +
  • + You can stop at any time: turn off the toggle in your settings, use + "Forget Phone Number" to remove your number entirely, or reply STOP + to any message. Reply HELP for help. +
  • +
  • + Delivery is not guaranteed or precisely timed; carriers may delay + or drop messages, and neither we nor the carriers are liable for + late or undelivered texts. +
  • +
  • + Only US phone numbers are currently supported. +
  • +
  • + Your phone number is used only to send you these notifications; it + is stored on the notification server and is deleted when you choose + "Forget Phone Number". +
  • +
From b02a1b29c5a89c8359cdf71cac20ecbff42184f0 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Sat, 22 Aug 2026 13:01:48 -0600 Subject: [PATCH 3/5] add T&C/privacy link for notification help --- src/views/HelpNotificationTypesView.vue | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/views/HelpNotificationTypesView.vue b/src/views/HelpNotificationTypesView.vue index a42c0835..a0ea10c7 100644 --- a/src/views/HelpNotificationTypesView.vue +++ b/src/views/HelpNotificationTypesView.vue @@ -94,6 +94,12 @@ is stored on the notification server and is deleted when you choose "Forget Phone Number". +
  • + See our + + Terms & Conditions and Privacy Policy docs here. + +
  • From 940e851d3c4a3c5d20e498637c2ddb2c1b238ffa Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Sun, 30 Aug 2026 17:55:23 -0600 Subject: [PATCH 4/5] adjust the UX for SMS text opt-in, to show terms immediately --- src/components/ChoiceButtonDialog.vue | 3 ++ src/constants/accountView.ts | 4 +++ src/views/AccountViewView.vue | 42 +++++++++++++++++++-------- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/components/ChoiceButtonDialog.vue b/src/components/ChoiceButtonDialog.vue index 21624aae..3dae5622 100644 --- a/src/components/ChoiceButtonDialog.vue +++ b/src/components/ChoiceButtonDialog.vue @@ -25,6 +25,7 @@

    {{ text }}

    +
    { - if (success && phoneNumber) { - this.notifyingNewActivitySmsPhone = phoneNumber; - this.smsEnabled = true; - } + (this.$refs.smsOptInChoiceDialog as ChoiceButtonDialog).open({ + title: "Enable Text Messages", + text: ACCOUNT_VIEW_CONSTANTS.NOTIFICATIONS.SMS_OPT_IN_CHOICE, + option1Text: "See More Info", + option2Text: "Enter Phone Number", + onOption1: async () => { + await (this.$router as Router).push({ + name: "help-notification-types", + }); }, - ); + onOption2: () => { + (this.$refs.smsVerificationDialog as SmsVerificationDialog).open( + async (success: boolean, phoneNumber?: string) => { + if (success && phoneNumber) { + this.notifyingNewActivitySmsPhone = phoneNumber; + this.smsEnabled = true; + } + }, + ); + }, + }); } /** From 59993cb96a8ee627d6750563ec08ff48da892991 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Sun, 30 Aug 2026 17:59:01 -0600 Subject: [PATCH 5/5] fix husky git hooks (because it was complaining about those lines) --- .husky/commit-msg | 2 -- .husky/pre-commit | 2 -- .husky/pre-push | 2 -- 3 files changed, 6 deletions(-) diff --git a/.husky/commit-msg b/.husky/commit-msg index 4b8c242d..cc1ce805 100755 --- a/.husky/commit-msg +++ b/.husky/commit-msg @@ -1,9 +1,7 @@ -#!/usr/bin/env bash # # Husky Commit Message Hook # Validates commit message format using commitlint # -. "$(dirname -- "$0")/_/husky.sh" # Run commitlint but don't fail the commit (|| true) # This provides helpful feedback without blocking commits diff --git a/.husky/pre-commit b/.husky/pre-commit index b31dbcc0..f122047f 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,9 +1,7 @@ -#!/usr/bin/env bash # # Husky Pre-commit Hook # Runs lint-fix and Build Architecture Guard on staged files # -. "$(dirname -- "$0")/_/husky.sh" echo "🔍 Running pre-commit hooks..." diff --git a/.husky/pre-push b/.husky/pre-push index 2d79fde4..67780a48 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,9 +1,7 @@ -#!/usr/bin/env bash # # Husky Pre-push Hook # Runs Build Architecture Guard to check commits being pushed # -. "$(dirname -- "$0")/_/husky.sh" echo "🔍 Running Build Architecture Guard (pre-push)..."