@cosmicdrift/kumiko-samples 0.361.0 → 0.363.0

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.
Files changed (265) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +17 -6
  3. package/packages/bundled-features/src/admin-shell/changes.json +6 -0
  4. package/packages/bundled-features/src/admin-shell/feature.ts +8 -1
  5. package/packages/bundled-features/src/agent-tools/agent-manifest.ts +3 -4
  6. package/packages/bundled-features/src/agent-tools/filter-ops.ts +1 -3
  7. package/packages/bundled-features/src/agent-tools/tool-catalog.ts +1 -3
  8. package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +2 -1
  9. package/packages/bundled-features/src/auth-email-password/changes.json +14 -0
  10. package/packages/bundled-features/src/auth-email-password/email-templates.ts +12 -138
  11. package/packages/bundled-features/src/auth-email-password/feature.ts +30 -9
  12. package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +4 -5
  13. package/packages/bundled-features/src/auth-email-password/handlers/request-account-unlock.write.ts +2 -5
  14. package/packages/bundled-features/src/auth-email-password/handlers/request-email-verification.write.ts +2 -5
  15. package/packages/bundled-features/src/auth-email-password/handlers/request-password-reset.write.ts +2 -5
  16. package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +3 -4
  17. package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +3 -12
  18. package/packages/bundled-features/src/auth-email-password/index.ts +2 -17
  19. package/packages/bundled-features/src/auth-email-password/magic-link-mail.ts +30 -22
  20. package/packages/bundled-features/src/auth-email-password/mail-notifications.ts +158 -0
  21. package/packages/bundled-features/src/auth-email-password/signup-activation.ts +2 -3
  22. package/packages/bundled-features/src/auth-foundation/types.ts +8 -18
  23. package/packages/bundled-features/src/auth-mfa/feature.ts +8 -10
  24. package/packages/bundled-features/src/auth-mfa/hmac-token-codec.ts +4 -4
  25. package/packages/bundled-features/src/auth-mfa/mfa-challenge-token.ts +3 -3
  26. package/packages/bundled-features/src/auth-mfa/mfa-preauth-setup-token.ts +3 -3
  27. package/packages/bundled-features/src/auth-mfa/mfa-setup-token.ts +8 -8
  28. package/packages/bundled-features/src/billing-foundation/changes.json +7 -0
  29. package/packages/bundled-features/src/billing-foundation/constants.ts +5 -4
  30. package/packages/bundled-features/src/billing-foundation/consumer-protection/confirmation-mail.ts +38 -44
  31. package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +3 -3
  32. package/packages/bundled-features/src/billing-foundation/consumer-protection/mail-notifications.ts +115 -0
  33. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +2 -0
  34. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-mail.ts +30 -30
  35. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-notify.ts +2 -2
  36. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-pages.ts +1 -4
  37. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +2 -2
  38. package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +2 -2
  39. package/packages/bundled-features/src/billing-foundation/handlers/create-portal-session.write.ts +4 -9
  40. package/packages/bundled-features/src/billing-foundation/handlers/list-subscriptions.query.ts +1 -1
  41. package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +2 -2
  42. package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +1 -1
  43. package/packages/bundled-features/src/billing-foundation/subscription-tier-sync.ts +3 -6
  44. package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +2 -1
  45. package/packages/bundled-features/src/cap-overview/web/cap-usage-cell.tsx +6 -4
  46. package/packages/bundled-features/src/channel-email/changes.json +19 -0
  47. package/packages/bundled-features/src/channel-email/email-channel.ts +122 -29
  48. package/packages/bundled-features/src/channel-email/feature.ts +6 -3
  49. package/packages/bundled-features/src/channel-email/index.ts +7 -1
  50. package/packages/bundled-features/src/channel-email/pii-guard.ts +13 -0
  51. package/packages/bundled-features/src/channel-email/smtp-transport.ts +15 -1
  52. package/packages/bundled-features/src/channel-email/types.ts +23 -0
  53. package/packages/bundled-features/src/channel-in-app/handlers/mark-read.write.ts +2 -2
  54. package/packages/bundled-features/src/config/handlers/set.write.ts +1 -5
  55. package/packages/bundled-features/src/custom-fields/db/queries/user-data-rights.ts +2 -2
  56. package/packages/bundled-features/src/custom-fields/lib/value-schema.ts +10 -11
  57. package/packages/bundled-features/src/custom-fields/wire-user-data-rights.ts +7 -6
  58. package/packages/bundled-features/src/data-retention/changes.json +6 -0
  59. package/packages/bundled-features/src/data-retention/index.ts +1 -0
  60. package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +8 -6
  61. package/packages/bundled-features/src/delivery/attachment-ref.ts +84 -0
  62. package/packages/bundled-features/src/delivery/changes.json +48 -0
  63. package/packages/bundled-features/src/delivery/channel-context.ts +129 -4
  64. package/packages/bundled-features/src/delivery/constants.ts +2 -0
  65. package/packages/bundled-features/src/delivery/delivery-service.ts +104 -26
  66. package/packages/bundled-features/src/delivery/feature.ts +1 -1
  67. package/packages/bundled-features/src/delivery/index.ts +17 -2
  68. package/packages/bundled-features/src/delivery/jobs.ts +48 -5
  69. package/packages/bundled-features/src/delivery/notify-factory.ts +14 -9
  70. package/packages/bundled-features/src/delivery/testing.ts +9 -1
  71. package/packages/bundled-features/src/delivery/types.ts +28 -18
  72. package/packages/bundled-features/src/delivery/unsubscribe.ts +5 -2
  73. package/packages/bundled-features/src/derivatives-sharp/render.ts +1 -22
  74. package/packages/bundled-features/src/document-ingest-foundation/aggregate-id.ts +10 -0
  75. package/packages/bundled-features/src/document-ingest-foundation/changes.json +6 -0
  76. package/packages/bundled-features/src/document-ingest-foundation/events.ts +3 -1
  77. package/packages/bundled-features/src/document-ingest-foundation/feature.ts +5 -3
  78. package/packages/bundled-features/src/document-ingest-foundation/index.ts +1 -0
  79. package/packages/bundled-features/src/document-ingest-foundation/pages.ts +6 -3
  80. package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +4 -4
  81. package/packages/bundled-features/src/file-foundation/index.ts +7 -0
  82. package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +86 -0
  83. package/packages/bundled-features/src/folders/web/folder-manager.tsx +5 -2
  84. package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +11 -11
  85. package/packages/bundled-features/src/form-draft/db/queries/draft-count.ts +2 -2
  86. package/packages/bundled-features/src/form-draft/db/queries/owned-file-refs.ts +2 -2
  87. package/packages/bundled-features/src/form-draft/release-file-refs.ts +3 -2
  88. package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +1 -1
  89. package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +6 -6
  90. package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-accounts.query.ts +1 -1
  91. package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-messages.query.ts +1 -1
  92. package/packages/bundled-features/src/inbound-mail-foundation/handlers/scope-visibility.ts +2 -2
  93. package/packages/bundled-features/src/inbound-mail-foundation/oauth-state.ts +7 -7
  94. package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +2 -6
  95. package/packages/bundled-features/src/inbound-mail-foundation/tenant-destroy-hook.ts +5 -13
  96. package/packages/bundled-features/src/inbound-mail-foundation/types.ts +4 -6
  97. package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +40 -25
  98. package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +4 -3
  99. package/packages/bundled-features/src/jobs/handlers/catalog.query.ts +1 -1
  100. package/packages/bundled-features/src/ledger/recurring.ts +1 -3
  101. package/packages/bundled-features/src/managed-pages/branding.ts +33 -9
  102. package/packages/bundled-features/src/managed-pages/changes.json +6 -0
  103. package/packages/bundled-features/src/managed-pages/feature.ts +1 -0
  104. package/packages/bundled-features/src/managed-pages/i18n.ts +1 -0
  105. package/packages/bundled-features/src/managed-pages/screens/branding-screen.ts +8 -0
  106. package/packages/bundled-features/src/page-render/branding.ts +3 -0
  107. package/packages/bundled-features/src/page-render/markdown.ts +1 -1
  108. package/packages/bundled-features/src/personal-access-tokens/feature.ts +2 -4
  109. package/packages/bundled-features/src/personal-access-tokens/handlers/list.query.ts +2 -1
  110. package/packages/bundled-features/src/personal-access-tokens/index.ts +1 -1
  111. package/packages/bundled-features/src/personal-access-tokens/revoke-for-user.ts +2 -1
  112. package/packages/bundled-features/src/renderer-foundation/README.md +11 -10
  113. package/packages/bundled-features/src/renderer-foundation/api.ts +14 -14
  114. package/packages/bundled-features/src/renderer-foundation/changes.json +39 -0
  115. package/packages/bundled-features/src/renderer-foundation/constants.ts +6 -8
  116. package/packages/bundled-features/src/renderer-foundation/feature.ts +7 -7
  117. package/packages/bundled-features/src/renderer-foundation/index.ts +13 -2
  118. package/packages/bundled-features/src/renderer-foundation/types.ts +132 -35
  119. package/packages/bundled-features/src/renderer-pdf/feature.ts +7 -1
  120. package/packages/bundled-features/src/sessions/db/queries/cleanup.ts +2 -2
  121. package/packages/bundled-features/src/sessions/feature.ts +2 -4
  122. package/packages/bundled-features/src/sessions/handlers/list.query.ts +2 -1
  123. package/packages/bundled-features/src/shared/attachment-file-name.ts +39 -0
  124. package/packages/bundled-features/src/shared/index.ts +6 -0
  125. package/packages/bundled-features/src/shared/is-tenant-db.ts +1 -1
  126. package/packages/bundled-features/src/shared/mail-template-body.ts +25 -0
  127. package/packages/bundled-features/src/shared/sharp-input-limits.ts +26 -0
  128. package/packages/bundled-features/src/shared/signup-handover.ts +4 -4
  129. package/packages/bundled-features/src/tags/handlers/assign-tag.write.ts +5 -1
  130. package/packages/bundled-features/src/tags/web/tag-chip.tsx +2 -1
  131. package/packages/bundled-features/src/template-resolver/README.md +63 -7
  132. package/packages/bundled-features/src/template-resolver/api.ts +83 -8
  133. package/packages/bundled-features/src/template-resolver/changes.json +118 -0
  134. package/packages/bundled-features/src/template-resolver/compile-document-body.ts +214 -0
  135. package/packages/bundled-features/src/template-resolver/constants.ts +39 -11
  136. package/packages/bundled-features/src/template-resolver/document-sanitizer.ts +335 -0
  137. package/packages/bundled-features/src/template-resolver/document-schema.ts +323 -0
  138. package/packages/bundled-features/src/template-resolver/feature.ts +56 -6
  139. package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +188 -0
  140. package/packages/bundled-features/src/template-resolver/handlers/collection-set.write.ts +30 -2
  141. package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +142 -0
  142. package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +72 -0
  143. package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +83 -0
  144. package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +3 -3
  145. package/packages/bundled-features/src/template-resolver/handlers/import-bundle-system.write.ts +42 -0
  146. package/packages/bundled-features/src/template-resolver/handlers/import-bundle.write.ts +22 -0
  147. package/packages/bundled-features/src/template-resolver/handlers/layout-source.query.ts +108 -0
  148. package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +5 -7
  149. package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +250 -0
  150. package/packages/bundled-features/src/template-resolver/handlers/preview.query.ts +25 -0
  151. package/packages/bundled-features/src/template-resolver/handlers/reset-to-system.write.ts +58 -0
  152. package/packages/bundled-features/src/template-resolver/handlers/resource-link-pdf.write.ts +34 -0
  153. package/packages/bundled-features/src/template-resolver/handlers/resource-remove.write.ts +50 -0
  154. package/packages/bundled-features/src/template-resolver/handlers/resource-shared.ts +24 -0
  155. package/packages/bundled-features/src/template-resolver/handlers/resource-upload.write.ts +135 -0
  156. package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +106 -0
  157. package/packages/bundled-features/src/template-resolver/handlers/set.write.ts +32 -3
  158. package/packages/bundled-features/src/template-resolver/handlers/shared.ts +7 -2
  159. package/packages/bundled-features/src/template-resolver/handlers/template-gates.ts +184 -0
  160. package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +183 -0
  161. package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +55 -0
  162. package/packages/bundled-features/src/template-resolver/handlers/toggle-status.write.ts +57 -10
  163. package/packages/bundled-features/src/template-resolver/handlers/upsert-system.write.ts +19 -71
  164. package/packages/bundled-features/src/template-resolver/handlers/upsert-tenant.write.ts +15 -76
  165. package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +17 -0
  166. package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +116 -0
  167. package/packages/bundled-features/src/template-resolver/index.ts +100 -0
  168. package/packages/bundled-features/src/template-resolver/layout-regions.ts +206 -0
  169. package/packages/bundled-features/src/template-resolver/linked-resources.ts +102 -0
  170. package/packages/bundled-features/src/template-resolver/preview-display-html.ts +36 -0
  171. package/packages/bundled-features/src/template-resolver/print-document.ts +58 -0
  172. package/packages/bundled-features/src/template-resolver/qualified-names.ts +30 -0
  173. package/packages/bundled-features/src/template-resolver/record-mapping.ts +26 -0
  174. package/packages/bundled-features/src/template-resolver/resource-image.ts +47 -0
  175. package/packages/bundled-features/src/template-resolver/seeding.ts +147 -17
  176. package/packages/bundled-features/src/template-resolver/store-template-resource.ts +416 -0
  177. package/packages/bundled-features/src/template-resolver/table.ts +14 -0
  178. package/packages/bundled-features/src/template-resolver/template-bundle.ts +79 -0
  179. package/packages/bundled-features/src/template-resolver/template-diff.ts +78 -0
  180. package/packages/bundled-features/src/template-resolver/template-meta.ts +129 -0
  181. package/packages/bundled-features/src/template-resolver/template-preview.ts +573 -0
  182. package/packages/bundled-features/src/template-resolver/template-resources.ts +74 -0
  183. package/packages/bundled-features/src/template-resolver/template-usage.ts +48 -0
  184. package/packages/bundled-features/src/template-resolver/template-validation.ts +229 -0
  185. package/packages/bundled-features/src/template-resolver/testing.ts +5 -5
  186. package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +9 -6
  187. package/packages/bundled-features/src/template-resolver/web/document-editor-extensions.ts +162 -0
  188. package/packages/bundled-features/src/template-resolver/web/document-editor.tsx +28 -0
  189. package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +850 -0
  190. package/packages/bundled-features/src/template-resolver/web/document-toolbar-blocks.ts +61 -0
  191. package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +26 -0
  192. package/packages/bundled-features/src/template-resolver/web/i18n.ts +100 -0
  193. package/packages/bundled-features/src/template-resolver/web/index.ts +19 -0
  194. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor-lazy.tsx +36 -0
  195. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +169 -0
  196. package/packages/bundled-features/src/template-resolver/web/placeholder-text.ts +37 -0
  197. package/packages/bundled-features/src/template-resolver/web/use-stable-by-key.ts +14 -0
  198. package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +3 -2
  199. package/packages/bundled-features/src/tenant/seeding.ts +41 -7
  200. package/packages/bundled-features/src/tenant-lifecycle/boot-checks.ts +1 -1
  201. package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +2 -1
  202. package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +4 -4
  203. package/packages/bundled-features/src/user/handlers/detail.query.ts +3 -5
  204. package/packages/bundled-features/src/user/roles-input-schema.ts +2 -2
  205. package/packages/bundled-features/src/user-data-rights/changes.json +14 -0
  206. package/packages/bundled-features/src/user-data-rights/db/queries/forget-cleanup.ts +2 -2
  207. package/packages/bundled-features/src/user-data-rights/email-templates.ts +5 -149
  208. package/packages/bundled-features/src/user-data-rights/feature.ts +72 -35
  209. package/packages/bundled-features/src/user-data-rights/handlers/cancel-deletion.write.ts +2 -1
  210. package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +5 -1
  211. package/packages/bundled-features/src/user-data-rights/handlers/list-download-attempts.query.ts +13 -1
  212. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +5 -11
  213. package/packages/bundled-features/src/user-data-rights/index.ts +1 -0
  214. package/packages/bundled-features/src/user-data-rights/lib/default-mailers.ts +134 -53
  215. package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +2 -2
  216. package/packages/bundled-features/src/user-data-rights/mail-notifications.ts +157 -0
  217. package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +1 -1
  218. package/packages/bundled-features/src/user-data-rights/schema/download-attempt.ts +6 -12
  219. package/packages/bundled-features/src/user-data-rights/web/privacy-center-screen.tsx +2 -1
  220. package/packages/bundled-features/src/user-data-rights-defaults/feature.ts +10 -1
  221. package/packages/bundled-features/src/user-data-rights-defaults/hooks/download-attempt.userdata-hook.ts +58 -0
  222. package/packages/bundled-features/src/user-profile/web/profile-screen.tsx +2 -1
  223. package/packages/bundled-features/src/waitlist/changes.json +7 -0
  224. package/packages/bundled-features/src/waitlist/feature.ts +17 -0
  225. package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +4 -5
  226. package/packages/bundled-features/src/waitlist/mail.ts +69 -18
  227. package/packages/bundled-features/src/workflow-runner/event-trigger.ts +1 -2
  228. package/samples/apps/ui-walkthrough/package.json +2 -0
  229. package/samples/apps/ui-walkthrough/src/app/auth-constants.ts +9 -0
  230. package/samples/apps/ui-walkthrough/src/app/client.tsx +6 -0
  231. package/samples/apps/ui-walkthrough/src/app/seed-templates.ts +154 -0
  232. package/samples/apps/ui-walkthrough/src/app/server.ts +5 -1
  233. package/samples/apps/ui-walkthrough/src/app/shell.tsx +12 -1
  234. package/samples/apps/ui-walkthrough/src/app/template-seed-data.ts +354 -0
  235. package/samples/apps/ui-walkthrough/src/features/billing/feature.ts +68 -0
  236. package/samples/apps/ui-walkthrough/src/features/billing/i18n.ts +18 -0
  237. package/samples/apps/ui-walkthrough/src/features/billing/index.ts +2 -0
  238. package/samples/apps/ui-walkthrough/src/features/billing/web/index.ts +7 -0
  239. package/samples/apps/ui-walkthrough/src/features/rental/screens.ts +1 -1
  240. package/samples/apps/ui-walkthrough/src/run-config.ts +37 -1
  241. package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +0 -2
  242. package/samples/apps/use-all-bundled/feature-manifest.json +123 -22
  243. package/samples/apps/use-all-bundled/kumiko/migrations/0044_download-attempt-ip-bidx.rebuild.json +4 -0
  244. package/samples/apps/use-all-bundled/kumiko/migrations/0045_template-resource-meta.rebuild.json +4 -0
  245. package/samples/apps/use-all-bundled/kumiko/migrations/0046_pdf-rendered.rebuild.json +4 -0
  246. package/samples/apps/use-all-bundled/package.json +2 -0
  247. package/samples/apps/use-all-bundled/playwright.config.ts +38 -3
  248. package/samples/apps/use-all-bundled/schema/generate.ts +13 -3
  249. package/samples/apps/use-all-bundled/schema/schema.generated.ts +3 -0
  250. package/samples/apps/use-all-bundled/scripts/check-coverage.ts +3 -0
  251. package/samples/apps/use-all-bundled/src/app/auth-constants.ts +3 -0
  252. package/samples/apps/use-all-bundled/src/app/client.tsx +4 -0
  253. package/samples/apps/use-all-bundled/src/app/seed-documents.ts +128 -0
  254. package/samples/apps/use-all-bundled/src/app/seed.ts +4 -0
  255. package/samples/apps/use-all-bundled/src/run-config.ts +11 -11
  256. package/samples/recipes/delivery-notifications/README.md +26 -25
  257. package/samples/recipes/delivery-notifications/package.json +2 -1
  258. package/samples/recipes/delivery-notifications/src/feature.ts +64 -58
  259. package/samples/recipes/seed-migration/README.md +1 -1
  260. package/packages/bundled-features/src/renderer-simple/changes.json +0 -27
  261. package/packages/bundled-features/src/renderer-simple/feature.ts +0 -66
  262. package/packages/bundled-features/src/renderer-simple/index.ts +0 -8
  263. package/packages/bundled-features/src/renderer-simple/mail-blocks.ts +0 -138
  264. package/packages/bundled-features/src/renderer-simple/resolve-variables.ts +0 -57
  265. package/packages/bundled-features/src/renderer-simple/simple-renderer.ts +0 -304
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
2
2
  import type { CheckoutConsentRecordedPayload } from "../events.js";
