@stacksjs/defaults 0.75.165 → 0.75.167

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,6 +25,13 @@ accepted input; fetchSubmissions and exportSubmissionsCsv serve administrative
25
25
  reads. Supply the site identity from trusted request context. A null site id is
26
26
  the single-site/global context, not a tenant identity from a client.
27
27
 
28
+ Public endpoints that are not persisted forms (a lead or contact form an app
29
+ stores itself) use `checkSubmissionSpam({ honeypot, renderedAtMs }, policy)`
30
+ from `@stacksjs/forms/spam`: `honeypot` (answer as if it worked, store nothing),
31
+ `too_fast` (under `minSubmitSeconds`, default 3) or null. submitForm uses the
32
+ same check with `config.forms.spam`. No CAPTCHA, no third-party script; still
33
+ rate-limit per address or IP.
34
+
28
35
  ## Uploads, notifications, and paid forms
29
36
 
30
37
  Use resolveUploadLimits, checkUpload, fileFieldNamed, formUploadPrefix and
@@ -256,6 +256,51 @@ with fixes recorded separately. In a browser and on Android `isAvailable()` is
256
256
  false, the state says `available: false` and nothing fires. Word iOS's prompt
257
257
  with `ios.usageDescriptions.NSBluetoothAlwaysUsageDescription`.
258
258
 
259
+ ## Apple Watch scheduled workouts
260
+
261
+ `scheduledWorkouts` puts structured workouts on the paired Apple Watch through
262
+ WorkoutKit's scheduler (iOS 17+): each appears in the watch's Workout app on
263
+ its day, with its warmup, repeated blocks, cooldown, goals and alerts. iOS
264
+ only, behind `capabilities.scheduledWorkouts`; only an app with it compiles in
265
+ and links WorkoutKit. The calls work on the page and on native stx screens.
266
+ A plan is plain JSON in SI units, its `id` a UUID the app keeps for it.
267
+
268
+ ```ts
269
+ import { scheduledWorkouts } from '@stacksjs/mobile'
270
+
271
+ if (await scheduledWorkouts.isSupported()) { // false off an iPhone, never rejects
272
+ await scheduledWorkouts.requestAuthorization() // iOS's prompt, once; call it from a tap
273
+ const kept = await scheduledWorkouts.list() // [{ id, date, hour, minute, complete }]
274
+ await scheduledWorkouts.schedule({
275
+ id: '6f1c2b8e-5a7d-4c3e-9b1a-2d4e6f8a0b1c',
276
+ date: '2026-10-20', hour: 6, minute: 30, // local; 07:00 without a time
277
+ workout: {
278
+ activity: 'running', location: 'outdoor', displayName: '4 x 1 km',
279
+ warmup: { goal: { type: 'time', seconds: 600 } },
280
+ blocks: [{ iterations: 4, steps: [
281
+ { purpose: 'work', step: { goal: { type: 'distance', meters: 1000 }, alert: { type: 'speed', min: 4.1, max: 4.4 } } },
282
+ { purpose: 'recovery', step: { goal: { type: 'time', seconds: 120 } } },
283
+ ] }],
284
+ },
285
+ })
286
+ await scheduledWorkouts.remove(kept[0].id) // every day it is on
287
+ }
288
+ ```
289
+
290
+ Goals are `open`, `time` (seconds), `distance` (meters) and `energy`
291
+ (kilocalories); alerts `heartRate`, `power`, `speed` (m/s), `cadence` ranges and
292
+ `heartRateZone` / `powerZone`. An activity or goal WorkoutKit refuses rejects
293
+ the call with a message naming it; an alert it refuses is left out and named
294
+ in the result's `droppedAlerts`. `schedule` also rejects LIMIT_REACHED (the
295
+ watch holds 15) and NOT_SCHEDULED when nothing kept the plan, as without a
296
+ paired watch. In a browser and on Android `isSupported()` is false and every
297
+ other call rejects `unsupported`.
298
+
299
+ For tests, `createScheduledWorkoutsFake()` from `@stacksjs/mobile/testing` is a
300
+ `craft.scheduledWorkouts` that answers like the shell. On the iOS simulator,
301
+ which never has a watch, the app's `CraftFakeWorkoutScheduler` default makes
302
+ the shell keep plans itself (`xcrun simctl spawn <device> defaults write <bundle id> CraftFakeWorkoutScheduler -bool YES`).
303
+
259
304
  ## Apple Music (now playing)
