feat: add Discord reminder and timezone settings

This commit is contained in:
syntaxbullet
2026-09-08 08:23:27 +02:00
parent 3487488f7b
commit 5dbec04b04
12 changed files with 324 additions and 28 deletions

View File

@@ -33,3 +33,29 @@ local midnight (including DST). Delivery claims, nonce, opt-out, and retry rules
are unchanged. The dashboard preview explicitly disables buttons because it has
no signed interaction endpoint configured. Global command registration and the
Discord interaction endpoint must be configured separately before production use.
## Preferences
`preferences.ts` implements global `/settings` and `/reminders`. Reminder menus
paginate ten named schedules at a time; entries with matching name and schedule
are edited together as multiple comma-separated 24-hour times. Forms accept
weekday names (or `every day`) or one ISO calendar date. Delivery is toggled for
all schedules. Removing the last schedule disables delivery and stores an empty
list; the shared contract permits empty lists only while disabled. Invalid forms
provide a prefilled retry button. UTC accounts receive a timezone setup hint.
`ReminderService` is shared with the website routes. Every save advances its
`updatedAt` monotonically; Discord controls compare the saved preference snapshot
and the user's timezone/update version before editing. `HabitService.setTimezone`
also advances the user timestamp monotonically, including repeated writes in the
same millisecond. This rejects old Discord forms after edits from either interface,
including change-and-revert sequences. Website PUT retains its existing request
contract. Delivery receipts do not invalidate preference controls.
Integration: retain the `preferences` command dispatch and `preferencesAction`
component dispatch in `routes.ts`, the two definitions in `commands.ts`, and the
preference fields in `ControlData`. `CommandConfig.remindersAvailable` supplies
availability; `createApi` derives it from the existing sharing bot token, matching
the website and reminder worker. Direct route fixtures default it to false.
No new environment variables, migrations, outbound requests, or remote command
registration are needed by this module. Global command registration remains an
integration step. Successful mutations invoke the supplied live-card dirty hook.