3
- import { CONSENT_TEXTS, type ConsentLocale, resolveConsentLocale } from "./consent-text.js";
3
+ import { type ConsentLocale, resolveConsentLocale } from "./consent-text.js";
4
4
 
5
5
  // Inline de/en like the consent texts: the confirmation repeats the legal
6
6
  // wording the buyer agreed to, so it cannot follow the en-only i18n convention.
@@ -22,7 +22,7 @@ type ConfirmationLabels = {
22
22
  readonly every: string;
23
23
  };
24
24
 
25
- const LABELS: Readonly<Record<ConsentLocale, ConfirmationLabels>> = {
25
+ export const CONFIRMATION_LABELS: Readonly<Record<ConsentLocale, ConfirmationLabels>> = {
26
26
  de: {
27
27
  subject: "Vertragsbestätigung",
28
28
  intro:
@@ -71,19 +71,17 @@ const LABELS: Readonly<Record<ConsentLocale, ConfirmationLabels>> = {
71
71
  },
72
72
  };
73
73
 
74
- export type ContractConfirmationSection =
75
- | { readonly text: string }
76
- | { readonly heading: string }
77
- | { readonly markdown: string };
74
+ type TextRow = { readonly text: string };
78
75
 
79
- export type ContractConfirmationContent = {
80
- readonly subject: string;
81
- readonly header: string;
82
- readonly sections: readonly ContractConfirmationSection[];
83
- readonly footer: string;
76
+ /** Variables of the contract confirmation template. */
77
+ export type ContractConfirmationVariables = {
78
+ readonly contractLines: readonly TextRow[];
79
+ readonly consentGivenAt: string;
80
+ readonly termsParagraphs: readonly TextRow[];
81
+ readonly operatorEmail: string;
84
82
  };
85
83
 
86
- export type RenderContractConfirmationArgs = {
84
+ export type ContractConfirmationArgs = {
87
85
  readonly consent: CheckoutConsentRecordedPayload;
88
86
  readonly timeZone: string;
89
87
  /** Translated plan name; falls back to the raw tier. */
@@ -99,7 +97,7 @@ export type RenderContractConfirmationArgs = {
99
97
  };
100
98
 
101
99
  function formatPrice(consent: CheckoutConsentRecordedPayload, locale: ConsentLocale): string {
102
- const labels = LABELS[locale];
100
+ const labels = CONFIRMATION_LABELS[locale];
103
101
  if (consent.unitAmount === null || consent.currency === null) return "-";
104
102
  let amount: string;
105
103
  try {
@@ -140,45 +138,41 @@ function formatDate(iso: string, locale: ConsentLocale, timeZone: string): strin
140
138
  }).format(epochMilliseconds);
141
139
  }
142
140
 
143
- /** Structured mail content (header/sections/footer) for the email channel's
144
- * renderer; the renderer owns HTML escaping, this only builds plain text. */
145
- export function renderContractConfirmation(
146
- args: RenderContractConfirmationArgs,
147
- ): ContractConfirmationContent {
141
+ // The mail template only knows plain text variables, so the Markdown terms
142
+ // become one row per paragraph; heading markers are dropped.
143
+ function termsParagraphsOf(markdown: string): readonly TextRow[] {
144
+ return markdown
145
+ .split(/\n\s*\n/)
146
+ .map((paragraph) => paragraph.replace(/^#{1,6}\s+/gm, "").trim())
147
+ .filter((paragraph) => paragraph.length > 0)
148
+ .map((text) => ({ text }));
149
+ }
150
+
151
+ export function contractConfirmationVariables(
152
+ args: ContractConfirmationArgs,
153
+ ): ContractConfirmationVariables {
148
154
  const locale = resolveConsentLocale(args.consent.locale);
149
- const labels = LABELS[locale];
150
- const texts = CONSENT_TEXTS[locale];
151
- const { consent } = args;
155
+ const labels = CONFIRMATION_LABELS[locale];
156
+ const { consent, timeZone } = args;
152
157
 
153
- const { timeZone } = args;
154
158
  const subjectLine =
155
159
  consent.mode === "payment"
156
160
  ? `${labels.item}: ${args.itemLabel ?? labels.oneOffPayment}`
157
161
  : `${labels.plan}: ${args.tierLabel ?? consent.tier ?? labels.oneOffPayment}`;
158
- const contractLines = [
159
- subjectLine,
160
- `${labels.price}: ${formatPrice(consent, locale)}`,
161
- `${labels.contractStart}: ${formatDate(args.contractStartIso, locale, timeZone)}`,
162
- ...(args.currentPeriodEndIso !== undefined
163
- ? [`${labels.currentPeriodEnd}: ${formatDate(args.currentPeriodEndIso, locale, timeZone)}`]
164
- : []),
165
- ];
166
162
  const vatNote = args.vatNote[locale] ?? args.vatNote[resolveConsentLocale()];
167
163
 
168
164
  return {
169
- subject: labels.subject,
170
- header: labels.subject,
171
- sections: [
172
- { text: labels.intro },
173
- ...contractLines.map((text) => ({ text })),
174
- ...(vatNote !== undefined ? [{ text: vatNote }] : []),
175
- { heading: labels.consentHeading },
176
- { text: texts.earlyPerformance },
177
- { text: texts.withdrawalLoss },
178
- { text: `${labels.consentGivenAt}: ${formatDate(args.consentGivenAtIso, locale, timeZone)}` },
179
- { heading: labels.termsHeading },
180
- { markdown: args.termsContent },
181
- ],
182
- footer: `${labels.contact} ${args.operatorEmail}`,
165
+ contractLines: [
166
+ subjectLine,
167
+ `${labels.price}: ${formatPrice(consent, locale)}`,
168
+ `${labels.contractStart}: ${formatDate(args.contractStartIso, locale, timeZone)}`,
169
+ ...(args.currentPeriodEndIso !== undefined
170
+ ? [`${labels.currentPeriodEnd}: ${formatDate(args.currentPeriodEndIso, locale, timeZone)}`]
171
+ : []),
172
+ ...(vatNote !== undefined ? [vatNote] : []),
173
+ ].map((text) => ({ text })),
174
+ consentGivenAt: formatDate(args.consentGivenAtIso, locale, timeZone),
175
+ termsParagraphs: termsParagraphsOf(args.termsContent),
176
+ operatorEmail: args.operatorEmail,
183
177
  };
184
178
  }
@@ -27,7 +27,7 @@ import {
27
27
  SUBSCRIPTION_AGGREGATE_TYPE,
28
28
  } from "../events.js";
29
29
  import type { ConsumerProtectionOptions, ResolvedBillingFoundationOptions } from "../types.js";
30
- import { renderContractConfirmation } from "./confirmation-mail.js";
30
+ import { contractConfirmationVariables } from "./confirmation-mail.js";
31
31
  import { resolveConsentLocale } from "./consent-text.js";
32
32
 
33
33
  export const issueContractConfirmationSchema = z
@@ -185,7 +185,7 @@ export function createIssueContractConfirmationHandler(
185
185
  : undefined;
186
186
 
187
187
  const issuedAtIso = options.now().toString();
188
- const content = renderContractConfirmation({
188
+ const variables = contractConfirmationVariables({
189
189
  consent,
190
190
  timeZone: pickTimeZone(recipient.data.timezone, ctx.tz.tenant),
191
191
  ...(tierLabel !== undefined && { tierLabel }),
@@ -220,7 +220,7 @@ export function createIssueContractConfirmationHandler(
220
220
 
221
221
  await ctx.notify(CONTRACT_CONFIRMATION_NOTIFICATION_TYPE, {
222
222
  route: { email: recipient.data.email },
223
- data: content,
223
+ data: variables,
224
224
  locale,
225
225
  priority: "critical",
226
226
  });
@@ -0,0 +1,115 @@
1
+ import {
2
+ type FeatureRegistrar,
3
+ type NotificationLocalizedTemplate,
4
+ type TemplateVariables,
5
+ templateVariable,
6
+ } from "@cosmicdrift/kumiko-framework/engine";
7
+ import { mailHeading, mailParagraph, mailRepeatedRows } from "../../shared/index.js";
8
+ import { CONFIRMATION_LABELS } from "./confirmation-mail.js";
9
+ import { CONSENT_LOCALES, CONSENT_TEXTS, type ConsentLocale } from "./consent-text.js";
10
+ import { TERMINATION_TEXTS } from "./termination-texts.js";
11
+
12
+ // Slot syntax for the translated texts: `{name}` becomes a template variable.
13
+ const OPERATOR_EMAIL_SLOT = "{operatorEmail}";
14
+
15
+ const textRows = (): TemplateVariables[string] =>
16
+ templateVariable.list({ text: templateVariable.text({ required: true }) }, { required: true });
17
+
18
+ export const TERMINATION_RECEIPT_VARIABLES: TemplateVariables = {
19
+ subject: templateVariable.text({ required: true }),
20
+ receivedAt: templateVariable.text({ required: true }),
21
+ requestId: templateVariable.text({ required: true }),
22
+ details: textRows(),
23
+ operatorEmail: templateVariable.text({ required: true }),
24
+ };
25
+
26
+ export const OPERATOR_NOTICE_VARIABLES: TemplateVariables = {
27
+ subject: templateVariable.text({ required: true }),
28
+ lines: textRows(),
29
+ operatorEmail: templateVariable.text({ required: true }),
30
+ };
31
+
32
+ export const CONTRACT_CONFIRMATION_VARIABLES: TemplateVariables = {
33
+ contractLines: textRows(),
34
+ consentGivenAt: templateVariable.text({ required: true }),
35
+ termsParagraphs: textRows(),
36
+ operatorEmail: templateVariable.text({ required: true }),
37
+ };
38
+
39
+ function perLocale(
40
+ build: (locale: ConsentLocale) => { body: string; subject: string },
41
+ ): NotificationLocalizedTemplate {
42
+ return Object.fromEntries(
43
+ CONSENT_LOCALES.map((locale) => {
44
+ const { body, subject } = build(locale);
45
+ return [locale, { body, meta: { subject, category: "transactional" as const } }];
46
+ }),
47
+ );
48
+ }
49
+
50
+ export function terminationReceiptTemplate(): NotificationLocalizedTemplate {
51
+ return perLocale((locale) => {
52
+ const texts = TERMINATION_TEXTS[locale];
53
+ return {
54
+ subject: "{{subject}}",
55
+ body: [
56
+ mailParagraph(texts.receiptGreeting),
57
+ mailParagraph(texts.receiptIntro),
58
+ mailParagraph(`${texts.receivedAt}: {receivedAt}`),
59
+ mailParagraph(`${texts.requestId}: {requestId}`),
60
+ mailRepeatedRows("details", "text"),
61
+ mailParagraph(texts.receiptNextSteps),
62
+ mailParagraph(texts.receiptRevoke(OPERATOR_EMAIL_SLOT)),
63
+ mailParagraph(`${texts.contact} {operatorEmail}`),
64
+ ].join(""),
65
+ };
66
+ });
67
+ }
68
+
69
+ export function operatorNoticeTemplate(): NotificationLocalizedTemplate {
70
+ return perLocale((locale) => ({
71
+ subject: "{{subject}}",
72
+ body: [
73
+ mailParagraph(TERMINATION_TEXTS[locale].operatorIntro),
74
+ mailRepeatedRows("lines", "text"),
75
+ mailParagraph("{operatorEmail}"),
76
+ ].join(""),
77
+ }));
78
+ }
79
+
80
+ export function contractConfirmationTemplate(): NotificationLocalizedTemplate {
81
+ return perLocale((locale) => {
82
+ const labels = CONFIRMATION_LABELS[locale];
83
+ const consent = CONSENT_TEXTS[locale];
84
+ return {
85
+ subject: labels.subject,
86
+ body: [
87
+ mailParagraph(labels.intro),
88
+ mailRepeatedRows("contractLines", "text"),
89
+ mailHeading(labels.consentHeading),
90
+ mailParagraph(consent.earlyPerformance),
91
+ mailParagraph(consent.withdrawalLoss),
92
+ mailParagraph(`${labels.consentGivenAt}: {consentGivenAt}`),
93
+ mailHeading(labels.termsHeading),
94
+ mailRepeatedRows("termsParagraphs", "text"),
95
+ mailParagraph(`${labels.contact} {operatorEmail}`),
96
+ ].join(""),
97
+ };
98
+ });
99
+ }
100
+
101
+ // The registered notification names are the tail of the QN types in constants.ts.
102
+ export function registerConsumerProtectionMails(r: FeatureRegistrar): void {
103
+ r.notification("contract-confirmation", {
104
+ variables: CONTRACT_CONFIRMATION_VARIABLES,
105
+ localizedTemplate: contractConfirmationTemplate,
106
+ });
107
+ r.notification("contract-termination-receipt", {
108
+ variables: TERMINATION_RECEIPT_VARIABLES,
109
+ localizedTemplate: terminationReceiptTemplate,
110
+ });
111
+ r.notification("contract-termination-operator-notice", {
112
+ variables: OPERATOR_NOTICE_VARIABLES,
113
+ localizedTemplate: operatorNoticeTemplate,
114
+ });
115
+ }
@@ -13,6 +13,7 @@ import {
13
13
  } from "../events.js";
14
14
  import type { ConsumerProtectionOptions, ResolvedBillingFoundationOptions } from "../types.js";
15
15
  import { createIssueContractConfirmationHandler } from "./issue-confirmation.js";
16
+ import { registerConsumerProtectionMails } from "./mail-notifications.js";
16
17
  import { registerContractTermination } from "./register-termination.js";
17
18
 
18
19
  type ConfirmationTrigger = {
@@ -46,6 +47,7 @@ export function registerConsumerProtection(
46
47
  consumerProtection: ConsumerProtectionOptions,
47
48
  ): void {
48
49
  r.requires("template-resolver", "delivery", "user", "tenant");
50
+ registerConsumerProtectionMails(r);
49
51
  registerContractTermination(r, options, consumerProtection);
50
52
  r.defineEvent(CHECKOUT_CONSENT_RECORDED_EVENT_SHORT, checkoutConsentRecordedPayloadSchema, {
51
53
  piiFields: "none",
@@ -7,11 +7,22 @@ import type {
7
7
  import type { ConsentLocale } from "./consent-text.js";
8
8
  import { type OperatorNoticeReason, TERMINATION_TEXTS } from "./termination-texts.js";
9
9
 
10
- export type TerminationMailContent = {
10
+ type TextRow = { readonly text: string };
11
+
12
+ /** Variables of the termination receipt template. */
13
+ export type TerminationReceiptVariables = {
11
14
  readonly subject: string;
12
- readonly header: string;
13
- readonly sections: readonly { readonly text: string }[];
14
- readonly footer: string;
15
+ readonly receivedAt: string;
16
+ readonly requestId: string;
17
+ readonly details: readonly TextRow[];
18
+ readonly operatorEmail: string;
19
+ };
20
+
21
+ /** Variables of the operator notice template. */
22
+ export type OperatorNoticeVariables = {
23
+ readonly subject: string;
24
+ readonly lines: readonly TextRow[];
25
+ readonly operatorEmail: string;
15
26
  };
16
27
 
17
28
  /** What the declarant entered; the only input of the receipt. */
@@ -53,7 +64,7 @@ export function formatReceivedAt(iso: string, locale: ConsentLocale): string {
53
64
  function declarationLines(
54
65
  declaration: OperatorNoticeDeclaration,
55
66
  locale: ConsentLocale,
56
- ): readonly { readonly text: string }[] {
67
+ ): readonly TextRow[] {
57
68
  const texts = TERMINATION_TEXTS[locale];
58
69
  const lines = [
59
70
  `${texts.declarationType}: ${texts.declarationTypeLabel[declaration.declarationType]}`,
@@ -68,7 +79,7 @@ function declarationLines(
68
79
  return lines.map((text) => ({ text }));
69
80
  }
70
81
 
71
- export type RenderTerminationReceiptArgs = {
82
+ export type TerminationReceiptArgs = {
72
83
  readonly locale: ConsentLocale;
73
84
  readonly requestId: string;
74
85
  readonly receivedAtIso: string;
@@ -78,28 +89,20 @@ export type RenderTerminationReceiptArgs = {
78
89
 
79
90
  /** Built from the declarant's own input only: the content must not depend on
80
91
  * whether (or how) the declaration matched a contract. */
81
- export function renderTerminationReceipt(
82
- args: RenderTerminationReceiptArgs,
83
- ): TerminationMailContent {
92
+ export function terminationReceiptVariables(
93
+ args: TerminationReceiptArgs,
94
+ ): TerminationReceiptVariables {
84
95
  const texts = TERMINATION_TEXTS[args.locale];
85
- const subject = texts.receiptSubject[args.declaration.declarationType];
86
96
  return {
87
- subject,
88
- header: subject,
89
- sections: [
90
- { text: texts.receiptGreeting },
91
- { text: texts.receiptIntro },
92
- { text: `${texts.receivedAt}: ${formatReceivedAt(args.receivedAtIso, args.locale)}` },
93
- { text: `${texts.requestId}: ${args.requestId}` },
94
- ...declarationLines(args.declaration, args.locale),
95
- { text: texts.receiptNextSteps },
96
- { text: texts.receiptRevoke(args.operatorEmail) },
97
- ],
98
- footer: `${texts.contact} ${args.operatorEmail}`,
97
+ subject: texts.receiptSubject[args.declaration.declarationType],
98
+ receivedAt: formatReceivedAt(args.receivedAtIso, args.locale),
99
+ requestId: args.requestId,
100
+ details: declarationLines(args.declaration, args.locale),
101
+ operatorEmail: args.operatorEmail,
99
102
  };
100
103
  }
101
104
 
102
- export type RenderOperatorNoticeArgs = {
105
+ export type OperatorNoticeArgs = {
103
106
  readonly locale: ConsentLocale;
104
107
  readonly requestId: string;
105
108
  readonly receivedAtIso: string;
@@ -111,14 +114,11 @@ export type RenderOperatorNoticeArgs = {
111
114
  readonly operatorEmail: string;
112
115
  };
113
116
 
114
- export function renderOperatorNotice(args: RenderOperatorNoticeArgs): TerminationMailContent {
117
+ export function operatorNoticeVariables(args: OperatorNoticeArgs): OperatorNoticeVariables {
115
118
  const texts = TERMINATION_TEXTS[args.locale];
116
- const subject = texts.operatorSubject[args.declaration.declarationType];
117
119
  return {
118
- subject,
119
- header: subject,
120
- sections: [
121
- { text: texts.operatorIntro },
120
+ subject: texts.operatorSubject[args.declaration.declarationType],
121
+ lines: [
122
122
  ...args.reasons.map((reason) => ({
123
123
  text: `${texts.noticeReasons}: ${texts.operatorReasons[reason]}`,
124
124
  })),
@@ -131,6 +131,6 @@ export function renderOperatorNotice(args: RenderOperatorNoticeArgs): Terminatio
131
131
  : []),
132
132
  ...declarationLines(args.declaration, args.locale),
133
133
  ],
134
- footer: args.operatorEmail,
134
+ operatorEmail: args.operatorEmail,
135
135
  };
136
136
  }
@@ -4,7 +4,7 @@ import { CONTRACT_TERMINATION_OPERATOR_NOTIFICATION_TYPE } from "../constants.js
4
4
  import type { ProviderCancelOutcome } from "../events.js";
5
5
  import type { ConsumerProtectionOptions } from "../types.js";
6
6
  import type { ConsentLocale } from "./consent-text.js";
7
- import { type OperatorNoticeDeclaration, renderOperatorNotice } from "./termination-mail.js";
7
+ import { type OperatorNoticeDeclaration, operatorNoticeVariables } from "./termination-mail.js";
8
8
  import type { OperatorNoticeReason } from "./termination-texts.js";
9
9
 
10
10
  export type OperatorNoticePlan = {
@@ -35,7 +35,7 @@ export async function notifyOperator(
35
35
  ): Promise<void> {
36
36
  await requireNotify(ctx)(CONTRACT_TERMINATION_OPERATOR_NOTIFICATION_TYPE, {
37
37
  route: { email: consumerProtection.operatorEmail },
38
- data: renderOperatorNotice({
38
+ data: operatorNoticeVariables({
39
39
  ...plan,
40
40
  operatorEmail: consumerProtection.operatorEmail,
41
41
  }),
@@ -3,6 +3,7 @@
3
3
 
4
4
  import { type AnonymousExtraRoute, getSessionCookies } from "@cosmicdrift/kumiko-framework/api";
5
5
  import { escapeHtml, escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
6
+ import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
6
7
  import type { Context } from "hono";
7
8
  import { getCookie } from "hono/cookie";
8
9
  import {
@@ -98,10 +99,6 @@ function htmlResponse(
98
99
  );
99
100
  }
100
101
 
101
- function isOneOf<T extends string>(options: readonly T[], value: string): value is T {
102
- return (options as readonly string[]).includes(value);
103
- }
104
-
105
102
  async function readForm(c: Context): Promise<{ step: string; values: FormValues }> {
106
103
  const body = await c.req.parseBody();
107
104
  const text = (key: string): string => {
@@ -31,7 +31,7 @@ import {
31
31
  import { purchaseRolesOf } from "../plan-catalog.js";
32
32
  import type { ConsumerProtectionOptions, ResolvedBillingFoundationOptions } from "../types.js";
33
33
  import { resolveConsentLocale } from "./consent-text.js";
34
- import { renderTerminationReceipt, type TerminationDeclaration } from "./termination-mail.js";
34
+ import { type TerminationDeclaration, terminationReceiptVariables } from "./termination-mail.js";
35
35
  import { notifyOperator, type OperatorNoticePlan, requireNotify } from "./termination-notify.js";
36
36
  import type { RecordContractTerminationResult } from "./termination-record.js";
37
37
  import type { OperatorNoticeReason } from "./termination-texts.js";
@@ -111,7 +111,7 @@ async function sendTerminationMails(
111
111
  ): Promise<void> {
112
112
  await requireNotify(ctx)(CONTRACT_TERMINATION_RECEIPT_NOTIFICATION_TYPE, {
113
113
  route: { email: plan.declaration.email },
114
- data: renderTerminationReceipt({
114
+ data: terminationReceiptVariables({
115
115
  locale: plan.locale,
116
116
  requestId: plan.requestId,
117
117
  receivedAtIso: plan.receivedAtIso,
@@ -40,10 +40,10 @@ export async function getSubscriptionForTenant(
40
40
  if (!row) return null;
41
41
  const piiKms = configuredPiiSubjectKms();
42
42
  const decrypted = piiKms
43
- ? await decryptPiiFieldValues(row as Record<string, unknown>, SUBSCRIPTION_PII_FIELDS, piiKms, {
43
+ ? await decryptPiiFieldValues(row, SUBSCRIPTION_PII_FIELDS, piiKms, {
44
44
  requestId: `billing-foundation:get-subscription:${tenantId}`,
45
45
  })
46
- : (row as Record<string, unknown>);
46
+ : row;
47
47
  // @cast-boundary db-row — drizzle-row carries column-as-unknown
48
48
  return {
49
49
  tier: decrypted["tier"] as string,
@@ -52,15 +52,10 @@ export function createPortalSessionHandler(options: BillingFoundationOptions): W
52
52
  }
53
53
  const piiKms = configuredPiiSubjectKms();
54
54
  const decrypted = piiKms
55
- ? await decryptPiiFieldValues(
56
- row as Record<string, unknown>,
57
- SUBSCRIPTION_PII_FIELDS,
58
- piiKms,
59
- {
60
- requestId: `billing-foundation:create-portal-session:${tenantId}`,
61
- },
62
- )
63
- : (row as Record<string, unknown>);
55
+ ? await decryptPiiFieldValues(row, SUBSCRIPTION_PII_FIELDS, piiKms, {
56
+ requestId: `billing-foundation:create-portal-session:${tenantId}`,
57
+ })
58
+ : row;
64
59
  const providerName = row["providerName"] as string; // @cast-boundary db-row
65
60
  const providerCustomerId = decrypted["providerCustomerId"] as string; // @cast-boundary db-row
66
61
 
@@ -27,7 +27,7 @@ export const listSubscriptionsQuery: QueryHandlerDef = {
27
27
  if (!piiKms) return { rows };
28
28
  const decryptedRows = await Promise.all(
29
29
  rows.map((row) =>
30
- decryptPiiFieldValues(row as Record<string, unknown>, SUBSCRIPTION_PII_FIELDS, piiKms, {
30
+ decryptPiiFieldValues(row, SUBSCRIPTION_PII_FIELDS, piiKms, {
31
31
  requestId: `billing-foundation:list-subscriptions:${ctx.user.tenantId}`,
32
32
  }),
33
33
  ),
@@ -167,8 +167,8 @@ export async function appendSubscriptionEvent(
167
167
  // ---------------------------------------------------------------
168
168
  const eventPayload: SubscriptionEventPayload = {
169
169
  providerName: payload.providerName,
170
- providerCustomerId: encryptedFields["providerCustomerId"] as string,
171
- providerSubscriptionId: encryptedFields["providerSubscriptionId"] as string,
170
+ providerCustomerId: encryptedFields.providerCustomerId,
171
+ providerSubscriptionId: encryptedFields.providerSubscriptionId,
172
172
  status: payload.status,
173
173
  tier: payload.tier,
174
174
  currentPeriodEndIso: payload.currentPeriodEndIso,
@@ -95,7 +95,7 @@ export const processPaymentEventHandler: WriteHandlerDef = {
95
95
  // ---------------------------------------------------------------
96
96
  const eventPayload: PaymentEventPayload = {
97
97
  providerName: payload.providerName,
98
- providerCustomerId: encryptedFields["providerCustomerId"] as string,
98
+ providerCustomerId: encryptedFields.providerCustomerId,
99
99
  priceId: payload.priceId,
100
100
  ...(payload.consentId !== undefined && { consentId: payload.consentId }),
101
101
  };
@@ -14,6 +14,7 @@ import type {
14
14
  SignatureExtraRouteDeps,
15
15
  } from "@cosmicdrift/kumiko-framework/api";
16
16
  import type { TenantId, WriteResult } from "@cosmicdrift/kumiko-framework/engine";
17
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
17
18
  import { subscriptionAggregateId } from "./aggregate-id.js";
18
19
  import { SubscriptionFoundationQueries, SubscriptionStatuses } from "./constants.js";
19
20
  import { createSubscriptionWebhookRoute } from "./webhook-handler.js";
@@ -52,12 +53,8 @@ export function effectiveTierFromSubscription<TTier extends string>(
52
53
  // (handler bug, or the handlerQn resolving to a non-list query) fails
53
54
  // loudly instead of crashing on `.find` against `undefined`.
54
55
  function asRows(result: unknown): ReadonlyArray<Record<string, unknown>> {
55
- if (
56
- typeof result === "object" &&
57
- result !== null &&
58
- Array.isArray((result as { rows?: unknown }).rows)
59
- ) {
60
- return (result as { rows: ReadonlyArray<Record<string, unknown>> }).rows;
56
+ if (isPlainObject(result) && Array.isArray(result["rows"])) {
57
+ return result["rows"];
61
58
  }
62
59
  throw new Error("expected a { rows: [...] } list-query result");
63
60
  }
@@ -7,6 +7,7 @@ import {
7
7
  type TenantId,
8
8
  } from "@cosmicdrift/kumiko-framework/engine";
9
9
  import { InternalError, ValidationError } from "@cosmicdrift/kumiko-framework/errors";
10
+ import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
10
11
  import * as z from "zod";
11
12
  import { subscriptionsProjectionTable } from "../../billing-foundation/index.js";
12
13
  import type { CapLimitContext } from "../../cap-counter/index.js";
@@ -79,7 +80,7 @@ async function resolveLimitsByTenantAndCap(
79
80
  }
80
81
 
81
82
  function isSortableField(field: string): field is SortableField {
82
- return (SORTABLE_FIELDS as readonly string[]).includes(field);
83
+ return isOneOf(SORTABLE_FIELDS, field);
83
84
  }
84
85
 
85
86
  // tier is a plain string with no defined ordering — lt/gt have no meaning
@@ -1,17 +1,19 @@
1
1
  // @runtime client
2
+
2
3
  import type { ColumnRendererProps } from "@cosmicdrift/kumiko-renderer";
4
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
3
5
  import type { ReactNode } from "react";
4
6
  import type { CapUsage } from "../types.js";
5
7
  import { CapUsageBar } from "./cap-usage-bar.js";
6
8
 
7
9
  function isCapUsage(value: unknown): value is CapUsage {
8
- if (typeof value !== "object" || value === null) return false;
9
- const used = (value as { used?: unknown }).used;
10
- const limit = (value as { limit?: unknown }).limit;
10
+ if (!isPlainObject(value)) return false;
11
+ const used = value["used"];
12
+ const limit = value["limit"];
11
13
  return (
12
14
  (typeof used === "number" || used === null) &&
13
15
  (typeof limit === "number" || limit === null) &&
14
- typeof (value as { fraction?: unknown }).fraction === "number"
16
+ typeof value["fraction"] === "number"
15
17
  );
16
18
  }
17
19
 
@@ -1,4 +1,23 @@
1
1
  [
2
+ {
3
+ "version": "0.363.0",
4
+ "type": "breaking",
5
+ "title": "A notification without a mail template fails on the email channel",
6
+ "detail": "The renderer option of createChannelEmailFeature is removed. A notification routed to the email channel without template + meta fails its delivery permanently with \"has no mail template, declare template + meta on r.notification\". r.notification declares no channels, so this is not checked at boot; an app's own mail tests over the inmemory transport catch it.",
7
+ "migration": "Drop the renderer option. Declare template + meta (or localizedTemplate) and variables on every r.notification that reaches the email channel, or accept that its email delivery fails. Run the app's notification tests after the bump: every email notification without a template now fails there."
8
+ },
9
+ {
10
+ "version": "0.363.0",
11
+ "type": "improvement",
12
+ "title": "Email channel sends rendered-pdf attachments",
13
+ "detail": "The channel resolves rendered-pdf entries from notify data next to file entries; without a locale the message locale is used. A render failure fails the send job, so the retry runs it again."
14
+ },
15
+ {
16
+ "version": "0.363.0",
17
+ "type": "improvement",
18
+ "title": "Email channel sends file attachments and inline cid images",
19
+ "detail": "EmailMessage.attachments is sent through the SMTP transport (inline images with Content-ID). notify data.attachments with kind file entries are resolved through the file store; the PII guard rejects ciphertext file names."
20
+ },
2
21
  {
3
22
  "version": "0.350.0",
4
23
  "type": "improvement",