260
305
 
261
306
  `music` shows what the Music app is playing (title, artist, album, a small
@@ -51,7 +51,7 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
51
51
  | `@stacksjs/faker` | [`stacks-faker`](../stacks-faker/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/faker/src/index.ts) | 1 |
52
52
  | `@stacksjs/feature-flags` | [`stacks-feature-flags`](../stacks-feature-flags/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/feature-flags/src/index.ts) | 7 |
53
53
  | `@stacksjs/features` | [`stacks-features`](../stacks-features/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/features/src/index.ts) | 1 |
54
- | `@stacksjs/forms` | [`stacks-forms`](../stacks-forms/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/forms/src/index.ts) | 3 |
54
+ | `@stacksjs/forms` | [`stacks-forms`](../stacks-forms/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/forms/src/index.ts) | 4 |
55
55
  | `@stacksjs/git` | [`stacks-git`](../stacks-git/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/git/src/index.ts) | 1 |
56
56
  | `@stacksjs/github` | [`stacks-git`](../stacks-git/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/github/src/index.ts) | 7 |
57
57
  | `@stacksjs/health` | [`stacks-health`](../stacks-health/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/health/src/index.ts) | 2 |
@@ -60,10 +60,10 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
60
60
  | `@stacksjs/image` | [`stacks-image`](../stacks-image/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/image/src/index.ts) | 2 |
61
61
  | `@stacksjs/lint` | [`stacks-lint`](../stacks-lint/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/lint/src/index.ts) | 3 |
62
62
  | `@stacksjs/logging` | [`stacks-logging`](../stacks-logging/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/logging/src/index.ts) | 14 |
63
- | `@stacksjs/mobile` | [`stacks-mobile`](../stacks-mobile/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/index.ts) | 16 |
63
+ | `@stacksjs/mobile` | [`stacks-mobile`](../stacks-mobile/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/index.ts) | 17 |
64
64
  | `@stacksjs/model-meta` | [`stacks-models`](../stacks-models/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/model-meta/src/index.ts) | 1 |
65
65
  | `@stacksjs/newsletter` | [`stacks-newsletter`](../stacks-newsletter/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/newsletter/src/index.ts) | 7 |
66
- | `@stacksjs/notifications` | [`stacks-notifications`](../stacks-notifications/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/index.ts) | 14 |
66
+ | `@stacksjs/notifications` | [`stacks-notifications`](../stacks-notifications/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/index.ts) | 15 |
67
67
  | `@stacksjs/objects` | [`stacks-objects`](../stacks-objects/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/objects/src/index.ts) | 4 |
68
68
  | `@stacksjs/orm` | [`stacks-orm`](../stacks-orm/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/index.ts) | 60 |
69
69
  | `@stacksjs/pagination` | [`stacks-pagination`](../stacks-pagination/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/pagination/src/index.ts) | 0 |
@@ -497,11 +497,11 @@ Evidence directory: [1 retained tests](https://github.com/stacksjs/stacks/blob/m
497
497
 
498
498
  Guidance: [`stacks-forms`](../stacks-forms/SKILL.md). Source: [storage/framework/core/forms/src/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/forms/src/index.ts).
499
499
 
500
- Declared runtime exports: `checkUpload`, `completeSubmissionPayment`, `computeAmountCents`, `createForm`, `dispatchSubmissionNotifications`, `evaluateConditions`, `exportSubmissionsCsv`, `extensionOf`, `fetchSubmissions`, `fileFieldNamed`, `formUploadPrefix`, `isOwnedUploadPath`, `loadFormByHandle`, `loadFormByUuid`, `publicDefinition`, `resolveUploadLimits`, `submissionIdentity`, `submitForm`, `toCreateFields`, `validateSubmission`, `visibleFields`.
500
+ Declared runtime exports: `checkSubmissionSpam`, `checkUpload`, `completeSubmissionPayment`, `computeAmountCents`, `createForm`, `dispatchSubmissionNotifications`, `evaluateConditions`, `exportSubmissionsCsv`, `extensionOf`, `fetchSubmissions`, `fileFieldNamed`, `formUploadPrefix`, `isOwnedUploadPath`, `loadFormByHandle`, `loadFormByUuid`, `publicDefinition`, `resolveUploadLimits`, `submissionIdentity`, `submitForm`, `toCreateFields`, `validateSubmission`, `visibleFields`.
501
501
 
502
- Local source modules: [src/conditions.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/forms/src/conditions.ts), [src/create.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/forms/src/create.ts), [src/definition.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/forms/src/definition.ts), [src/notifications.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/forms/src/notifications.ts), [src/submissions.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/forms/src/submissions.ts), [src/uploads.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/forms/src/uploads.ts), [src/validate.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/forms/src/validate.ts).
502
+ Local source modules: [src/conditions.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/forms/src/conditions.ts), [src/create.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/forms/src/create.ts), [src/definition.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/forms/src/definition.ts), [src/notifications.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/forms/src/notifications.ts), [src/spam.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/forms/src/spam.ts), [src/submissions.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/forms/src/submissions.ts), [src/uploads.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/forms/src/uploads.ts), [src/validate.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/forms/src/validate.ts).
503
503
 
504
- Evidence directory: [3 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/forms/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
504
+ Evidence directory: [4 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/forms/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
505
505
 
506
506
  ### @stacksjs/git
507
507
 
@@ -581,9 +581,9 @@ Guidance: [`stacks-mobile`](../stacks-mobile/SKILL.md). Source: [storage/framewo
581
581
 
582
582
  Declared runtime exports: `appReview`, `biometrics`, `brightness`, `camera`, `deepLinks`, `device`, `getNativeMobileBridge`, `haptics`, `health`, `heartRate`, `isNativeMobile`, `keepAwake`, `lifecycle`, `liveActivities`, `location`, `mobile`, `music`, `network`, `normalizeDeepLinkURL`, `notifications`, `onMobileReady`, `permissions`, `pushNotifications`, `secureStorage`, `share`, `snapshots`, `speech`, `splash`, `watchConnectivity`, `whenNativeMobile`, `withNativeFeedback`.
583
583
 
584
- Local source modules: [src/bridge.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/bridge.ts), [src/controls.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/controls.ts), [src/events.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/events.ts), [src/gestures.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/gestures.ts), [src/indoor.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/indoor.ts), [src/native.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/native.ts), [src/navigation.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/navigation.ts), [src/refresh-control.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/refresh-control.ts), [src/route.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/route.ts), [src/session-clock.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/session-clock.ts), [src/sheet-gesture.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/sheet-gesture.ts), [src/sheets.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/sheets.ts), [src/shell.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/shell.ts), [src/spotlight.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/spotlight.ts), [src/tab-bar.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/tab-bar.ts), [src/types.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/types.ts).
584
+ Local source modules: [src/bridge.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/bridge.ts), [src/controls.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/controls.ts), [src/events.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/events.ts), [src/gestures.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/gestures.ts), [src/indoor.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/indoor.ts), [src/native.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/native.ts), [src/navigation.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/navigation.ts), [src/refresh-control.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/refresh-control.ts), [src/route.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/route.ts), [src/scheduled-workouts.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/scheduled-workouts.ts), [src/session-clock.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/session-clock.ts), [src/sheet-gesture.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/sheet-gesture.ts), [src/sheets.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/sheets.ts), [src/shell.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/shell.ts), [src/spotlight.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/spotlight.ts), [src/tab-bar.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/tab-bar.ts), [src/types.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/types.ts).
585
585
 
586
- Evidence directory: [16 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
586
+ Evidence directory: [17 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
587
587
 
588
588
  ### @stacksjs/model-meta
589
589
 
@@ -607,11 +607,11 @@ Evidence directory: [7 retained tests](https://github.com/stacksjs/stacks/blob/m
607
607
 
608
608
  Guidance: [`stacks-notifications`](../stacks-notifications/SKILL.md). Source: [storage/framework/core/notifications/src/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/index.ts).
609
609
 
610
- Declared runtime exports: `BroadcastNotificationDriver`, `DEFAULT_SMS_OPT_OUT`, `DEFAULT_UNSUBSCRIBE_NOTICE`, `DatabaseNotificationDriver`, `NOTIFICATION_FRAME_STRINGS`, `SMS_SENDER_MAX_LENGTH`, `accountNotificationTemplates`, `bulkSetPreferences`, `commerceNotificationTemplates`, `createNotificationTemplates`, `defineNotificationTemplates`, `emailParagraphs`, `ensureSuccessfulEmailResult`, `ensureSuccessfulNotificationResults`, `filterChannelsByPreferences`, `getNotificationPreferences`, `interpolateNotificationText`, `listUnsubscribeHeaders`, `makeDeliveryRecord`, `notification`, `notificationChannel`, `notificationEmailHtml`, `notificationEmailMessage`, `notificationEmailText`, `notificationFrameStrings`, `notificationLanguageTag`, `notificationSmsBody`, `notificationTemplateVariables`, `notificationUnsubscribeLinks`, `notificationUnsubscribeToken`, `notify`, `parseNotificationUnsubscribeToken`, `recordNotificationDelivery`, `renderNotificationTemplate`, `resolveDeliveryRecipient`, `resolveNotificationLocale`, `setNotificationPreference`, `smsSenderPrefix`, `subscriptionNotificationTemplates`, `unsubscribeTokenFromRequest`, `unsubscribeWithToken`, `useBroadcast`, `useChat`, `useDatabase`, `useEmail`, `useNotification`, `usePush`, `useSMS`, `wherePreferenceCategory`.
610
+ Declared runtime exports: `BroadcastNotificationDriver`, `DEFAULT_SMS_OPT_OUT`, `DEFAULT_UNSUBSCRIBE_NOTICE`, `DatabaseNotificationDriver`, `NOTIFICATION_FRAME_STRINGS`, `SMS_SENDER_MAX_LENGTH`, `accountNotificationTemplates`, `advanceSequence`, `bulkSetPreferences`, `commerceNotificationTemplates`, `createNotificationTemplates`, `defineNotificationTemplates`, `emailParagraphs`, `ensureSuccessfulEmailResult`, `ensureSuccessfulNotificationResults`, `filterChannelsByPreferences`, `getNotificationPreferences`, `interpolateNotificationText`, `isQuietTime`, `listUnsubscribeHeaders`, `makeDeliveryRecord`, `nextSendableTime`, `notification`, `notificationChannel`, `notificationEmailHtml`, `notificationEmailMessage`, `notificationEmailText`, `notificationFrameStrings`, `notificationLanguageTag`, `notificationSmsBody`, `notificationTemplateVariables`, `notificationUnsubscribeLinks`, `notificationUnsubscribeToken`, `notify`, `parseNotificationUnsubscribeToken`, `recordNotificationDelivery`, `renderNotificationTemplate`, `resolveDeliveryRecipient`, `resolveNotificationLocale`, `sequenceTimeline`, `setNotificationPreference`, `smsSenderPrefix`, `subscriptionNotificationTemplates`, `unsubscribeTokenFromRequest`, `unsubscribeWithToken`, `useBroadcast`, `useChat`, `useDatabase`, `useEmail`, `useNotification`, `usePush`, `useSMS`, `wherePreferenceCategory`.
611
611
 
612
- Local source modules: [src/delivery.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/delivery.ts), [src/drivers/broadcast.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/drivers/broadcast.ts), [src/drivers/database.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/drivers/database.ts), [src/drivers/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/drivers/index.ts), [src/email-body.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/email-body.ts), [src/frame-strings.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/frame-strings.ts), [src/preferences.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/preferences.ts), [src/templates.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/templates.ts), [src/unsubscribe.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/unsubscribe.ts).
612
+ Local source modules: [src/delivery.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/delivery.ts), [src/drivers/broadcast.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/drivers/broadcast.ts), [src/drivers/database.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/drivers/database.ts), [src/drivers/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/drivers/index.ts), [src/email-body.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/email-body.ts), [src/frame-strings.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/frame-strings.ts), [src/preferences.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/preferences.ts), [src/sequences.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/sequences.ts), [src/templates.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/templates.ts), [src/unsubscribe.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/unsubscribe.ts).
613
613
 
614
- Evidence directory: [14 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
614
+ Evidence directory: [15 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
615
615
 
616
616
  ### @stacksjs/objects
617
617
 
@@ -186,6 +186,34 @@ const { subject, body, action } = templates.render('password_changed', { app: 'H
186
186
  - `account_created` carries a credential link: send it directly, not through a
187
187
  stored queue that keeps the body.
188
188
 
189
+ ## Sequences: steps with waits and quiet hours
190
+
191
+ `@stacksjs/notifications/sequences` (pure; also exported from the root) is the
192
+ timing engine for journeys such as "email now, wait two days, text, then a call
193
+ task for staff". It never sends or stores anything: the app keeps each
194
+ person's next step index and due time, and on each tick asks what to do now.
195
+
196
+ ```ts
197
+ import { advanceSequence, sequenceTimeline } from '@stacksjs/notifications/sequences'
198
+
199
+ const quietHours = { start: '21:00', end: '08:00', timeZone: 'Europe/Berlin' }
200
+ const { run, next } = advanceSequence(steps, enrollment.step_index, new Date(), {
201
+ quietHours,
202
+ respectsQuietHours: step => step.type === 'email' || step.type === 'sms',
203
+ })
204
+ // perform `run` in order, then store next?.index / next?.at (null: finished)
205
+ ```
206
+
207
+ - A step is `{ type, minutes? }` plus whatever the app needs. `type: 'wait'`
208
+ with `minutes` sets when the sequence continues; a zero wait is skipped.
209
+ - A step that respects quiet hours (default: every non-wait) is held until the
210
+ window ends in its zone (`nextSendableTime`); `isQuietTime` answers for one
211
+ instant. Windows may cross midnight; DST comes from the runtime's tz data.
212
+ - `sequenceTimeline(steps, start, options)` lays the steps out in time for a
213
+ preview.
214
+ - Consent, unsubscribes and STOP are still the sender's checks (`notify()`,
215
+ preferences); the engine only decides when.
216
+
189
217
  ## Channel factories
190
218
 
191
219
  - `useEmail(driver?)` returns a sendable transport. Without a driver it uses
@@ -231,7 +259,7 @@ delivery log, not extra columns on the inbox.
231
259
  `storage/framework/core/notifications/src/index.ts` defines the real
232
260
  signatures. `preferences.ts`, `delivery.ts` and `drivers/database.ts`
233
261
  own their persistence; the query builder is bun-query-builder.
234
- `notifications.test.ts`, `delivery-tracking.test.ts`,
262
+ `notifications.test.ts`, `delivery-tracking.test.ts`, `sequences.test.ts`,
235
263
  `broadcast.test.ts`, `email-body.test.ts` and `frame-locale.test.ts` cover
236
264
  retained behavior; `frame-strings.ts` holds the frame's languages.
237
265
  For provider status, consult `config/src/capabilities.ts` and each channel
@@ -2,7 +2,7 @@
2
2
  "publisher": "Stacks",
3
3
  "name": "vscode-stacks",
4
4
  "displayName": "Stacks",
5
- "version": "0.75.165",
5
+ "version": "0.75.167",
6
6
  "description": "Stacks, built into VS Code: stx templates, pickier linting and formatting, encrypted env files, the dev server preview and every buddy command. No other extensions required.",
7
7
  "license": "MIT",
8
8
  "funding": "https://github.com/sponsors/chrisbbreuer",
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/defaults",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.75.165",
5
+ "version": "0.75.167",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/stacksjs/stacks.git",
@@ -55,7 +55,7 @@
55
55
  "dependencies": {
56
56
  "@iconify-json/f7": "^1.2.2",
57
57
  "@iconify-json/hugeicons": "^1.2.35",
58
- "@stacksjs/mobile": "0.75.165",
58
+ "@stacksjs/mobile": "0.75.167",
59
59
  "@stacksjs/sanitizer": "^0.2.483",
60
60
  "@xterm/addon-fit": "^0.11.0",
61
61
  "@xterm/xterm": "^6.0.0",