@stacksjs/defaults 0.75.164 → 0.75.166

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
@@ -229,6 +229,12 @@ every reading natively, screen locked or app closed, until `stopRecording()`
229
229
  hands them back. iOS only, behind `capabilities.heartRate` (which adds the
230
230
  `bluetooth-central` background mode); Bluetooth needs no network.
231
231
 
232
+ `capabilities.backgroundAudio` (iOS) adds the `audio` background mode, so a
233
+ soundscape played with Web Audio keeps going with the screen locked: a sleep
234
+ timer built on stx's `useAmbientSound().fadeOut(seconds, after)` then fades it
235
+ out on the audio clock. App Review expects audible content from an app that
236
+ asks for it.
237
+
232
238
  ```ts
233
239
  import { createRouteRecorder, heartRate, location } from '@stacksjs/mobile'
234
240
 
@@ -250,6 +256,51 @@ with fixes recorded separately. In a browser and on Android `isAvailable()` is
250
256
  false, the state says `available: false` and nothing fires. Word iOS's prompt
251
257
  with `ios.usageDescriptions.NSBluetoothAlwaysUsageDescription`.
252
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
+
253
304
  ## Apple Music (now playing)
254
305
 
255
306
  `music` shows what the Music app is playing (title, artist, album, a small
@@ -422,7 +473,10 @@ energy, distance, heart rate, resting heart rate, HRV, weight and sleep.
422
473
  `health.getHeartRateSamples(range)` returns every heart-rate reading (bpm,
423
474
  start/end, source and device) and `health.getSleepIntervals(range)` the night's
424
475
  in-bed, awake and asleep stages; check `health.supports(method)` first, since an
425
- app build that predates a read resolves `[]` for it. Completed
476
+ app build that predates a read resolves `[]` for it. Mindful minutes go through
477
+ `health.saveMindfulSession({ id, startDate, endDate })` (the `mindful` grant;
478
+ the id is the sample's external UUID, so a repeat save keeps the first) and come
479
+ back from `health.getMindfulSessions(range)` with `ownApp` on this app's own. Completed
426
480
  recordings can be written back with `health.saveWorkout(...)`; treat permission
427
481
  revocation as a normal runtime state and never block saving the application's
428
482
  own activity when a health write fails.
@@ -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.164",
5
+ "version": "0.75.166",
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.164",
5
+ "version": "0.75.166",
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.164",
58
+ "@stacksjs/mobile": "0.75.166",
59
59
  "@stacksjs/sanitizer": "^0.2.483",
60
60
  "@xterm/addon-fit": "^0.11.0",
61
61
  "@xterm/xterm": "^6.0.0",