@stacksjs/defaults 0.75.468 → 0.75.475

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.
@@ -44,6 +44,23 @@ Source: `src/two-factor.ts` and default auth actions. Evidence:
44
44
  `two-factor-disable-reauth.test.ts`, `two-factor-challenge-atomicity.test.ts` and
45
45
  `two-factor-state-atomicity.test.ts`.
46
46
 
47
+ ### Recovery codes and retryable setup
48
+
49
+ `confirmTwoFactorSetup(userId, code)` verifies the pending server-owned setup
50
+ without discarding it on a mistyped code. It commits factor activation and ten
51
+ recovery codes together. The response returns plaintext codes once; only hashes
52
+ bound to that factor version persist in the auth-owned
53
+ `two_factor_recovery_codes` table. Run `buddy migrate --auth` when upgrading.
54
+
55
+ `verifyTwoFactorCredential(userId, code)` accepts a six-digit authenticator code
56
+ or a one-use recovery code and shares the account's second-factor lockout.
57
+ Call it inside `withTwoFactorChallenge` before issuing the completed session.
58
+ `generateTwoFactorRecoveryCodes(userId)` replaces all backup codes; applications
59
+ must require fresh factor proof before calling it from a management endpoint.
60
+ `remainingTwoFactorRecoveryCodes(userId)` returns only the available count.
61
+ Never log codes, include them in URLs, cache their response or persist plaintext
62
+ in browser or native storage.
63
+
47
64
  ## Cookie tokens and database sessions
48
65
 
49
66
  Bearer/token-cookie auth and `SessionAuth` database sessions use different
@@ -294,6 +294,17 @@ appears; `buddy release:notes <tag> --testflight` does it later, and
294
294
  `buddy release:notes <tag> --play` prints the 500-character Google Play text.
295
295
  The code is `@stacksjs/actions/release-notes`.
296
296
 
297
+ Set `ios.testFlightGroups` to the names of existing internal groups (for example,
298
+ `['Internal testers']`) to distribute processed builds to those groups.
299
+ `release:ios --wait` confirms `IN_BETA_TESTING` and the configured memberships
300
+ before retiring older builds; upload or `VALID` processing alone is insufficient.
301
+ Without a wait, older builds stay available. Resume a tagged release with
302
+ `buddy release:testflight <tag> --wait [minutes]`, preserving its existing
303
+ localized notes; `--dry-run` validates the
304
+ existing internal groups without assigning or expiring builds. This creates no
305
+ testers, public links or external beta review submissions. `--keep-builds` retains
306
+ that many older processed builds in addition to the newly available release.
307
+
297
308
  ## Device search index
298
309
 
299
310
  `spotlight` says what of the application's own content a device may index, so
@@ -748,3 +759,14 @@ nativeFonts: {
748
759
  Craft >= 0.0.229 packages the files and licenses into both apps. iOS registers
749
760
  `UIAppFonts`; Android loads asset typefaces. The build rejects older builders,
750
761
  missing font files and invalid names. Fonts work without a network request.
762
+
763
+ ## Native passkeys
764
+
765
+ Enable `ios.capabilities.passkeys` and a `webcredentials:<relying-party-host>`
766
+ associated domain. Craft >= 0.0.451 supplies the system AuthenticationServices
767
+ sheet. Import `passkeys` from `@stacksjs/mobile`, check
768
+ `await passkeys.isAvailable()`, then pass server-issued JSON WebAuthn options to
769
+ `create(options)` or `get(options)`. Send the JSON credential back to the server
770
+ for verification. The native capability never replaces the server challenge,
771
+ origin and user-verification checks. On unsupported hosts retain another sign-in
772
+ method and offer the installation's secure browser settings.
@@ -11,14 +11,14 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
11
11
  | Package | Task guidance | Source entry | Retained tests |
12
12
  |---|---|---|---|
13
13
  | `@stacksjs/action-runner` | [`stacks-actions`](../stacks-actions/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/action-runner/src/index.ts) | 1 |
14
- | `@stacksjs/actions` | [`stacks-actions`](../stacks-actions/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/index.ts) | 101 |
14
+ | `@stacksjs/actions` | [`stacks-actions`](../stacks-actions/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/index.ts) | 102 |
15
15
  | `@stacksjs/ai` | [`stacks-ai`](../stacks-ai/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/ai/src/index.ts) | 21 |
16
16
  | `@stacksjs/alias` | [`stacks-alias`](../stacks-alias/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/alias/src/index.ts) | 3 |
17
17
  | `@stacksjs/analytics` | [`stacks-analytics`](../stacks-analytics/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/analytics/src/index.ts) | 7 |
18
18
  | `@stacksjs/api` | [`stacks-api`](../stacks-api/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/api/src/index.ts) | 12 |
19
19
  | `@stacksjs/arrays` | [`stacks-arrays`](../stacks-arrays/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/arrays/src/index.ts) | 12 |
20
20
  | `@stacksjs/audio` | [`stacks-audio`](../stacks-audio/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/audio/src/index.ts) | 3 |
21
- | `@stacksjs/auth` | [`stacks-auth`](../stacks-auth/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/index.ts) | 130 |
21
+ | `@stacksjs/auth` | [`stacks-auth`](../stacks-auth/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/index.ts) | 131 |
22
22
  | `@stacksjs/browser` | [`stacks-browser`](../stacks-browser/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/browser/src/index.ts) | 9 |
23
23
  | `@stacksjs/browser-extension` | [`stacks-browser-extension`](../stacks-browser-extension/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/browser-extension/src/index.ts) | 9 |
24
24
  | `@stacksjs/buddy` | [`stacks-buddy`](../stacks-buddy/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/buddy/src/index.ts) | 181 |
@@ -31,11 +31,11 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
31
31
  | `@stacksjs/cloud` | [`stacks-cloud`](../stacks-cloud/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/cloud/src/index.ts) | 10 |
32
32
  | `@stacksjs/cms` | [`stacks-cms`](../stacks-cms/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/cms/src/index.ts) | 5 |
33
33
  | `@stacksjs/collections` | [`stacks-collections`](../stacks-collections/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/collections/src/index.ts) | 2 |
34
- | `@stacksjs/commerce` | [`stacks-commerce`](../stacks-commerce/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/index.ts) | 14 |
34
+ | `@stacksjs/commerce` | [`stacks-commerce`](../stacks-commerce/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/index.ts) | 15 |
35
35
  | `@stacksjs/composables` | [`stacks-composables`](../stacks-composables/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/composables/src/index.ts) | 18 |
36
36
  | `@stacksjs/config` | [`stacks-config`](../stacks-config/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/config/src/index.ts) | 16 |
37
37
  | `@stacksjs/cron` | [`stacks-cron`](../stacks-cron/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/cron/src/index.ts) | 6 |
38
- | `@stacksjs/database` | [`stacks-database`](../stacks-database/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/index.ts) | 139 |
38
+ | `@stacksjs/database` | [`stacks-database`](../stacks-database/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/index.ts) | 140 |
39
39
  | `@stacksjs/datetime` | [`stacks-datetime`](../stacks-datetime/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/datetime/src/index.ts) | 22 |
40
40
  | `@stacksjs/defaults` | [`stacks-build`](../stacks-build/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/defaults/package.json) | 2 |
41
41
  | `@stacksjs/deploy` | [`stacks-deploy`](../stacks-deploy/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/deploy/package.json) | 1 |
@@ -43,7 +43,7 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
43
43
  | `@stacksjs/development` | [`stacks-development`](../stacks-development/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/development/package.json) | 1 |
44
44
  | `@stacksjs/dns` | [`stacks-dns`](../stacks-dns/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/dns/src/index.ts) | 4 |
45
45
  | `@stacksjs/docs` | [`stacks-docs`](../stacks-docs/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/docs/src/index.ts) | 2 |
46
- | `@stacksjs/email` | [`stacks-email`](../stacks-email/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/email/src/index.ts) | 23 |
46
+ | `@stacksjs/email` | [`stacks-email`](../stacks-email/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/email/src/index.ts) | 24 |
47
47
  | `@stacksjs/enums` | [`stacks-enums`](../stacks-enums/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/enums/src/index.ts) | 3 |
48
48
  | `@stacksjs/env` | [`stacks-env`](../stacks-env/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/env/src/index.ts) | 31 |
49
49
  | `@stacksjs/error-handling` | [`stacks-error-handling`](../stacks-error-handling/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/error-handling/src/index.ts) | 10 |
@@ -86,7 +86,7 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
86
86
  | `@stacksjs/slug` | [`stacks-slug`](../stacks-slug/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/slug/src/index.ts) | 5 |
87
87
  | `@stacksjs/sms` | [`stacks-sms`](../stacks-sms/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/sms/src/index.ts) | 6 |
88
88
  | `@stacksjs/socials` | [`stacks-socials`](../stacks-socials/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/socials/src/index.ts) | 18 |
89
- | `@stacksjs/storage` | [`stacks-storage`](../stacks-storage/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/index.ts) | 49 |
89
+ | `@stacksjs/storage` | [`stacks-storage`](../stacks-storage/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/index.ts) | 50 |
90
90
  | `@stacksjs/strings` | [`stacks-strings`](../stacks-strings/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/strings/src/index.ts) | 11 |
91
91
  | `@stacksjs/testing` | [`stacks-testing`](../stacks-testing/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/testing/src/index.ts) | 24 |
92
92
  | `@stacksjs/tinker` | [`stacks-repl`](../stacks-repl/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/tinker/src/index.ts) | 1 |
@@ -115,11 +115,11 @@ Evidence directory: [1 retained tests](https://github.com/stacksjs/stacks/blob/m
115
115
 
116
116
  Guidance: [`stacks-actions`](../stacks-actions/SKILL.md). Source: [storage/framework/core/actions/src/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/index.ts).
117
117
 
118
- Declared runtime exports: `BlogAdminError`, `DEFAULT_IOS_DEPENDENCIES`, `DEFAULT_STX_LINT_CONFIG`, `IosToolchainError`, `PUBLICATION_CHECK_SCRIPT`, `XCODE_NOTE`, `assertPublicationAllowed`, `blogSlugify`, `craftSupportsWebkit`, `createComponent`, `createDatabase`, `createFactory`, `createFunction`, `createLanguage`, `createMail`, `createMiddleware`, `createMigration`, `createModel`, `createNotification`, `createPage`, `deleteBlogPost`, `describeDanglingRouteActions`, `describeGitHubRelease`, `discoverPackages`, `ensureDiscoveredPackages`, `findDanglingRouteActions`, `formatProject`, `getBlogPost`, `installStack`, `isDryRunActive`, `lintFix`, `lintProject`, `listBlogPosts`, `listStacks`, `loadIosDependencies`, `loadStxLintConfig`, `macosAgentActionFiles`, `macosAgentDevFiles`, `macosAgentDevManifest`, `macosAgentEntry`, `macosAgentRouteFiles`, `makeAction`, `makeCertificate`, `makeCommand`, `makeComponent`, `makeDatabase`, `makeFunction`, `makeJob`, `makeLanguage`, `makeMail`, `makeNotification`, `makePage`, `makePolicy`, `makeQueueTable`, `makeResource`, `makeStack`, `measureDefaultsDrift`, `pantryExecutable`, `pantryInstallArgs`, `parseFields`, `parseGitHubRemote`, `prepareMacosAgent`, `prepareMacosNative`, `publicationChannels`, `publishGitHubRelease`, `resolveStxSource`, `resolveXcodegen`, `runAdd`, `runCommit`, `runMake`, `runStxLint`, `saveBlogPost`, `scaffoldCrud`, `setDryRun`, `summarizeStructureChanges`, `uninstallStack`, `validateReleaseEvidencePlan`, `verifyReleaseEvidence`, `xcodeDeveloperDir`.
118
+ Declared runtime exports: `BlogAdminError`, `DEFAULT_IOS_DEPENDENCIES`, `DEFAULT_STX_LINT_CONFIG`, `IosToolchainError`, `PUBLICATION_CHECK_SCRIPT`, `XCODE_NOTE`, `assertPublicationAllowed`, `blogSlugify`, `craftSupportsWebkit`, `createComponent`, `createDatabase`, `createFactory`, `createFunction`, `createLanguage`, `createMail`, `createMiddleware`, `createMigration`, `createModel`, `createNotification`, `createPage`, `deleteBlogPost`, `describeDanglingRouteActions`, `describeGitHubRelease`, `discoverPackages`, `ensureDiscoveredPackages`, `evaluateReviewOsEvidence`, `findDanglingRouteActions`, `formatProject`, `getBlogPost`, `inspectReviewOsEvidence`, `installStack`, `isDryRunActive`, `lintFix`, `lintProject`, `listBlogPosts`, `listStacks`, `loadIosDependencies`, `loadStxLintConfig`, `macosAgentActionFiles`, `macosAgentDevFiles`, `macosAgentDevManifest`, `macosAgentEntry`, `macosAgentRouteFiles`, `makeAction`, `makeCertificate`, `makeCommand`, `makeComponent`, `makeDatabase`, `makeFunction`, `makeJob`, `makeLanguage`, `makeMail`, `makeNotification`, `makePage`, `makePolicy`, `makeQueueTable`, `makeResource`, `makeStack`, `measureDefaultsDrift`, `pantryExecutable`, `pantryInstallArgs`, `parseFields`, `parseGitHubRemote`, `prepareMacosAgent`, `prepareMacosNative`, `publicationChannels`, `publishGitHubRelease`, `resolveStxSource`, `resolveXcodegen`, `runAdd`, `runCommit`, `runMake`, `runStxLint`, `saveBlogPost`, `scaffoldCrud`, `setDryRun`, `summarizeStructureChanges`, `uninstallStack`, `validateReleaseEvidencePlan`, `verifyReleaseEvidence`, `xcodeDeveloperDir`.
119
119
 
120
- Local source modules: [src/action.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/action.ts), [src/add.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/add.ts), [src/blog-admin.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/blog-admin.ts), [src/build/ios-toolchain.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/build/ios-toolchain.ts), [src/build/macos-native.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/build/macos-native.ts), [src/commit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/commit.ts), [src/dev/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/dev/index.ts), [src/discover-packages.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/discover-packages.ts), [src/generate/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/generate/index.ts), [src/github-release.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/github-release.ts), [src/helpers/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/helpers/index.ts), [src/library/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/library/index.ts), [src/lint/lint.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/lint/lint.ts), [src/lint/stx-gate.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/lint/stx-gate.ts), [src/make-command.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/make-command.ts), [src/make-job.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/make-job.ts), [src/make-policy.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/make-policy.ts), [src/make-resource.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/make-resource.ts), [src/make.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/make.ts), [src/publication-preflight.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/publication-preflight.ts), [src/release-evidence.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/release-evidence.ts), [src/scaffold-crud.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/scaffold-crud.ts), [src/setup/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/setup/index.ts), [src/stacks.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/stacks.ts), [src/stx-source.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/stx-source.ts), [src/upgrade/package-project.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/upgrade/package-project.ts), [src/upgrade/route-actions.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/upgrade/route-actions.ts).
120
+ Local source modules: [src/action.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/action.ts), [src/add.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/add.ts), [src/blog-admin.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/blog-admin.ts), [src/build/ios-toolchain.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/build/ios-toolchain.ts), [src/build/macos-native.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/build/macos-native.ts), [src/commit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/commit.ts), [src/dev/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/dev/index.ts), [src/discover-packages.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/discover-packages.ts), [src/generate/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/generate/index.ts), [src/github-release.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/github-release.ts), [src/helpers/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/helpers/index.ts), [src/library/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/library/index.ts), [src/lint/lint.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/lint/lint.ts), [src/lint/stx-gate.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/lint/stx-gate.ts), [src/make-command.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/make-command.ts), [src/make-job.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/make-job.ts), [src/make-policy.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/make-policy.ts), [src/make-resource.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/make-resource.ts), [src/make.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/make.ts), [src/publication-preflight.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/publication-preflight.ts), [src/release-evidence.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/release-evidence.ts), [src/reviewos-evidence.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/reviewos-evidence.ts), [src/scaffold-crud.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/scaffold-crud.ts), [src/setup/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/setup/index.ts), [src/stacks.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/stacks.ts), [src/stx-source.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/stx-source.ts), [src/upgrade/package-project.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/upgrade/package-project.ts), [src/upgrade/route-actions.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/upgrade/route-actions.ts).
121
121
 
122
- Evidence directory: [101 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
122
+ Evidence directory: [102 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
123
123
 
124
124
  ### @stacksjs/ai
125
125
 
@@ -187,9 +187,9 @@ Guidance: [`stacks-auth`](../stacks-auth/SKILL.md). Source: [storage/framework/c
187
187
 
188
188
  Declared runtime exports: `DEFAULT_ROLE_PACKS`, `adminEmails`, `createBqbRbacStore`, `databaseAdminAccounts`, `generateTOTP`, `generateTOTPSecret`, `nameFromEmail`, `provisionAdmins`, `requestImpersonation`, `seedDefaultRoles`, `totpKeyUri`, `verifyTOTP`.
189
189
 
190
- Local source modules: [src/admins.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/admins.ts), [src/authentication.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/authentication.ts), [src/authenticator.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/authenticator.ts), [src/authorizable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/authorizable.ts), [src/browser-session-store.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/browser-session-store.ts), [src/browser-session.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/browser-session.ts), [src/client.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/client.ts), [src/cookie-auth.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/cookie-auth.ts), [src/credential-user.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/credential-user.ts), [src/email-verification.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/email-verification.ts), [src/gate.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/gate.ts), [src/impersonation-auth.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/impersonation-auth.ts), [src/impersonation-store.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/impersonation-store.ts), [src/impersonation.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/impersonation.ts), [src/magic-link.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/magic-link.ts), [src/middleware.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/middleware.ts), [src/oauth-authorization-codes.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-authorization-codes.ts), [src/oauth-authorization-endpoint.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-authorization-endpoint.ts), [src/oauth-authorization-requests.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-authorization-requests.ts), [src/oauth-authorization-response.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-authorization-response.ts), [src/oauth-authorization.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-authorization.ts), [src/oauth-browser-session.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-browser-session.ts), [src/oauth-client-registration.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-client-registration.ts), [src/oauth-consent.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-consent.ts), [src/oauth-delegated-access.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-delegated-access.ts), [src/oauth-dynamic-registration.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-dynamic-registration.ts), [src/oauth-grants.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-grants.ts), [src/oauth-introspection.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-introspection.ts), [src/oauth-metadata.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-metadata.ts), [src/oauth-pkce.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-pkce.ts), [src/oauth-protected-resource.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-protected-resource.ts), [src/oauth-provider.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-provider.ts), [src/oauth-pruning.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-pruning.ts), [src/oauth-revocation.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-revocation.ts), [src/oauth-server.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-server.ts), [src/oauth-token-endpoint.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-token-endpoint.ts), [src/oauth-token-exchange.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-token-exchange.ts), [src/oauth-token-request.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-token-request.ts), [src/oauth-token-response.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-token-response.ts), [src/oauth-userinfo.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-userinfo.ts), [src/page-gate.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/page-gate.ts), [src/passkey.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/passkey.ts), [src/passkeys.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/passkeys.ts), [src/password/reset-token.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/password/reset-token.ts), [src/password/reset.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/password/reset.ts), [src/permissions.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/permissions.ts), [src/policy.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/policy.ts), [src/rate-limiter.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/rate-limiter.ts), [src/rbac-seed.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/rbac-seed.ts), [src/rbac-store-bqb.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/rbac-store-bqb.ts), [src/rbac.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/rbac.ts), [src/referrals.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/referrals.ts), [src/register.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/register.ts), [src/request-token.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/request-token.ts), [src/session-auth.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/session-auth.ts), [src/session-handoff.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/session-handoff.ts), [src/socials.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/socials.ts), [src/team.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/team.ts), [src/tokens.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/tokens.ts), [src/two-factor.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/two-factor.ts), [src/user.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/user.ts).
190
+ Local source modules: [src/admins.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/admins.ts), [src/authentication.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/authentication.ts), [src/authenticator.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/authenticator.ts), [src/authorizable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/authorizable.ts), [src/browser-session-store.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/browser-session-store.ts), [src/browser-session.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/browser-session.ts), [src/client.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/client.ts), [src/cookie-auth.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/cookie-auth.ts), [src/credential-user.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/credential-user.ts), [src/email-verification.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/email-verification.ts), [src/gate.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/gate.ts), [src/impersonation-auth.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/impersonation-auth.ts), [src/impersonation-store.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/impersonation-store.ts), [src/impersonation.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/impersonation.ts), [src/magic-link.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/magic-link.ts), [src/middleware.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/middleware.ts), [src/oauth-authorization-codes.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-authorization-codes.ts), [src/oauth-authorization-endpoint.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-authorization-endpoint.ts), [src/oauth-authorization-requests.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-authorization-requests.ts), [src/oauth-authorization-response.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-authorization-response.ts), [src/oauth-authorization.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-authorization.ts), [src/oauth-browser-session.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-browser-session.ts), [src/oauth-client-registration.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-client-registration.ts), [src/oauth-consent.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-consent.ts), [src/oauth-delegated-access.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-delegated-access.ts), [src/oauth-dynamic-registration.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-dynamic-registration.ts), [src/oauth-grants.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-grants.ts), [src/oauth-introspection.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-introspection.ts), [src/oauth-metadata.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-metadata.ts), [src/oauth-pkce.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-pkce.ts), [src/oauth-protected-resource.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-protected-resource.ts), [src/oauth-provider.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-provider.ts), [src/oauth-pruning.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-pruning.ts), [src/oauth-revocation.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-revocation.ts), [src/oauth-server.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-server.ts), [src/oauth-token-endpoint.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-token-endpoint.ts), [src/oauth-token-exchange.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-token-exchange.ts), [src/oauth-token-request.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-token-request.ts), [src/oauth-token-response.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-token-response.ts), [src/oauth-userinfo.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-userinfo.ts), [src/page-gate.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/page-gate.ts), [src/passkey.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/passkey.ts), [src/passkeys.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/passkeys.ts), [src/password/reset-token.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/password/reset-token.ts), [src/password/reset.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/password/reset.ts), [src/permissions.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/permissions.ts), [src/policy.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/policy.ts), [src/rate-limiter.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/rate-limiter.ts), [src/rbac-seed.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/rbac-seed.ts), [src/rbac-store-bqb.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/rbac-store-bqb.ts), [src/rbac.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/rbac.ts), [src/referrals.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/referrals.ts), [src/register.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/register.ts), [src/request-token.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/request-token.ts), [src/session-auth.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/session-auth.ts), [src/session-handoff.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/session-handoff.ts), [src/socials.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/socials.ts), [src/team.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/team.ts), [src/tokens.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/tokens.ts), [src/two-factor-recovery-codes.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/two-factor-recovery-codes.ts), [src/two-factor.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/two-factor.ts), [src/user.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/user.ts).
191
191
 
192
- Evidence directory: [130 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
192
+ Evidence directory: [131 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
193
193
 
194
194
  ### @stacksjs/browser
195
195
 
@@ -317,7 +317,7 @@ Declared runtime exports: `PriceFormatError`, `auctions`, `breakdownFor`, `carts
317
317
 
318
318
  Local source modules: [src/auctions/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/auctions/index.ts), [src/carts/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/carts/index.ts), [src/catalog.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/catalog.ts), [src/coupons/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/coupons/index.ts), [src/customers/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/customers/index.ts), [src/devices/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/devices/index.ts), [src/errors/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/errors/index.ts), [src/gift-cards/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/gift-cards/index.ts), [src/imports/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/imports/index.ts), [src/inventory.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/inventory.ts), [src/money.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/money.ts), [src/orders/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/orders/index.ts), [src/payments/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/payments/index.ts), [src/products/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/products/index.ts), [src/receipt-text.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/receipt-text.ts), [src/receipts/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/receipts/index.ts), [src/register.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/register.ts), [src/releases.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/releases.ts), [src/sales-tax.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/sales-tax.ts), [src/shippings/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/shippings/index.ts), [src/tax-input.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/tax-input.ts), [src/tax/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/tax/index.ts), [src/waitlists/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/waitlists/index.ts), [src/waitlists/restaurant/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/src/waitlists/restaurant/index.ts).
319
319
 
320
- Evidence directory: [14 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
320
+ Evidence directory: [15 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/commerce/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
321
321
 
322
322
  ### @stacksjs/composables
323
323
 
@@ -357,7 +357,7 @@ Declared runtime exports: `Database`, `EntityQueryBuilder`, `__flushAfterCommitN
357
357
 
358
358
  Local source modules: [src/affected-rows.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/affected-rows.ts), [src/auth-tables.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/auth-tables.ts), [src/custom/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/custom/index.ts), [src/database.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/database.ts), [src/datetime-columns.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/datetime-columns.ts), [src/ddl-constraints.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/ddl-constraints.ts), [src/defaults.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/defaults.ts), [src/dialect.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/dialect.ts), [src/driver-config.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/driver-config.ts), [src/drivers/dynamodb.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/drivers/dynamodb.ts), [src/drivers/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/drivers/index.ts), [src/ensure-database.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/ensure-database.ts), [src/erd.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/erd.ts), [src/fk-audit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/fk-audit.ts), [src/framework-renames.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/framework-renames.ts), [src/framework-route-admission.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/framework-route-admission.ts), [src/hand-written-migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/hand-written-migrations.ts), [src/ignored-migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/ignored-migrations.ts), [src/index-shape.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/index-shape.ts), [src/managed-columns.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/managed-columns.ts), [src/migration-dialect.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migration-dialect.ts), [src/migration-ledger.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migration-ledger.ts), [src/migration-names.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migration-names.ts), [src/migration-path.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migration-path.ts), [src/migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migrations.ts), [src/model-sources.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/model-sources.ts), [src/notification-tables.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/notification-tables.ts), [src/package-migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/package-migrations.ts), [src/package-models.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/package-models.ts), [src/query-logger.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/query-logger.ts), [src/rbac-tables.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/rbac-tables.ts), [src/relation-columns.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/relation-columns.ts), [src/replicas.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/replicas.ts), [src/row-lease.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/row-lease.ts), [src/row-lock.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/row-lock.ts), [src/safe-migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/safe-migrations.ts), [src/schema-align.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/schema-align.ts), [src/schema-drift.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/schema-drift.ts), [src/seeder.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/seeder.ts), [src/shadowed-models.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/shadowed-models.ts), [src/sql-helpers.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/sql-helpers.ts), [src/trait-tables.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/trait-tables.ts), [src/transaction-context.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/transaction-context.ts), [src/transaction-options.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/transaction-options.ts), [src/types.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/types.ts), [src/unique-audit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/unique-audit.ts), [src/utc-defaults.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/utc-defaults.ts), [src/utils.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/utils.ts), [src/uuid-columns.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/uuid-columns.ts), [src/vschema.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/vschema.ts).
359
359
 
360
- Evidence directory: [139 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
360
+ Evidence directory: [140 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
361
361
 
362
362
  ### @stacksjs/datetime
363
363
 
@@ -431,7 +431,7 @@ Declared runtime exports: `discoverMailables`, `inlineCss`, `loadSampleProps`, `
431
431
 
432
432
  Local source modules: [src/css-inliner.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/email/src/css-inliner.ts), [src/drivers/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/email/src/drivers/index.ts), [src/email.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/email/src/email.ts), [src/idempotency.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/email/src/idempotency.ts), [src/inbound-parser.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/email/src/inbound-parser.ts), [src/inbox-mailbox.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/email/src/inbox-mailbox.ts), [src/mailable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/email/src/mailable.ts), [src/mime-preview.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/email/src/mime-preview.ts), [src/preview-ui.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/email/src/preview-ui.ts), [src/preview.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/email/src/preview.ts), [src/sdk/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/email/src/sdk/index.ts), [src/suppression.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/email/src/suppression.ts), [src/template.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/email/src/template.ts), [src/types.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/email/src/types.ts), [src/unsubscribe.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/email/src/unsubscribe.ts), [src/webhook-dedup.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/email/src/webhook-dedup.ts), [src/webhook-events.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/email/src/webhook-events.ts), [src/webhook-handlers.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/email/src/webhook-handlers.ts), [src/webhook-signatures.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/email/src/webhook-signatures.ts).
433
433
 
434
- Evidence directory: [23 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/email/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
434
+ Evidence directory: [24 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/email/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
435
435
 
436
436
  ### @stacksjs/enums
437
437
 
@@ -591,7 +591,7 @@ Guidance: [`stacks-mobile`](../stacks-mobile/SKILL.md). Source: [storage/framewo
591
591
 
592
592
  Declared runtime exports: `appReview`, `biometrics`, `brightness`, `camera`, `createVoiceRecorder`, `deepLinks`, `device`, `getNativeMobileBridge`, `haptics`, `health`, `heartRate`, `isNativeMobile`, `keepAwake`, `lifecycle`, `liveActivities`, `location`, `mobile`, `music`, `network`, `normalizeDeepLinkURL`, `notifications`, `onMobileReady`, `permissions`, `pushNotifications`, `secureStorage`, `share`, `snapshots`, `speech`, `splash`, `voiceRecordingFormData`, `watchConnectivity`, `whenNativeMobile`, `withNativeFeedback`.
593
593
 
594
- 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/crash-reporting.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/crash-reporting.ts), [src/crypto.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/crypto.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/storage.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/storage.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), [src/voice-recorder.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/voice-recorder.ts).
594
+ 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/crash-reporting.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/crash-reporting.ts), [src/crypto.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/crypto.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/passkeys.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/passkeys.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/storage.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/storage.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), [src/voice-recorder.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/voice-recorder.ts).
595
595
 
596
596
  Evidence directory: [23 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.
597
597
 
@@ -637,7 +637,7 @@ Evidence directory: [10 retained tests](https://github.com/stacksjs/stacks/blob/
637
637
 
638
638
  Guidance: [`stacks-orm`](../stacks-orm/SKILL.md). Source: [storage/framework/core/orm/src/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/index.ts).
639
639
 
640
- Declared runtime exports: `Activity`, `AnalyticsEvent`, `Auction`, `AuctionItem`, `Author`, `Automation`, `AutomationRun`, `BILLABLE_INSTANCE_METHODS`, `Bid`, `Board`, `BoardColumn`, `Campaign`, `CampaignSend`, `CampaignVariant`, `Card`, `CardAttachment`, `CardComment`, `Cart`, `CartItem`, `Categorizable`, `Category`, `Comment`, `CommunicationSuppression`, `ConsentEvent`, `Coupon`, `Courier`, `CourierPing`, `Customer`, `DeliveryRoute`, `DeliveryStop`, `Deployment`, `DigitalDelivery`, `EmailIdempotency`, `EmailList`, `EmailListSubscriber`, `EmailSuppression`, `EmailWebhookEvent`, `ErrorModel`, `FailedJob`, `FeedbackToken`, `Form`, `FormField`, `FormSubmission`, `GdprRequest`, `GiftCard`, `Job`, `Label`, `LicenseKey`, `Log`, `LoyaltyPoint`, `LoyaltyReward`, `MagicLinkToken`, `MailPreference`, `Manufacturer`, `Menu`, `MenuItem`, `Notification`, `NotificationDelivery`, `Order`, `OrderIdempotency`, `OrderItem`, `Page`, `PageRevision`, `Payment`, `PaymentMethod`, `PaymentProduct`, `PaymentTransaction`, `PaymentWebhookEvent`, `PersonalAccessToken`, `Pledge`, `Post`, `PrintDevice`, `Product`, `ProductUnit`, `ProductVariant`, `QueryLog`, `Receipt`, `Redirect`, `Referral`, `ReferralCode`, `Release`, `Request`, `Review`, `SenderDomain`, `ShippingMethod`, `ShippingRate`, `ShippingZone`, `Site`, `SiteDomain`, `SmsOptOut`, `SocialAccount`, `SocialPost`, `StorageItem`, `StorageItemTask`, `Subscriber`, `SubscriberEmail`, `Subscription`, `SubscriptionNotOwnedError`, `Tag`, `TaxRate`, `Team`, `TeamInvitation`, `TeamMember`, `Transaction`, `UsageEvent`, `User`, `WaitlistProduct`, `WaitlistRestaurant`, `Websocket`, `buildDatabaseSchema`, `collectEncryptedAttributes`, `createAuditMethods`, `createCategorizableMethods`, `createCommentableMethods`, `createLikeableMethods`, `createTaggableMethods`, `customerOwnership`, `decryptValue`, `encryptValue`, `enrichPaginatorUrls`, `filterFillable`, `frameworkModelFeature`, `getWritableFields`, `isBillable`, `isCursorPaginator`, `isEncrypted`, `isPaginator`, `isSimplePaginator`, `isUniqueViolation`, `mapWriteError`, `modelGlobalNames`, `ormReady`, `parentOwnership`, `parseCursor`, `renderDatabaseTypeFile`, `resolveCursorArgs`, `resolvePageArgs`, `selfOwnership`, `setAuditUser`, `siteOwnership`, `teamMembershipOwnership`, `toCursorPaginator`, `toPaginator`, `toSimplePaginator`, `toSnakeCaseKeys`.
640
+ Declared runtime exports: `Activity`, `AnalyticsEvent`, `Auction`, `AuctionItem`, `Author`, `Automation`, `AutomationRun`, `BILLABLE_INSTANCE_METHODS`, `Bid`, `Board`, `BoardColumn`, `Campaign`, `CampaignSend`, `CampaignVariant`, `Card`, `CardAttachment`, `CardComment`, `Cart`, `CartItem`, `Categorizable`, `Category`, `Comment`, `CommunicationSuppression`, `ConsentEvent`, `Coupon`, `Courier`, `CourierPing`, `Customer`, `DeliveryRoute`, `DeliveryStop`, `Deployment`, `DigitalDelivery`, `EmailIdempotency`, `EmailList`, `EmailListSubscriber`, `EmailSuppression`, `EmailWebhookEvent`, `ErrorModel`, `FailedJob`, `FeedbackToken`, `Form`, `FormField`, `FormSubmission`, `GdprRequest`, `GiftCard`, `Job`, `Label`, `LicenseKey`, `Log`, `LoyaltyPoint`, `LoyaltyReward`, `MagicLinkToken`, `MailPreference`, `Manufacturer`, `Menu`, `MenuItem`, `ModelUnavailableError`, `Notification`, `NotificationDelivery`, `Order`, `OrderIdempotency`, `OrderItem`, `Page`, `PageRevision`, `Payment`, `PaymentMethod`, `PaymentProduct`, `PaymentTransaction`, `PaymentWebhookEvent`, `PersonalAccessToken`, `Pledge`, `Post`, `PrintDevice`, `Product`, `ProductUnit`, `ProductVariant`, `QueryLog`, `Receipt`, `Redirect`, `Referral`, `ReferralCode`, `Release`, `Request`, `Review`, `SenderDomain`, `ShippingMethod`, `ShippingRate`, `ShippingZone`, `Site`, `SiteDomain`, `SmsOptOut`, `SocialAccount`, `SocialPost`, `StorageItem`, `StorageItemTask`, `Subscriber`, `SubscriberEmail`, `Subscription`, `SubscriptionNotOwnedError`, `Tag`, `TaxRate`, `Team`, `TeamInvitation`, `TeamMember`, `Transaction`, `UsageEvent`, `User`, `WaitlistProduct`, `WaitlistRestaurant`, `Websocket`, `buildDatabaseSchema`, `collectEncryptedAttributes`, `createAuditMethods`, `createCategorizableMethods`, `createCommentableMethods`, `createLikeableMethods`, `createTaggableMethods`, `customerOwnership`, `decryptValue`, `encryptValue`, `enrichPaginatorUrls`, `filterFillable`, `frameworkModelFeature`, `getWritableFields`, `isBillable`, `isCursorPaginator`, `isEncrypted`, `isPaginator`, `isSimplePaginator`, `isUniqueViolation`, `mapWriteError`, `modelGlobalNames`, `ormReady`, `parentOwnership`, `parseCursor`, `renderDatabaseTypeFile`, `resolveCursorArgs`, `resolvePageArgs`, `selfOwnership`, `setAuditUser`, `siteOwnership`, `teamMembershipOwnership`, `toCursorPaginator`, `toPaginator`, `toSimplePaginator`, `toSnakeCaseKeys`.
641
641
 
642
642
  Local source modules: [src/auto-crud.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/auto-crud.ts), [src/batch-loader.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/batch-loader.ts), [src/db.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/db.ts), [src/define-model.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/define-model.ts), [src/extend-model.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/extend-model.ts), [src/gdpr/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/gdpr/index.ts), [src/generate-database-schema.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/generate-database-schema.ts), [src/locked-row.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/locked-row.ts), [src/messaging.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/messaging.ts), [src/model-types.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/model-types.ts), [src/ownership.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/ownership.ts), [src/paginator-request.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/paginator-request.ts), [src/row-token.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/row-token.ts), [src/subquery.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/subquery.ts), [src/traits/audit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/audit.ts), [src/traits/billable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/billable.ts), [src/traits/categorizable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/categorizable.ts), [src/traits/commentable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/commentable.ts), [src/traits/likeable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/likeable.ts), [src/traits/taggable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/taggable.ts), [src/transaction.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/transaction.ts), [src/types.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/types.ts), [src/utils/encrypted.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/utils/encrypted.ts), [src/utils/prunable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/utils/prunable.ts).
643
643
 
@@ -839,11 +839,11 @@ Evidence directory: [18 retained tests](https://github.com/stacksjs/stacks/blob/
839
839
 
840
840
  Guidance: [`stacks-storage`](../stacks-storage/SKILL.md). Source: [storage/framework/core/storage/src/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/index.ts).
841
841
 
842
- Declared runtime exports: `PathSanitizeError`, `Storage`, `StorageManager`, `UploadedFile`, `azureDisk`, `backblazeDisk`, `clearRevokedSignedStorageTokens`, `configFromEnv`, `createSignedStorageToken`, `detectMimeFromMagicBytes`, `ensureBucket`, `ensureConfiguredBuckets`, `fileInfo`, `fileInfoFromBytes`, `fileKind`, `filebaseDisk`, `gcsDisk`, `hetznerDisk`, `isPreviewable`, `isSignedStorageTokenRevoked`, `localDisk`, `parseDiskPath`, `r2Disk`, `resolveMime`, `revokeSignedStorageToken`, `s3Disk`, `sanitizePresignedDir`, `sanitizePresignedFilename`, `serveFile`, `signS3PresignedPost`, `storage`, `uploadedFile`, `uploadedFiles`, `verifySignedStorageToken`, `verifyUploadedMime`.
842
+ Declared runtime exports: `DirectoryListingChangedError`, `PathSanitizeError`, `Storage`, `StorageManager`, `UploadedFile`, `azureDisk`, `backblazeDisk`, `clearRevokedSignedStorageTokens`, `configFromEnv`, `createSignedStorageToken`, `detectMimeFromMagicBytes`, `ensureBucket`, `ensureConfiguredBuckets`, `fileInfo`, `fileInfoFromBytes`, `fileKind`, `filebaseDisk`, `gcsDisk`, `hetznerDisk`, `isPreviewable`, `isSignedStorageTokenRevoked`, `localDisk`, `parseDiskPath`, `r2Disk`, `resolveMime`, `revokeSignedStorageToken`, `s3Disk`, `sanitizePresignedDir`, `sanitizePresignedFilename`, `serveFile`, `signS3PresignedPost`, `storage`, `uploadedFile`, `uploadedFiles`, `verifySignedStorageToken`, `verifyUploadedMime`.
843
843
 
844
- Local source modules: [src/adapters/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/adapters/index.ts), [src/bounded-text-file.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/bounded-text-file.ts), [src/completed-artifact-directory.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/completed-artifact-directory.ts), [src/copy.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/copy.ts), [src/delete.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/delete.ts), [src/drivers/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/drivers/index.ts), [src/facade.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/facade.ts), [src/file-info.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/file-info.ts), [src/files.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/files.ts), [src/folders.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/folders.ts), [src/fs.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/fs.ts), [src/glob.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/glob.ts), [src/hash.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/hash.ts), [src/helpers.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/helpers.ts), [src/mime-verify.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/mime-verify.ts), [src/path-sanitize.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/path-sanitize.ts), [src/provision.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/provision.ts), [src/s3-presigned-post.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/s3-presigned-post.ts), [src/signed-url.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/signed-url.ts), [src/static-serve.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/static-serve.ts), [src/storage.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/storage.ts), [src/temporary-workspaces.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/temporary-workspaces.ts), [src/types.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/types.ts), [src/types/filesystem.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/types/filesystem.ts), [src/uploaded-file.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/uploaded-file.ts), [src/zip.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/zip.ts).
844
+ Local source modules: [src/adapters/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/adapters/index.ts), [src/bounded-text-file.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/bounded-text-file.ts), [src/completed-artifact-directory.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/completed-artifact-directory.ts), [src/copy.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/copy.ts), [src/delete.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/delete.ts), [src/directory-page.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/directory-page.ts), [src/drivers/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/drivers/index.ts), [src/facade.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/facade.ts), [src/file-info.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/file-info.ts), [src/files.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/files.ts), [src/folders.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/folders.ts), [src/fs.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/fs.ts), [src/glob.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/glob.ts), [src/hash.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/hash.ts), [src/helpers.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/helpers.ts), [src/mime-verify.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/mime-verify.ts), [src/path-sanitize.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/path-sanitize.ts), [src/provision.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/provision.ts), [src/s3-presigned-post.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/s3-presigned-post.ts), [src/signed-url.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/signed-url.ts), [src/static-serve.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/static-serve.ts), [src/storage.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/storage.ts), [src/temporary-workspaces.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/temporary-workspaces.ts), [src/types.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/types.ts), [src/types/filesystem.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/types/filesystem.ts), [src/uploaded-file.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/uploaded-file.ts), [src/zip.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/zip.ts).
845
845
 
846
- Evidence directory: [49 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
846
+ Evidence directory: [50 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
847
847
 
848
848
  ### @stacksjs/strings
849
849
 
@@ -131,3 +131,22 @@ tenant. Underlying optional methods still need driver support.
131
131
 
132
132
  Source: `signed-url.ts`, `static-serve.ts`, `adapters/scoped.ts`; evidence:
133
133
  `tests/scoped.test.ts`, `static-serve.test.ts`, `path-sanitize.test.ts`.
134
+
135
+ ## Resumable inventories
136
+
137
+ Use a disk's optional `listPage(path, { limit, cursor })` for a durable shallow
138
+ inventory. Each page contains at most 1,000 entries (100 by default) and an
139
+ opaque `nextCursor`. Persist the cursor only in the transaction that enrolls the
140
+ whole page; retrying a page must be idempotent. A null cursor completes the pass.
141
+ Do not emulate this API by materializing `list()` when an adapter lacks it.
142
+
143
+ S3 forwards the provider's continuation token and MaxKeys. Local/Bun disks on
144
+ Darwin or glibc Linux use a bounded 32 KiB native directory block plus its record
145
+ position, preserving that cursor across process restarts without rescanning the
146
+ preceding directory. Local cursors bind to directory identity and modification
147
+ times. `DirectoryListingChangedError` means restart that directory from null;
148
+ previously enrolled records remain valid. Concurrent additions are therefore
149
+ visited during the restarted pass. Symlinks and special files are excluded.
150
+ Permission/provider failures throw and must leave the durable cursor unchanged.
151
+ Windows and unsupported libc hosts fail explicitly rather than claiming a
152
+ bounded inventory through an unbounded fallback.
@@ -1,5 +1,5 @@
1
1
  import { Action } from '@stacksjs/actions/runtime'
2
- import { consumePendingTwoFactorSecret, enableTwoFactor } from '@stacksjs/auth'
2
+ import { confirmTwoFactorSetup } from '@stacksjs/auth'
3
3
  import { response } from '@stacksjs/router'
4
4
  import { schema } from '@stacksjs/validation/runtime'
5
5
 
@@ -22,16 +22,12 @@ export default new Action({
22
22
 
23
23
  const code = request.get('code')
24
24
 
25
- // The secret is never taken from the client — see
26
- // GenerateTwoFactorSecretAction / two-factor.ts's doc comment.
27
- const pendingSecret = await consumePendingTwoFactorSecret(user.id as number)
28
- if (!pendingSecret)
29
- return response.unauthorized('No pending setup found — please generate a new QR code and try again.')
25
+ // Confirm the server-owned setup atomically. A mistyped code keeps the QR
26
+ // usable, and recovery codes are returned once after the factor is saved.
27
+ const result = await confirmTwoFactorSetup(user.id as number, code)
28
+ if (!result.enabled)
29
+ return response.json({ error: result.reason === 'missing' ? 'Setup expired. Start setup again.' : result.reason === 'already_enabled' ? 'Two-factor authentication is already enabled.' : 'Invalid code. Check your authenticator app and try again.' }, { status: 422, headers: { 'Cache-Control': 'private, no-store' } })
30
30
 
31
- const enabled = await enableTwoFactor(user.id as number, pendingSecret, code)
32
- if (!enabled)
33
- return response.unauthorized('Invalid code — please check your authenticator app and try again.')
34
-
35
- return response.json({ enabled: true })
31
+ return response.json(result, { headers: { 'Cache-Control': 'private, no-store' } })
36
32
  },
37
33
  })
@@ -1,5 +1,5 @@
1
1
  import { Action } from '@stacksjs/actions/runtime'
2
- import { Auth, authCookieForBrowserSession, resolveBrowserSessionPolicy, verifyTwoFactorLoginCode, withTwoFactorChallenge } from '@stacksjs/auth'
2
+ import { Auth, authCookieForBrowserSession, resolveBrowserSessionPolicy, verifyTwoFactorCredential, withTwoFactorChallenge } from '@stacksjs/auth'
3
3
  import { response } from '@stacksjs/router'
4
4
  import { schema } from '@stacksjs/validation/runtime'
5
5
 
@@ -14,8 +14,8 @@ export default new Action({
14
14
  message: 'A challenge token is required.',
15
15
  },
16
16
  code: {
17
- rule: schema.string().min(6).max(6).required(),
18
- message: 'Code must be a 6-digit TOTP code.',
17
+ rule: schema.string().min(6).max(64).required(),
18
+ message: 'Enter an authenticator code or a recovery code.',
19
19
  },
20
20
  },
21
21
 
@@ -28,7 +28,7 @@ export default new Action({
28
28
  // LoginAction, not retry — mirrors the WebAuthn challenge
29
29
  // delete-on-read semantics in passkey.ts (stacksjs/stacks#1866).
30
30
  const result = await withTwoFactorChallenge(challengeToken, async (userId, challenge) => {
31
- if (!await verifyTwoFactorLoginCode(userId, code)) return null
31
+ if (!await verifyTwoFactorCredential(userId, code)) return null
32
32
  const policy = resolveBrowserSessionPolicy(challenge.remembered)
33
33
  return Auth.loginUsingId(userId, {
34
34
  expiresInMinutes: policy.expiresInMinutes,
@@ -1,12 +1,32 @@
1
1
  import type { ResponseStatus } from '@stacksjs/bun-router'
2
2
  import { response } from '@stacksjs/router'
3
3
 
4
+ /**
5
+ * The feature whose models this error says were never loaded, if it is the
6
+ * ORM's `ModelUnavailableError` for a feature that is off.
7
+ */
8
+ function disabledFeature(error: unknown): string | undefined {
9
+ if (!(error instanceof Error) || error.name !== 'ModelUnavailableError')
10
+ return undefined
11
+ const feature = (error as Error & { feature?: unknown }).feature
12
+ return typeof feature === 'string' && feature ? feature : undefined
13
+ }
14
+
4
15
  export function dashboardOperationalError(
5
16
  error: unknown,
6
17
  message: string,
7
18
  action: string,
8
19
  status: ResponseStatus = 503,
9
20
  ): Response {
21
+ // A page whose feature is off is not an outage, and saying "could not be
22
+ // read" sent people looking at their database (stacksjs/stacks#3184).
23
+ const feature = disabledFeature(error)
24
+ if (feature) {
25
+ console.warn(`[dashboard/api] ${action}: the '${feature}' feature is off`)
26
+ return response.json({
27
+ message: `${feature.charAt(0).toUpperCase()}${feature.slice(1)} is turned off for this app, so this page has no data. Set \`enabled: true\` in config/${feature}.ts to use it.`,
28
+ }, 404)
29
+ }
10
30
  console.error(`[dashboard/api] ${action} failed:`, error)
11
31
  return response.json({ message }, status)
12
32
  }
@@ -0,0 +1,42 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { badgeClasses, badgeClassToggles, badgeDotClasses, badgeDotClassToggles, badgeShowsDot } from './dashboard-badge'
3
+
4
+ describe('dashboard badge classes', () => {
5
+ test('a variant picks its colour', () => {
6
+ expect(badgeClasses('info')).toContain('bg-cyan-500/15 text-cyan-600')
7
+ expect(badgeClasses('success')).toContain('bg-green-500/15 text-green-600')
8
+ expect(badgeClasses('danger', 'sm', 'full', true)).toContain('border-red-500/40 text-red-600')
9
+ expect(badgeDotClasses('warning')).toContain('bg-orange-500')
10
+ })
11
+
12
+ test('an unknown or missing value falls back rather than printing undefined', () => {
13
+ expect(badgeClasses('nope', 'huge')).toBe(badgeClasses())
14
+ expect(badgeClasses(undefined)).toContain('bg-black/[0.06] text-neutral-600')
15
+ expect(badgeDotClasses('nope')).toBe(badgeDotClasses())
16
+ expect(badgeClasses()).not.toContain('undefined')
17
+ })
18
+
19
+ test('a flag that arrived as an attribute string reads as the attribute means', () => {
20
+ expect(badgeClasses('info', 'sm', 'full', 'false')).toBe(badgeClasses('info'))
21
+ expect(badgeClasses('info', 'sm', 'full', 'true')).toBe(badgeClasses('info', 'sm', 'full', true))
22
+ expect(badgeShowsDot('true')).toBe(true)
23
+ expect(badgeShowsDot('')).toBe(true)
24
+ expect(badgeShowsDot('false')).toBe(false)
25
+ expect(badgeShowsDot(undefined)).toBe(false)
26
+ })
27
+
28
+ test('the live toggles turn a grey first paint into exactly the bound variant', () => {
29
+ // What the browser does with an object `:class`: add the true keys, remove
30
+ // the false ones, leave every other class alone.
31
+ const apply = (start: string, toggles: Record<string, boolean>) => {
32
+ const list = new Set(start.split(' '))
33
+ for (const [token, on] of Object.entries(toggles)) on ? list.add(token) : list.delete(token)
34
+ return [...list].sort()
35
+ }
36
+ const sorted = (classes: string) => classes.split(' ').sort()
37
+
38
+ expect(apply(badgeClasses('default'), badgeClassToggles('info'))).toEqual(sorted(badgeClasses('info')))
39
+ expect(apply(badgeClasses('success', 'xs'), badgeClassToggles('danger', 'lg', 'default', 'true'))).toEqual(sorted(badgeClasses('danger', 'lg', 'default', true)))
40
+ expect(apply(badgeDotClasses('default'), badgeDotClassToggles('warning'))).toEqual(sorted(badgeDotClasses('warning')))
41
+ })
42
+ })
@@ -0,0 +1,104 @@
1
+ /**
2
+ * The classes a dashboard `<Badge>` renders.
3
+ *
4
+ * A pure function of the props, shared by the badge's server render (the first
5
+ * paint) and its client script (every later value). The client half is why
6
+ * this is a module: most callers bind `:variant` to data that arrives after
7
+ * hydration, the class computed at the server render never changed, and every
8
+ * such badge stayed the default grey (stacksjs/stacks#3179). A server script's
9
+ * function cannot cross to the client, so both halves import it from here.
10
+ */
11
+
12
+ export type BadgeVariant = 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info'
13
+ export type BadgeSize = 'xs' | 'sm' | 'md' | 'lg'
14
+
15
+ const sizeClasses: Record<string, string> = {
16
+ xs: 'text-[10px] px-1.5 py-0.5',
17
+ sm: 'text-[11px] px-2 py-0.5',
18
+ md: 'text-[12px] px-2.5 py-0.5',
19
+ lg: 'text-[13px] px-3 py-1'
20
+ }
21
+
22
+ const solidVariants: Record<string, string> = {
23
+ default: 'bg-black/[0.06] text-neutral-600 dark:bg-white/10 dark:text-neutral-300',
24
+ primary: 'bg-blue-500/15 text-blue-600 dark:bg-blue-400/20 dark:text-blue-400',
25
+ success: 'bg-green-500/15 text-green-600 dark:bg-green-400/20 dark:text-green-400',
26
+ warning: 'bg-orange-500/15 text-orange-600 dark:bg-orange-400/20 dark:text-orange-400',
27
+ danger: 'bg-red-500/15 text-red-600 dark:bg-red-400/20 dark:text-red-400',
28
+ info: 'bg-cyan-500/15 text-cyan-600 dark:bg-cyan-400/20 dark:text-cyan-400'
29
+ }
30
+
31
+ const outlineVariants: Record<string, string> = {
32
+ default: 'border border-black/15 text-neutral-600 dark:border-white/20 dark:text-neutral-300',
33
+ primary: 'border border-blue-500/40 text-blue-600 dark:border-blue-400/50 dark:text-blue-400',
34
+ success: 'border border-green-500/40 text-green-600 dark:border-green-400/50 dark:text-green-400',
35
+ warning: 'border border-orange-500/40 text-orange-600 dark:border-orange-400/50 dark:text-orange-400',
36
+ danger: 'border border-red-500/40 text-red-600 dark:border-red-400/50 dark:text-red-400',
37
+ info: 'border border-cyan-500/40 text-cyan-600 dark:border-cyan-400/50 dark:text-cyan-400'
38
+ }
39
+
40
+ const dotColors: Record<string, string> = {
41
+ default: 'bg-neutral-400 dark:bg-neutral-500',
42
+ primary: 'bg-blue-500 dark:bg-blue-400',
43
+ success: 'bg-green-500 dark:bg-green-400',
44
+ warning: 'bg-orange-500 dark:bg-orange-400',
45
+ danger: 'bg-red-500 dark:bg-red-400',
46
+ info: 'bg-cyan-500 dark:bg-cyan-400'
47
+ }
48
+
49
+ /**
50
+ * A prop that arrived as an attribute is a string, so `outline="false"` must
51
+ * not read as on.
52
+ */
53
+ function flag(value: unknown): boolean {
54
+ return value === true || value === 'true' || value === ''
55
+ }
56
+
57
+ export function badgeClasses(variant: unknown = 'default', size: unknown = 'sm', rounded: unknown = 'full', outline: unknown = false): string {
58
+ return [
59
+ 'inline-flex items-center font-medium tracking-tight',
60
+ 'transition-all duration-150',
61
+ sizeClasses[String(size)] ?? sizeClasses.sm,
62
+ rounded === 'full' ? 'rounded-full' : 'rounded-md',
63
+ (flag(outline) ? outlineVariants : solidVariants)[String(variant)] ?? solidVariants.default,
64
+ ].join(' ')
65
+ }
66
+
67
+ export function badgeDotClasses(variant: unknown = 'default'): string {
68
+ return `w-1.5 h-1.5 rounded-full mr-1.5 ${dotColors[String(variant)] ?? dotColors.default}`
69
+ }
70
+
71
+ export function badgeShowsDot(dot: unknown): boolean {
72
+ return flag(dot)
73
+ }
74
+
75
+ function tokens(...classLists: string[]): string[] {
76
+ return [...new Set(classLists.flatMap(list => list.split(/\s+/)).filter(Boolean))]
77
+ }
78
+
79
+ const switchedTokens = tokens(
80
+ ...Object.values(sizeClasses),
81
+ ...Object.values(solidVariants),
82
+ ...Object.values(outlineVariants),
83
+ 'rounded-full rounded-md',
84
+ )
85
+ const dotTokens = tokens(...Object.values(dotColors))
86
+
87
+ /**
88
+ * The badge's classes as a `:class` object: every class any size, shape or
89
+ * variant can set, each on or off.
90
+ *
91
+ * Not a string. A string `:class` is appended to the classes the element was
92
+ * rendered with, so a badge first painted grey kept the grey classes next to
93
+ * its live ones, and which colour showed depended on stylesheet order. The
94
+ * object form removes what the current props do not want.
95
+ */
96
+ export function badgeClassToggles(variant?: unknown, size?: unknown, rounded?: unknown, outline?: unknown): Record<string, boolean> {
97
+ const wanted = new Set(badgeClasses(variant, size, rounded, outline).split(/\s+/))
98
+ return Object.fromEntries(switchedTokens.map(token => [token, wanted.has(token)]))
99
+ }
100
+
101
+ export function badgeDotClassToggles(variant?: unknown): Record<string, boolean> {
102
+ const wanted = new Set(badgeDotClasses(variant).split(/\s+/))
103
+ return Object.fromEntries(dotTokens.map(token => [token, wanted.has(token)]))
104
+ }
@@ -0,0 +1,33 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { buttonVariantClasses, buttonVariantToggles } from './dashboard-button'
3
+
4
+ // What the browser does with an object `:class`: add the true keys, remove the
5
+ // false ones, leave every other class alone.
6
+ function apply(start: string, toggles: Record<string, boolean>): string[] {
7
+ const list = new Set(start.split(/\s+/).filter(Boolean))
8
+ for (const [token, on] of Object.entries(toggles)) on ? list.add(token) : list.delete(token)
9
+ return [...list].sort()
10
+ }
11
+
12
+ const sorted = (classes: string) => classes.split(/\s+/).filter(Boolean).sort()
13
+
14
+ describe('dashboard button variant toggles', () => {
15
+ test('a bound variant replaces the first paint\'s colour', () => {
16
+ const base = 'inline-flex items-center h-8'
17
+ const painted = `${base} ${buttonVariantClasses.primary}`
18
+ expect(apply(painted, buttonVariantToggles('danger'))).toEqual(sorted(`${base} ${buttonVariantClasses.danger}`))
19
+ })
20
+
21
+ test('the caller\'s own classes survive even when a variant uses them too', () => {
22
+ const caller = 'border shadow-sm w-full'
23
+ const painted = `${caller} ${buttonVariantClasses.secondary}`
24
+ const result = apply(painted, buttonVariantToggles('primary', caller))
25
+ for (const token of caller.split(' ')) expect(result).toContain(token)
26
+ expect(result).not.toContain('bg-white/80')
27
+ })
28
+
29
+ test('bare keeps nothing of a variant and an unknown one falls back to primary', () => {
30
+ expect(apply(buttonVariantClasses.success, buttonVariantToggles('bare'))).toEqual([])
31
+ expect(apply('', buttonVariantToggles('nope'))).toEqual(sorted(buttonVariantClasses.primary))
32
+ })
33
+ })
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The colour classes of a dashboard `<Button>` variant.
3
+ *
4
+ * Shared by the button's server render and its client script. The client half
5
+ * exists for callers that bind `:variant` to client state - the migration
6
+ * plan's confirm button turns `danger` when the plan is destructive - which
7
+ * the server render never sees, so the button kept the variant it was first
8
+ * painted with (stacksjs/stacks#3180).
9
+ */
10
+
11
+ export const buttonVariantClasses: Record<string, string> = {
12
+ primary: 'bg-gradient-to-b from-blue-500 to-blue-600 text-white shadow-sm shadow-blue-600/25 hover:from-blue-400 hover:to-blue-500 active:from-blue-600 active:to-blue-700 dark:from-blue-600 dark:to-blue-700 dark:hover:from-blue-500 dark:hover:to-blue-600',
13
+ secondary: 'bg-white/80 dark:bg-white/10 text-neutral-800 dark:text-neutral-100 border border-black/10 dark:border-white/10 shadow-sm hover:bg-white dark:hover:bg-white/15 active:bg-neutral-100 dark:active:bg-white/5',
14
+ outline: 'bg-transparent text-neutral-700 dark:text-neutral-200 border border-black/15 dark:border-white/15 hover:bg-black/5 dark:hover:bg-white/8 active:bg-black/10 dark:active:bg-white/10',
15
+ ghost: 'bg-transparent text-neutral-600 dark:text-neutral-300 hover:bg-black/5 dark:hover:bg-white/8 active:bg-black/10 dark:active:bg-white/12',
16
+ danger: 'bg-gradient-to-b from-red-500 to-red-600 text-white shadow-sm shadow-red-600/25 hover:from-red-400 hover:to-red-500 active:from-red-600 active:to-red-700',
17
+ success: 'bg-gradient-to-b from-green-500 to-green-600 text-white shadow-sm shadow-green-600/25 hover:from-green-400 hover:to-green-500 active:from-green-600 active:to-green-700',
18
+ warning: 'bg-gradient-to-b from-orange-500 to-orange-600 text-white shadow-sm shadow-orange-600/25 hover:from-orange-400 hover:to-orange-500 active:from-orange-600 active:to-orange-700',
19
+ /**
20
+ * No chrome at all: the caller supplies every visual through `class`.
21
+ *
22
+ * For an affordance that has to sit inside someone else's layout and carry
23
+ * no box of its own - a sortable table header fills its `<th>`, and the
24
+ * padding and fixed height the sizes impose would change the header row.
25
+ * What it still gets, and the reason to come through this component rather
26
+ * than write a raw `<button>`, is the shared focus-visible ring, the
27
+ * disabled and aria-disabled handling, and the press feedback.
28
+ * See stacksjs/stacks#2926.
29
+ */
30
+ bare: ''
31
+ }
32
+
33
+ /**
34
+ * The variant's classes as a `:class` object: every class any variant sets,
35
+ * each on or off, so a live change removes the first paint's colour instead
36
+ * of stacking on it. Classes the caller passed in `class` are left alone even
37
+ * when a variant also uses them.
38
+ */
39
+ export function buttonVariantToggles(variant: unknown, callerClass: unknown = ''): Record<string, boolean> {
40
+ const own = new Set(String(callerClass || '').split(/\s+/).filter(Boolean))
41
+ const wanted = new Set((buttonVariantClasses[String(variant)] ?? buttonVariantClasses.primary).split(/\s+/).filter(Boolean))
42
+ const all = new Set(Object.values(buttonVariantClasses).flatMap(list => list.split(/\s+/)).filter(Boolean))
43
+ return Object.fromEntries([...all].filter(token => !own.has(token)).map(token => [token, wanted.has(token)]))
44
+ }
@@ -2,7 +2,7 @@
2
2
  "publisher": "Stacks",
3
3
  "name": "vscode-stacks",
4
4
  "displayName": "Stacks",
5
- "version": "0.75.468",
5
+ "version": "0.75.475",
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.468",
5
+ "version": "0.75.475",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/stacksjs/stacks.git",
@@ -45,9 +45,9 @@
45
45
  "dependencies": {
46
46
  "@iconify-json/f7": "^1.2.2",
47
47
  "@iconify-json/hugeicons": "^1.2.35",
48
- "@stacksjs/mobile": "0.75.468",
49
- "@stacksjs/sanitizer": "0.2.798",
50
- "@stacksjs/stx": "0.2.798",
48
+ "@stacksjs/mobile": "0.75.475",
49
+ "@stacksjs/sanitizer": "0.2.800",
50
+ "@stacksjs/stx": "0.2.800",
51
51
  "@xterm/addon-fit": "^0.11.0",
52
52
  "@xterm/xterm": "^6.0.0",
53
53
  "ts-qr-codes": "^0.1.8"
@@ -1,3 +1,12 @@
1
+ <script server>
2
+ import { loadDiscoveredManifest } from '@stacksjs/defaults/resources/functions/dashboard/sidebar'
3
+
4
+ // The Commerce card follows the sidebar's commerce section: with the feature
5
+ // off its models are never loaded, so the page it opens could only answer
6
+ // 503 (stacksjs/stacks#3183).
7
+ export const commerceAnalytics = loadDiscoveredManifest().sections.commerce
8
+ </script>
9
+
1
10
  <script client>
2
11
  import type { AnalyticsOverview } from '../../../../functions/analytics'
3
12
  import { fetchWebAnalytics } from '../../../../functions/analytics'
@@ -17,7 +26,7 @@ const analyticsCards = [
17
26
  { name: 'Countries', description: 'Geographic data when provided', icon: 'i-hugeicons-globe-02', to: '/analytics/countries' },
18
27
  { name: 'Devices', description: 'Device classes from user agents', icon: 'i-hugeicons-smart-phone-01', to: '/analytics/devices' },
19
28
  { name: 'Browsers', description: 'Browser families from user agents', icon: 'i-hugeicons-browser', to: '/analytics/browsers' },
20
- ]
29
+ ].filter(card => commerceAnalytics || !card.to.startsWith('/analytics/commerce/'))
21
30
 
22
31
  function formatNumber(value: number | undefined): string {
23
32
  return new Intl.NumberFormat().format(value || 0)
@@ -239,7 +239,7 @@ onMount(() => {
239
239
  </div>
240
240
  <label class="block relative w-full sm:w-72">
241
241
  <span class="sr-only">Search {{ page().title.toLowerCase() }}</span>
242
- <span class="flex absolute inset-y-0 left-0 items-center pl-3 text-neutral-400 i-hugeicons-search-01"></span>
242
+ <span aria-hidden="true" class="absolute left-3 top-1/2 h-4 w-4 text-neutral-400 pointer-events-none -translate-y-1/2 i-hugeicons-search-01"></span>
243
243
  <input x-model="search" type="search" :placeholder="'Search ' + page().title.toLowerCase()" class="block pl-9 pr-3 py-2 w-full text-neutral-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-neutral-300 rounded-lg dark:border-neutral-700 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20">
244
244
  </label>
245
245
  </div>
@@ -41,7 +41,7 @@ function listClass(): string {
41
41
  <div class="px-4 py-2 bg-white dark:bg-neutral-700 border-b border-gray-200 dark:border-neutral-600">
42
42
  <label for="inbox-search" class="sr-only">Search messages</label>
43
43
  <div class="relative">
44
- <span class="flex absolute inset-y-0 left-0 items-center pl-3 text-gray-400 pointer-events-none i-hugeicons-search-01"></span>
44
+ <span aria-hidden="true" class="absolute left-3 top-1/2 h-4 w-4 text-gray-400 pointer-events-none -translate-y-1/2 i-hugeicons-search-01"></span>
45
45
  <input id="inbox-search" x-model="search" type="search" placeholder="Search messages" class="block placeholder-gray-500 dark:placeholder-gray-400 pl-10 pr-3 py-2 w-full text-gray-900 text-sm dark:text-neutral-100 bg-gray-50 dark:bg-neutral-600 border border-gray-300 rounded-md dark:border-neutral-600 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" @input="emit('search', $event.target.value)" />
46
46
  </div>
47
47
  </div>
@@ -57,9 +57,12 @@ function handlePanelClick(event: MouseEvent): void {
57
57
  closeSidebar()
58
58
  }
59
59
 
60
+ // Widening past the breakpoint closes the drawer the same way a tap does, so
61
+ // the scroll lock is released and the layout gets its `close` to take its
62
+ // sidebar back. Setting the state alone did neither.
60
63
  effect(() => {
61
64
  if (isDesktop() && isOpen())
62
- isOpen.set(false)
65
+ closeSidebar()
63
66
  })
64
67
 
65
68
  useEventListener('keydown', handleKeydown)
@@ -141,7 +141,7 @@ onMount(() => {
141
141
  <div class="flex flex-col gap-2 sm:flex-row w-full sm:w-auto">
142
142
  <label class="block relative w-full sm:w-64">
143
143
  <span class="sr-only">Search models</span>
144
- <span class="flex absolute inset-y-0 left-0 items-center pl-3 text-neutral-400 i-hugeicons-search-01"></span>
144
+ <span aria-hidden="true" class="absolute left-3 top-1/2 h-4 w-4 text-neutral-400 pointer-events-none -translate-y-1/2 i-hugeicons-search-01"></span>
145
145
  <input x-model="search" type="search" placeholder="Search models" class="block pl-9 pr-3 py-2 w-full text-neutral-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-neutral-300 rounded-lg dark:border-neutral-700 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20">
146
146
  </label>
147
147
  <label>
@@ -1,4 +1,6 @@
1
1
  <script server>
2
+ import { badgeClasses, badgeDotClasses } from '../../../../functions/dashboard-badge'
3
+
2
4
  interface BadgeProps {
3
5
  variant?: 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info'
4
6
  size?: 'xs' | 'sm' | 'md' | 'lg'
@@ -14,53 +16,28 @@ const { variant = 'default', size = 'sm', rounded = 'full', outline = false, dot
14
16
  // `{{ text }}` rendered the validator function source into every Badge.
15
17
  // Use the slot for the badge label instead.
16
18
 
17
- const sizeClasses = {
18
- xs: 'text-[10px] px-1.5 py-0.5',
19
- sm: 'text-[11px] px-2 py-0.5',
20
- md: 'text-[12px] px-2.5 py-0.5',
21
- lg: 'text-[13px] px-3 py-1'
22
- }
23
-
24
- const solidVariants = {
25
- default: 'bg-black/[0.06] text-neutral-600 dark:bg-white/10 dark:text-neutral-300',
26
- primary: 'bg-blue-500/15 text-blue-600 dark:bg-blue-400/20 dark:text-blue-400',
27
- success: 'bg-green-500/15 text-green-600 dark:bg-green-400/20 dark:text-green-400',
28
- warning: 'bg-orange-500/15 text-orange-600 dark:bg-orange-400/20 dark:text-orange-400',
29
- danger: 'bg-red-500/15 text-red-600 dark:bg-red-400/20 dark:text-red-400',
30
- info: 'bg-cyan-500/15 text-cyan-600 dark:bg-cyan-400/20 dark:text-cyan-400'
31
- }
32
-
33
- const outlineVariants = {
34
- default: 'border border-black/15 text-neutral-600 dark:border-white/20 dark:text-neutral-300',
35
- primary: 'border border-blue-500/40 text-blue-600 dark:border-blue-400/50 dark:text-blue-400',
36
- success: 'border border-green-500/40 text-green-600 dark:border-green-400/50 dark:text-green-400',
37
- warning: 'border border-orange-500/40 text-orange-600 dark:border-orange-400/50 dark:text-orange-400',
38
- danger: 'border border-red-500/40 text-red-600 dark:border-red-400/50 dark:text-red-400',
39
- info: 'border border-cyan-500/40 text-cyan-600 dark:border-cyan-400/50 dark:text-cyan-400'
40
- }
41
-
42
- const dotColors = {
43
- default: 'bg-neutral-400 dark:bg-neutral-500',
44
- primary: 'bg-blue-500 dark:bg-blue-400',
45
- success: 'bg-green-500 dark:bg-green-400',
46
- warning: 'bg-orange-500 dark:bg-orange-400',
47
- danger: 'bg-red-500 dark:bg-red-400',
48
- info: 'bg-cyan-500 dark:bg-cyan-400'
49
- }
50
-
51
- const badgeClass = [
52
- 'inline-flex items-center font-medium tracking-tight',
53
- 'transition-all duration-150',
54
- sizeClasses[size],
55
- rounded === 'full' ? 'rounded-full' : 'rounded-md',
56
- outline ? outlineVariants[variant] : solidVariants[variant]
57
- ].join(' ')
19
+ // The first paint. The client script keeps both in step afterwards.
20
+ const badgeClass = badgeClasses(variant, size, rounded, outline)
21
+ const dotClass = badgeDotClasses(variant)
22
+ </script>
23
+ <script client>
24
+ import { badgeClassToggles, badgeDotClassToggles, badgeShowsDot } from '../../../../functions/dashboard-badge'
58
25
 
59
- const dotClass = 'w-1.5 h-1.5 rounded-full mr-1.5 ' + dotColors[variant]
26
+ // Read live as well: callers bind `:variant` to data that arrives after
27
+ // hydration, and the value the server render saw never changed
28
+ // (stacksjs/stacks#3179).
29
+ const liveVariant = useReactiveProp('variant', 'default')
30
+ const liveSize = useReactiveProp('size', 'sm')
31
+ const liveRounded = useReactiveProp('rounded', 'full')
32
+ const liveOutline = useReactiveProp('outline', false)
33
+ const liveDot = useReactiveProp('dot', false)
34
+ const liveBadgeClass = derived(() => badgeClassToggles(liveVariant(), liveSize(), liveRounded(), liveOutline()))
35
+ const liveDotClass = derived(() => badgeDotClassToggles(liveVariant()))
36
+ const showDot = derived(() => badgeShowsDot(liveDot()))
60
37
  </script>
61
- <span class="{{ badgeClass }}">
38
+ <span class="{{ badgeClass }}" :class="liveBadgeClass()">
62
39
  @if(dot)
63
- <span class="{{ dotClass }}"></span>
40
+ <span class="{{ dotClass }}" :class="liveDotClass()" :if="showDot()"></span>
64
41
  @endif
65
42
  <slot />
66
43
  </span>
@@ -1,4 +1,6 @@
1
1
  <script server>
2
+ import { buttonVariantClasses } from '../../../../functions/dashboard-button'
3
+
2
4
  interface ButtonProps {
3
5
  variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger' | 'success' | 'warning' | 'bare'
4
6
  size?: 'xs' | 'sm' | 'md' | 'lg'
@@ -32,29 +34,6 @@ const {
32
34
  href = '',
33
35
  } = defineProps<ButtonProps>()
34
36
 
35
- // Variant classes
36
- const variantClasses = {
37
- primary: 'bg-gradient-to-b from-blue-500 to-blue-600 text-white shadow-sm shadow-blue-600/25 hover:from-blue-400 hover:to-blue-500 active:from-blue-600 active:to-blue-700 dark:from-blue-600 dark:to-blue-700 dark:hover:from-blue-500 dark:hover:to-blue-600',
38
- secondary: 'bg-white/80 dark:bg-white/10 text-neutral-800 dark:text-neutral-100 border border-black/10 dark:border-white/10 shadow-sm hover:bg-white dark:hover:bg-white/15 active:bg-neutral-100 dark:active:bg-white/5',
39
- outline: 'bg-transparent text-neutral-700 dark:text-neutral-200 border border-black/15 dark:border-white/15 hover:bg-black/5 dark:hover:bg-white/8 active:bg-black/10 dark:active:bg-white/10',
40
- ghost: 'bg-transparent text-neutral-600 dark:text-neutral-300 hover:bg-black/5 dark:hover:bg-white/8 active:bg-black/10 dark:active:bg-white/12',
41
- danger: 'bg-gradient-to-b from-red-500 to-red-600 text-white shadow-sm shadow-red-600/25 hover:from-red-400 hover:to-red-500 active:from-red-600 active:to-red-700',
42
- success: 'bg-gradient-to-b from-green-500 to-green-600 text-white shadow-sm shadow-green-600/25 hover:from-green-400 hover:to-green-500 active:from-green-600 active:to-green-700',
43
- warning: 'bg-gradient-to-b from-orange-500 to-orange-600 text-white shadow-sm shadow-orange-600/25 hover:from-orange-400 hover:to-orange-500 active:from-orange-600 active:to-orange-700',
44
- /**
45
- * No chrome at all: the caller supplies every visual through `class`.
46
- *
47
- * For an affordance that has to sit inside someone else's layout and carry
48
- * no box of its own - a sortable table header fills its `<th>`, and the
49
- * padding and fixed height the sizes impose would change the header row.
50
- * What it still gets, and the reason to come through this component rather
51
- * than write a raw `<button>`, is the shared focus-visible ring, the
52
- * disabled and aria-disabled handling, and the press feedback.
53
- * See stacksjs/stacks#2926.
54
- */
55
- bare: ''
56
- }
57
-
58
37
  // Size classes
59
38
  const sizeClasses = {
60
39
  xs: 'text-[11px] px-2 py-1 rounded-md gap-1 h-6',
@@ -85,10 +64,17 @@ const buttonClass = [
85
64
  fullWidth ? 'w-full' : '',
86
65
  // `bare` takes neither: its whole point is to impose no box.
87
66
  variant === 'bare' ? '' : (iconOnly ? iconOnlySizes[size] : sizeClasses[size]),
88
- variantClasses[variant]
67
+ buttonVariantClasses[variant]
89
68
  ].filter(Boolean).join(' ')
90
69
  </script>
91
70
  <script client>
71
+ import { buttonVariantToggles } from '../../../../functions/dashboard-button'
72
+
73
+ // A bound `:variant` changes after the server render; the toggles move the
74
+ // colour with it (stacksjs/stacks#3180).
75
+ const liveVariant = useReactiveProp('variant', 'primary')
76
+ const liveCallerClass = useReactiveProp('class', '')
77
+ const variantToggles = derived(() => buttonVariantToggles(liveVariant(), liveCallerClass()))
92
78
  const liveDisabled = useReactiveProp('disabled', false)
93
79
  const liveLoading = useReactiveProp('loading', false)
94
80
  const livePressed = useReactiveProp('pressed', false)
@@ -117,6 +103,7 @@ const liveAriaExpanded = useReactiveProp('ariaExpanded', '')
117
103
  :download="liveDownload() || undefined"
118
104
  :aria-label="liveAriaLabel() || undefined"
119
105
  class="{{ buttonClass }}"
106
+ :class="variantToggles()"
120
107
  :target="liveTarget() || undefined"
121
108
  :rel="liveRel() || undefined"
122
109
  :aria-busy="liveLoading() ? 'true' : 'false'"
@@ -134,6 +121,7 @@ const liveAriaExpanded = useReactiveProp('ariaExpanded', '')
134
121
  :type="liveType()"
135
122
  :form="liveForm() || undefined"
136
123
  class="{{ buttonClass }}"
124
+ :class="variantToggles()"
137
125
  :aria-label="liveAriaLabel() || undefined"
138
126
  :aria-busy="liveLoading() ? 'true' : 'false'"
139
127
  :aria-pressed="String(livePressed())"
@@ -149,6 +137,7 @@ const liveAriaExpanded = useReactiveProp('ariaExpanded', '')
149
137
  :type="liveType()"
150
138
  :form="liveForm() || undefined"
151
139
  class="{{ buttonClass }}"
140
+ :class="variantToggles()"
152
141
  :aria-label="liveAriaLabel() || undefined"
153
142
  :aria-haspopup="liveAriaHasPopup() || undefined"
154
143
  :aria-expanded="liveAriaExpanded() || undefined"
@@ -56,6 +56,12 @@ const cardClass = [
56
56
  clickableClass,
57
57
  ].join(' ')
58
58
  </script>
59
+ <script client>
60
+ // A bound `:subtitle` is empty when the card renders on the server, so the
61
+ // line above never rendered at all; this one follows the value once it
62
+ // arrives (stacksjs/stacks#3180). A static subtitle still paints first.
63
+ const liveSubtitle = useReactiveProp('subtitle', '')
64
+ </script>
59
65
 
60
66
  <article class="{{ cardClass }}" @if(clickable) role="button" tabindex="0" @endif>
61
67
  @if($slots.header)
@@ -70,6 +76,8 @@ const cardClass = [
70
76
  @endif
71
77
  @if(subtitle)
72
78
  <p class="mt-1 text-neutral-500 text-sm dark:text-neutral-400">{{ subtitle }}</p>
79
+ @else
80
+ <p :if="liveSubtitle()" class="mt-1 text-neutral-500 text-sm dark:text-neutral-400">{{ liveSubtitle() }}</p>
73
81
  @endif
74
82
  </div>
75
83
  @if($slots.actions)
@@ -471,7 +471,9 @@ export function buildNavSections(
471
471
  { to: '/marketing/social-posts', icon: 'clock', text: 'Social Posts' },
472
472
  { to: '/marketing/campaigns', icon: 'rocket', text: 'Campaigns' },
473
473
  { to: '/marketing/abandoned-carts', icon: 'cart', text: 'Abandoned Carts' },
474
- { to: '/marketing/reviews', icon: 'star', text: 'Reviews' },
474
+ // Product reviews are commerce records, so with commerce off this page
475
+ // could only say so (stacksjs/stacks#3186).
476
+ ...(toggles.commerce ? [{ to: '/marketing/reviews', icon: 'star', text: 'Reviews' }] : []),
475
477
  ...categoryNavItems(discoveredModels, 'marketing', new Set(['campaign'])),
476
478
  ]])
477
479
  }
@@ -491,8 +493,15 @@ export function buildNavSections(
491
493
  { to: '/analytics/browsers', icon: 'globe', text: 'Browsers' },
492
494
  { to: '/analytics/events', icon: 'bolt', text: 'Events' },
493
495
  { to: '/analytics/blog', icon: 'document', text: 'Blog' },
494
- { to: '/analytics/commerce/web', icon: 'cart', text: 'Commerce' },
495
- { to: '/analytics/commerce/sales', icon: 'sale-tag', text: 'Sales' },
496
+ // Commerce's analytics follow the commerce section. With the feature
497
+ // off its models are never loaded, so Sales could only answer 503
498
+ // (stacksjs/stacks#3183).
499
+ ...(toggles.commerce
500
+ ? [
501
+ { to: '/analytics/commerce/web', icon: 'cart', text: 'Commerce' },
502
+ { to: '/analytics/commerce/sales', icon: 'sale-tag', text: 'Sales' },
503
+ ]
504
+ : []),
496
505
  { to: '/analytics/marketing', icon: 'megaphone', text: 'Marketing' },
497
506
  ]])
498
507
  }
@@ -129,6 +129,20 @@ body {
129
129
  left: var(--stx-sidebar-width, 250px);
130
130
  transition: left 0.3s ease-out;
131
131
  }
132
+ /*
133
+ * The window's sidebar pane while it is in the mobile drawer: it fills the
134
+ * drawer instead of sitting fixed at its window width, and gives up the
135
+ * window's z-index so the drawer's close button stays on top of it.
136
+ */
137
+ [data-dashboard-mobile-sidebar] > [data-stx-component-wrapper] {
138
+ height: 100%;
139
+ }
140
+ [data-dashboard-mobile-sidebar] [data-stx-sidebar] {
141
+ position: relative;
142
+ z-index: auto;
143
+ width: 100% !important;
144
+ height: 100%;
145
+ }
132
146
  @media (max-width: 1023px) {
133
147
  [data-stx-content] {
134
148
  margin-left: 0;
@@ -589,11 +603,34 @@ function handleSidebarItemClick(payload: { item?: { href?: string }, event?: { p
589
603
  navigate(href)
590
604
  }
591
605
 
592
- function syncMobileSidebar() {
606
+ // Where the Sidebar sat in the window while it is lent to the drawer.
607
+ let lentSidebar: { element: HTMLElement, parent: Node, next: Node | null } | null = null
608
+
609
+ // The Sidebar's component wrapper moves, not just its pane: the wrapper is
610
+ // where the layout's `@itemClick` listens, so a pane moved without it kept
611
+ // selecting rows but never navigated.
612
+ function showSidebarInDrawer() {
613
+ const pane = sidebarHost.current?.querySelector<HTMLElement>('[data-stx-sidebar]')
614
+ const element = pane?.closest<HTMLElement>('[data-stx-component-wrapper]') ?? pane
615
+ const drawer = mobileSidebarHost.current
616
+ if (element?.parentNode && drawer && !lentSidebar) {
617
+ lentSidebar = { element, parent: element.parentNode, next: element.nextSibling }
618
+ drawer.appendChild(element)
619
+ }
593
620
  applyRoleFilter()
594
621
  syncActiveSidebarRow()
595
622
  }
596
623
 
624
+ // Back where it came from, before anything else looks for it. The drawer's
625
+ // markup is already gone by now, so the Sidebar is put back from the
626
+ // reference kept above rather than looked up.
627
+ function returnSidebarFromDrawer() {
628
+ if (!lentSidebar) return
629
+ const { element, parent, next } = lentSidebar
630
+ lentSidebar = null
631
+ parent.insertBefore(element, next && next.parentNode === parent ? next : null)
632
+ }
633
+
597
634
  function handleCraftSidebarSelection(event: CraftSidebarSelectEvent | undefined): void {
598
635
  if (!event?.itemId) return
599
636
  for (const pane of sidebarPanes()) {
@@ -658,23 +695,11 @@ onDestroy(() => {
658
695
  </Sidebar>
659
696
  </div>
660
697
 
661
- <MobileSidebar @open="syncMobileSidebar()">
662
- <div ref="dashboardMobileSidebarHost" class="h-full">
663
- <Sidebar
664
- :sections="sidebarSections"
665
- theme="macos"
666
- width="320"
667
- placement="static"
668
- className="h-full max-w-full"
669
- @itemClick="handleSidebarItemClick($event)"
670
- >
671
- <template #header>
672
- <!-- A drawer, not the window edge: no window buttons land on it, so
673
- there is nothing to draw and nothing to reserve. -->
674
- <SidebarHeader windowControls="none" />
675
- </template>
676
- </Sidebar>
677
- </div>
698
+ <!-- The drawer holds the same Sidebar as the window, moved in while it is open
699
+ (`showSidebarInDrawer`). A second Sidebar here rendered all its rows again
700
+ on every page and doubled each page's first render (stacksjs/stacks#3177). -->
701
+ <MobileSidebar @open="showSidebarInDrawer()" @close="returnSidebarFromDrawer()">
702
+ <div ref="dashboardMobileSidebarHost" data-dashboard-mobile-sidebar class="h-full"></div>
678
703
  </MobileSidebar>
679
704
 
680
705
  <main data-stx-content>