@cosmicdrift/kumiko-samples 0.377.0 → 0.379.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 (526) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +18 -17
  3. package/packages/bundled-features/src/account-security/feature.ts +2 -2
  4. package/packages/bundled-features/src/account-security/index.ts +8 -8
  5. package/packages/bundled-features/src/account-security/{constants.ts → web/constants.ts} +0 -2
  6. package/packages/bundled-features/src/account-security/{i18n.ts → web/i18n.ts} +0 -1
  7. package/packages/bundled-features/src/admin-shell/feature.ts +10 -9
  8. package/packages/bundled-features/src/admin-shell/index.ts +8 -8
  9. package/packages/bundled-features/src/admin-shell/web/client-plugin.tsx +2 -3
  10. package/packages/bundled-features/src/admin-shell/{constants.ts → web/constants.ts} +0 -1
  11. package/packages/bundled-features/src/admin-shell/web/default-translations.ts +10 -0
  12. package/packages/bundled-features/src/admin-shell/web/i18n.ts +51 -9
  13. package/packages/bundled-features/src/admin-shell/web/index.ts +0 -1
  14. package/packages/bundled-features/src/audit/changes.json +7 -0
  15. package/packages/bundled-features/src/audit/feature.ts +72 -64
  16. package/packages/bundled-features/src/audit/handlers/audit-events.ts +153 -0
  17. package/packages/bundled-features/src/audit/handlers/audit-system-db.ts +13 -0
  18. package/packages/bundled-features/src/audit/handlers/details-all-tenants.query.ts +26 -0
  19. package/packages/bundled-features/src/audit/handlers/details.query.ts +11 -39
  20. package/packages/bundled-features/src/audit/handlers/list-all-tenants.query.ts +47 -0
  21. package/packages/bundled-features/src/audit/handlers/list.query.ts +16 -123
  22. package/packages/bundled-features/src/audit/handlers/resolve-audit-tenant.ts +21 -7
  23. package/packages/bundled-features/src/audit/index.ts +1 -1
  24. package/packages/bundled-features/src/audit/run-escape-hatch-retention.ts +4 -4
  25. package/packages/bundled-features/src/audit/screens.ts +82 -0
  26. package/packages/bundled-features/src/audit/web/client-plugin.tsx +2 -3
  27. package/packages/bundled-features/src/audit/{constants.ts → web/constants.ts} +11 -1
  28. package/packages/bundled-features/src/audit/web/default-translations.ts +7 -0
  29. package/packages/bundled-features/src/audit/web/i18n.ts +33 -7
  30. package/packages/bundled-features/src/audit/web/index.ts +0 -2
  31. package/packages/bundled-features/src/auth-email-password/bootstrap.ts +6 -6
  32. package/packages/bundled-features/src/auth-email-password/changes.json +6 -0
  33. package/packages/bundled-features/src/auth-email-password/errors.ts +1 -1
  34. package/packages/bundled-features/src/auth-email-password/feature.ts +2 -2
  35. package/packages/bundled-features/src/auth-email-password/handlers/confirm-account-unlock.write.ts +1 -1
  36. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +4 -4
  37. package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +3 -3
  38. package/packages/bundled-features/src/auth-email-password/handlers/invite-info.query.ts +1 -1
  39. package/packages/bundled-features/src/auth-email-password/handlers/login.write.ts +38 -4
  40. package/packages/bundled-features/src/auth-email-password/handlers/request-account-unlock.write.ts +1 -1
  41. package/packages/bundled-features/src/auth-email-password/handlers/request-email-verification.write.ts +1 -1
  42. package/packages/bundled-features/src/auth-email-password/handlers/request-password-reset.write.ts +1 -1
  43. package/packages/bundled-features/src/auth-email-password/handlers/reset-password.write.ts +1 -1
  44. package/packages/bundled-features/src/auth-email-password/handlers/verify-email.write.ts +1 -1
  45. package/packages/bundled-features/src/auth-email-password/index.ts +6 -1
  46. package/packages/bundled-features/src/auth-email-password/mail-notifications.ts +1 -1
  47. package/packages/bundled-features/src/auth-email-password/signup-activation.ts +1 -1
  48. package/packages/bundled-features/src/auth-email-password/web/auth-client.ts +0 -1
  49. package/packages/bundled-features/src/auth-email-password/web/auth-form-logic.ts +0 -1
  50. package/packages/bundled-features/src/auth-email-password/web/auth-form-primitives.tsx +0 -1
  51. package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +0 -1
  52. package/packages/bundled-features/src/auth-email-password/web/auth-redirect.ts +0 -1
  53. package/packages/bundled-features/src/auth-email-password/web/client-actions.ts +0 -1
  54. package/packages/bundled-features/src/auth-email-password/web/client-plugin.ts +1 -2
  55. package/packages/bundled-features/src/auth-email-password/web/confirm-account-unlock-screen.tsx +0 -1
  56. package/packages/bundled-features/src/auth-email-password/{constants.ts → web/constants.ts} +3 -6
  57. package/packages/bundled-features/src/auth-email-password/web/default-topbar-actions.tsx +0 -1
  58. package/packages/bundled-features/src/auth-email-password/web/forgot-password-screen.tsx +0 -1
  59. package/packages/bundled-features/src/auth-email-password/{i18n.ts → web/i18n.ts} +3 -4
  60. package/packages/bundled-features/src/auth-email-password/web/index.ts +1 -2
  61. package/packages/bundled-features/src/auth-email-password/web/invite-accept-screen.tsx +2 -3
  62. package/packages/bundled-features/src/auth-email-password/web/login-screen.tsx +0 -1
  63. package/packages/bundled-features/src/auth-email-password/{mail-i18n.ts → web/mail-i18n.ts} +0 -1
  64. package/packages/bundled-features/src/auth-email-password/web/request-account-unlock-screen.tsx +0 -1
  65. package/packages/bundled-features/src/auth-email-password/web/reset-password-screen.tsx +0 -1
  66. package/packages/bundled-features/src/auth-email-password/web/session-bootstrap-error.tsx +0 -1
  67. package/packages/bundled-features/src/auth-email-password/web/session.tsx +0 -1
  68. package/packages/bundled-features/src/auth-email-password/web/signup-complete-screen.tsx +2 -3
  69. package/packages/bundled-features/src/auth-email-password/web/signup-screen.tsx +0 -1
  70. package/packages/bundled-features/src/auth-email-password/web/tenant-menu-items.tsx +0 -1
  71. package/packages/bundled-features/src/auth-email-password/web/tenant-switcher.tsx +0 -1
  72. package/packages/bundled-features/src/auth-email-password/web/use-shell-user.ts +0 -1
  73. package/packages/bundled-features/src/auth-email-password/web/user-menu.tsx +0 -1
  74. package/packages/bundled-features/src/auth-email-password/web/verify-email-screen.tsx +0 -1
  75. package/packages/bundled-features/src/auth-mfa/changes.json +6 -0
  76. package/packages/bundled-features/src/auth-mfa/errors.ts +2 -2
  77. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +1 -1
  78. package/packages/bundled-features/src/auth-mfa/handlers/enable-start-preauth.write.ts +1 -1
  79. package/packages/bundled-features/src/auth-mfa/handlers/enable-start.write.ts +1 -1
  80. package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +1 -1
  81. package/packages/bundled-features/src/auth-mfa/index.ts +8 -8
  82. package/packages/bundled-features/src/auth-mfa/mfa-status-checker.ts +4 -4
  83. package/packages/bundled-features/src/auth-mfa/screens.ts +1 -1
  84. package/packages/bundled-features/src/auth-mfa/web/client-plugin.ts +0 -1
  85. package/packages/bundled-features/src/auth-mfa/{constants.ts → web/constants.ts} +0 -1
  86. package/packages/bundled-features/src/auth-mfa/web/i18n.ts +0 -1
  87. package/packages/bundled-features/src/auth-mfa/web/index.ts +0 -1
  88. package/packages/bundled-features/src/auth-mfa/web/mfa-client.ts +0 -1
  89. package/packages/bundled-features/src/auth-mfa/web/mfa-disable-dialog.tsx +1 -2
  90. package/packages/bundled-features/src/auth-mfa/web/mfa-recovery-codes-reveal.tsx +0 -1
  91. package/packages/bundled-features/src/auth-mfa/web/mfa-regenerate-recovery-dialog.tsx +1 -2
  92. package/packages/bundled-features/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +21 -23
  93. package/packages/bundled-features/src/auth-mfa/web/mfa-verify-screen.tsx +0 -1
  94. package/packages/bundled-features/src/billing-foundation/changes.json +18 -0
  95. package/packages/bundled-features/src/billing-foundation/checkout-core.ts +3 -3
  96. package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-text.ts +2 -2
  97. package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +11 -3
  98. package/packages/bundled-features/src/billing-foundation/consumer-protection/mail-notifications.ts +11 -1
  99. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +1 -1
  100. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts +12 -1
  101. package/packages/bundled-features/src/billing-foundation/consumer-protection/stored-user-email.ts +12 -0
  102. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-mail.ts +6 -1
  103. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-notify.ts +1 -1
  104. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-pages.ts +167 -28
  105. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +102 -3
  106. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +43 -12
  107. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-revoke.ts +202 -0
  108. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-texts.ts +70 -0
  109. package/packages/bundled-features/src/billing-foundation/events.ts +20 -2
  110. package/packages/bundled-features/src/billing-foundation/feature.ts +5 -5
  111. package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +82 -9
  112. package/packages/bundled-features/src/billing-foundation/handlers/switch-plan.write.ts +1 -1
  113. package/packages/bundled-features/src/billing-foundation/handlers/sync-subscription.write.ts +1 -1
  114. package/packages/bundled-features/src/billing-foundation/index.ts +27 -27
  115. package/packages/bundled-features/src/billing-foundation/plan-catalog.ts +7 -7
  116. package/packages/bundled-features/src/billing-foundation/screens.ts +1 -1
  117. package/packages/bundled-features/src/billing-foundation/subscription-tier-sync.ts +1 -1
  118. package/packages/bundled-features/src/billing-foundation/types.ts +17 -3
  119. package/packages/bundled-features/src/billing-foundation/validate-options.ts +3 -3
  120. package/packages/bundled-features/src/billing-foundation/web/billing-contact-plan.tsx +1 -3
  121. package/packages/bundled-features/src/billing-foundation/web/billing-plans-panel.tsx +5 -7
  122. package/packages/bundled-features/src/billing-foundation/web/cancel-contract-dialog.tsx +1 -3
  123. package/packages/bundled-features/src/billing-foundation/web/checkout-consent-dialog.tsx +1 -3
  124. package/packages/bundled-features/src/billing-foundation/web/client-plugin.tsx +1 -2
  125. package/packages/bundled-features/src/billing-foundation/{constants.ts → web/constants.ts} +37 -31
  126. package/packages/bundled-features/src/billing-foundation/{consumer-protection → web/consumer-protection}/consent-locale.ts +0 -1
  127. package/packages/bundled-features/src/billing-foundation/{contact-card.ts → web/contact-card.ts} +0 -2
  128. package/packages/bundled-features/src/billing-foundation/web/index.ts +14 -15
  129. package/packages/bundled-features/src/billing-foundation/web/localized-params.ts +0 -2
  130. package/packages/bundled-features/src/billing-foundation/webhook-handler.ts +5 -2
  131. package/packages/bundled-features/src/cap-counter/changes.json +12 -0
  132. package/packages/bundled-features/src/cap-counter/stock-cap-guard.ts +9 -0
  133. package/packages/bundled-features/src/cap-overview/feature.ts +1 -1
  134. package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +1 -1
  135. package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +2 -2
  136. package/packages/bundled-features/src/cap-overview/index.ts +9 -9
  137. package/packages/bundled-features/src/cap-overview/screens.ts +2 -2
  138. package/packages/bundled-features/src/cap-overview/web/cap-cards-panel.tsx +1 -2
  139. package/packages/bundled-features/src/cap-overview/web/cap-usage-bar.tsx +1 -2
  140. package/packages/bundled-features/src/cap-overview/web/cap-usage-cell.tsx +0 -2
  141. package/packages/bundled-features/src/cap-overview/web/client-plugin.tsx +3 -4
  142. package/packages/bundled-features/src/cap-overview/{constants.ts → web/constants.ts} +0 -1
  143. package/packages/bundled-features/src/cap-overview/web/index.ts +0 -1
  144. package/packages/bundled-features/src/cap-overview/{usage-math.ts → web/usage-math.ts} +1 -2
  145. package/packages/bundled-features/src/compliance-profiles/feature.ts +5 -5
  146. package/packages/bundled-features/src/compliance-profiles/index.ts +6 -6
  147. package/packages/bundled-features/src/compliance-profiles/web/client-plugin.tsx +2 -3
  148. package/packages/bundled-features/src/compliance-profiles/web/compliance-profile-catalog.tsx +1 -2
  149. package/packages/bundled-features/src/compliance-profiles/{constants.ts → web/constants.ts} +0 -1
  150. package/packages/bundled-features/src/compliance-profiles/web/i18n.ts +0 -1
  151. package/packages/bundled-features/src/compliance-profiles/web/index.ts +0 -1
  152. package/packages/bundled-features/src/config/index.ts +6 -6
  153. package/packages/bundled-features/src/config/web/client-plugin.ts +1 -2
  154. package/packages/bundled-features/src/config/{constants.ts → web/constants.ts} +0 -1
  155. package/packages/bundled-features/src/config/web/i18n.ts +1 -1
  156. package/packages/bundled-features/src/config/web/index.ts +1 -3
  157. package/packages/bundled-features/src/config/write-helpers.ts +1 -1
  158. package/packages/bundled-features/src/custom-fields/feature.ts +8 -8
  159. package/packages/bundled-features/src/custom-fields/handlers/clear-custom-field.write.ts +1 -1
  160. package/packages/bundled-features/src/custom-fields/handlers/define-tenant-field.write.ts +1 -1
  161. package/packages/bundled-features/src/custom-fields/handlers/delete-system-field.write.ts +1 -1
  162. package/packages/bundled-features/src/custom-fields/handlers/delete-tenant-field.write.ts +4 -4
  163. package/packages/bundled-features/src/custom-fields/handlers/set-custom-field.write.ts +1 -1
  164. package/packages/bundled-features/src/custom-fields/handlers/update-tenant-field.write.ts +1 -1
  165. package/packages/bundled-features/src/custom-fields/index.ts +8 -8
  166. package/packages/bundled-features/src/custom-fields/lib/value-schema.ts +1 -1
  167. package/packages/bundled-features/src/custom-fields/schemas.ts +1 -1
  168. package/packages/bundled-features/src/custom-fields/web/client-plugin.tsx +1 -2
  169. package/packages/bundled-features/src/custom-fields/{constants.ts → web/constants.ts} +18 -19
  170. package/packages/bundled-features/src/custom-fields/web/custom-fields-form-section.tsx +1 -2
  171. package/packages/bundled-features/src/custom-fields/web/i18n.ts +0 -1
  172. package/packages/bundled-features/src/custom-fields/web/index.ts +2 -3
  173. package/packages/bundled-features/src/custom-fields/wire-for-entity.ts +1 -2
  174. package/packages/bundled-features/src/delivery/address-opt-out.ts +1 -1
  175. package/packages/bundled-features/src/delivery/attempt-log-retention.ts +24 -8
  176. package/packages/bundled-features/src/delivery/changes.json +6 -0
  177. package/packages/bundled-features/src/delivery/constants.ts +2 -2
  178. package/packages/bundled-features/src/delivery/i18n.ts +1 -1
  179. package/packages/bundled-features/src/delivery/unsubscribe-page.ts +1 -1
  180. package/packages/bundled-features/src/delivery/web/client-plugin.tsx +6 -7
  181. package/packages/bundled-features/src/delivery/web/delivery-channel-cell.tsx +0 -1
  182. package/packages/bundled-features/src/delivery/web/delivery-error-cell.tsx +0 -1
  183. package/packages/bundled-features/src/delivery/web/delivery-status-cell.tsx +1 -2
  184. package/packages/bundled-features/src/delivery/web/delivery-time-cell.tsx +0 -1
  185. package/packages/bundled-features/src/delivery/web/delivery-type-cell.tsx +0 -1
  186. package/packages/bundled-features/src/delivery/web/index.ts +0 -1
  187. package/packages/bundled-features/src/delivery/{public-names.ts → web/public-names.ts} +0 -1
  188. package/packages/bundled-features/src/document-ingest-foundation/changes.json +6 -0
  189. package/packages/bundled-features/src/document-ingest-foundation/events.ts +4 -0
  190. package/packages/bundled-features/src/document-ingest-foundation/executor.ts +2 -1
  191. package/packages/bundled-features/src/document-ingest-foundation/feature.ts +18 -4
  192. package/packages/bundled-features/src/document-ingest-foundation/index.ts +1 -0
  193. package/packages/bundled-features/src/feature-toggles/feature.ts +7 -7
  194. package/packages/bundled-features/src/feature-toggles/handlers/list.query.ts +1 -1
  195. package/packages/bundled-features/src/feature-toggles/handlers/registered.query.ts +1 -1
  196. package/packages/bundled-features/src/feature-toggles/handlers/set.write.ts +3 -3
  197. package/packages/bundled-features/src/feature-toggles/index.ts +6 -6
  198. package/packages/bundled-features/src/feature-toggles/web/client-plugin.tsx +1 -2
  199. package/packages/bundled-features/src/feature-toggles/{constants.ts → web/constants.ts} +0 -1
  200. package/packages/bundled-features/src/feature-toggles/web/i18n.ts +0 -1
  201. package/packages/bundled-features/src/feature-toggles/web/index.ts +0 -1
  202. package/packages/bundled-features/src/files/changes.json +7 -0
  203. package/packages/bundled-features/src/folders/feature.ts +1 -1
  204. package/packages/bundled-features/src/folders/handlers/clear-folder.write.ts +1 -1
  205. package/packages/bundled-features/src/folders/handlers/delete-folder.write.ts +1 -1
  206. package/packages/bundled-features/src/folders/handlers/set-folder.write.ts +1 -1
  207. package/packages/bundled-features/src/folders/index.ts +8 -8
  208. package/packages/bundled-features/src/folders/web/client-plugin.tsx +1 -3
  209. package/packages/bundled-features/src/folders/{constants.ts → web/constants.ts} +0 -1
  210. package/packages/bundled-features/src/folders/web/folder-manager.tsx +1 -2
  211. package/packages/bundled-features/src/folders/web/folder-section.tsx +1 -2
  212. package/packages/bundled-features/src/folders/web/i18n.ts +0 -1
  213. package/packages/bundled-features/src/folders/web/index.ts +1 -2
  214. package/packages/bundled-features/src/folders/web/tree.ts +0 -1
  215. package/packages/bundled-features/src/form-draft/changes.json +7 -0
  216. package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +25 -11
  217. package/packages/bundled-features/src/form-draft/decrypt-draft.ts +24 -0
  218. package/packages/bundled-features/src/form-draft/entity.ts +6 -9
  219. package/packages/bundled-features/src/form-draft/feature.ts +1 -1
  220. package/packages/bundled-features/src/form-draft/handlers/cleanup.job.ts +26 -5
  221. package/packages/bundled-features/src/form-draft/handlers/discard.write.ts +1 -1
  222. package/packages/bundled-features/src/form-draft/handlers/get.query.ts +6 -7
  223. package/packages/bundled-features/src/form-draft/handlers/list.query.ts +13 -7
  224. package/packages/bundled-features/src/form-draft/handlers/save.write.ts +5 -5
  225. package/packages/bundled-features/src/form-draft/index.ts +8 -8
  226. package/packages/bundled-features/src/form-draft/lookup.ts +10 -4
  227. package/packages/bundled-features/src/form-draft/schemas.ts +1 -1
  228. package/packages/bundled-features/src/form-draft/{constants.ts → web/constants.ts} +0 -1
  229. package/packages/bundled-features/src/form-draft-user-data/hooks.ts +2 -2
  230. package/packages/bundled-features/src/inbound-mail-foundation/changes.json +6 -0
  231. package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-accounts.query.ts +11 -4
  232. package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +10 -10
  233. package/packages/bundled-features/src/jobs/feature.ts +7 -7
  234. package/packages/bundled-features/src/jobs/handlers/retry.write.ts +1 -1
  235. package/packages/bundled-features/src/jobs/handlers/trigger.write.ts +1 -1
  236. package/packages/bundled-features/src/jobs/index.ts +1 -1
  237. package/packages/bundled-features/src/jobs/web/client-plugin.tsx +2 -3
  238. package/packages/bundled-features/src/jobs/{constants.ts → web/constants.ts} +0 -1
  239. package/packages/bundled-features/src/jobs/web/default-translations.ts +7 -0
  240. package/packages/bundled-features/src/jobs/web/i18n.ts +53 -7
  241. package/packages/bundled-features/src/jobs/web/index.ts +0 -1
  242. package/packages/bundled-features/src/ledger/changes.json +7 -0
  243. package/packages/bundled-features/src/ledger/entity.ts +1 -1
  244. package/packages/bundled-features/src/ledger/feature.ts +1 -1
  245. package/packages/bundled-features/src/ledger/handlers/confirm-schedule-period.write.ts +2 -10
  246. package/packages/bundled-features/src/ledger/handlers/create-transaction.write.ts +1 -1
  247. package/packages/bundled-features/src/ledger/handlers/reports.query.ts +1 -1
  248. package/packages/bundled-features/src/ledger/handlers/reverse-transaction.write.ts +1 -1
  249. package/packages/bundled-features/src/ledger/index.ts +13 -13
  250. package/packages/bundled-features/src/ledger/recurring.ts +1 -1
  251. package/packages/bundled-features/src/ledger/reports.ts +1 -1
  252. package/packages/bundled-features/src/ledger/schemas.ts +1 -1
  253. package/packages/bundled-features/src/ledger/{constants.ts → web/constants.ts} +0 -1
  254. package/packages/bundled-features/src/ledger/web/index.ts +11 -12
  255. package/packages/bundled-features/src/legal-pages/README.md +1 -1
  256. package/packages/bundled-features/src/legal-pages/feature.ts +5 -5
  257. package/packages/bundled-features/src/legal-pages/index.ts +11 -11
  258. package/packages/bundled-features/src/legal-pages/lib/boot-check.ts +1 -1
  259. package/packages/bundled-features/src/legal-pages/lib/validate-routes.ts +1 -1
  260. package/packages/bundled-features/src/legal-pages/web/client-plugin.ts +1 -2
  261. package/packages/bundled-features/src/legal-pages/{constants.ts → web/constants.ts} +6 -8
  262. package/packages/bundled-features/src/legal-pages/web/index.ts +0 -1
  263. package/packages/bundled-features/src/mail-foundation/changes.json +6 -0
  264. package/packages/bundled-features/src/mail-foundation/i18n.ts +3 -2
  265. package/packages/bundled-features/src/mail-transport-smtp/i18n.ts +1 -1
  266. package/packages/bundled-features/src/managed-pages/feature.ts +1 -1
  267. package/packages/bundled-features/src/managed-pages/web/client-plugin.tsx +1 -2
  268. package/packages/bundled-features/src/managed-pages/web/default-translations.ts +17 -0
  269. package/packages/bundled-features/src/managed-pages/web/i18n.ts +59 -16
  270. package/packages/bundled-features/src/managed-pages/web/index.ts +1 -2
  271. package/packages/bundled-features/src/metrics/compute.ts +1 -1
  272. package/packages/bundled-features/src/metrics/feature.ts +6 -6
  273. package/packages/bundled-features/src/metrics/index.ts +8 -8
  274. package/packages/bundled-features/src/metrics/types.ts +1 -1
  275. package/packages/bundled-features/src/metrics/validate.ts +1 -1
  276. package/packages/bundled-features/src/metrics/{constants.ts → web/constants.ts} +0 -1
  277. package/packages/bundled-features/src/metrics/window.ts +1 -1
  278. package/packages/bundled-features/src/notes-history/feature.ts +1 -1
  279. package/packages/bundled-features/src/notes-history/handlers/add-note.write.ts +1 -1
  280. package/packages/bundled-features/src/notes-history/index.ts +8 -8
  281. package/packages/bundled-features/src/notes-history/web/client-plugin.tsx +1 -3
  282. package/packages/bundled-features/src/notes-history/{constants.ts → web/constants.ts} +0 -1
  283. package/packages/bundled-features/src/notes-history/web/i18n.ts +0 -1
  284. package/packages/bundled-features/src/notes-history/web/index.ts +2 -3
  285. package/packages/bundled-features/src/notes-history/web/notes-section.tsx +2 -3
  286. package/packages/bundled-features/src/personal-access-tokens/feature.ts +12 -10
  287. package/packages/bundled-features/src/personal-access-tokens/handlers/available-scopes.query.ts +1 -1
  288. package/packages/bundled-features/src/personal-access-tokens/handlers/create.write.ts +1 -1
  289. package/packages/bundled-features/src/personal-access-tokens/handlers/revoke.write.ts +1 -1
  290. package/packages/bundled-features/src/personal-access-tokens/hash.ts +1 -1
  291. package/packages/bundled-features/src/personal-access-tokens/i18n.ts +2 -2
  292. package/packages/bundled-features/src/personal-access-tokens/index.ts +5 -5
  293. package/packages/bundled-features/src/personal-access-tokens/resolver.ts +1 -1
  294. package/packages/bundled-features/src/personal-access-tokens/schema/api-token.ts +1 -1
  295. package/packages/bundled-features/src/personal-access-tokens/screens.ts +2 -2
  296. package/packages/bundled-features/src/personal-access-tokens/{constants.ts → web/constants.ts} +0 -1
  297. package/packages/bundled-features/src/personal-access-tokens/{scopes.ts → web/scopes.ts} +0 -1
  298. package/packages/bundled-features/src/presets/changes.json +7 -0
  299. package/packages/bundled-features/src/presets/index.ts +0 -1
  300. package/packages/bundled-features/src/presets/security-baseline.ts +1 -1
  301. package/packages/bundled-features/src/renderer-foundation/types.ts +1 -1
  302. package/packages/bundled-features/src/secrets/feature.ts +1 -1
  303. package/packages/bundled-features/src/secrets/handlers/delete.write.ts +1 -1
  304. package/packages/bundled-features/src/secrets/handlers/list.query.ts +1 -1
  305. package/packages/bundled-features/src/secrets/handlers/set.write.ts +1 -1
  306. package/packages/bundled-features/src/secrets/i18n.ts +1 -1
  307. package/packages/bundled-features/src/secrets/index.ts +8 -8
  308. package/packages/bundled-features/src/secrets/{constants.ts → web/constants.ts} +0 -1
  309. package/packages/bundled-features/src/secrets/write-gate.ts +1 -1
  310. package/packages/bundled-features/src/sessions/feature.ts +7 -4
  311. package/packages/bundled-features/src/sessions/{i18n.ts → web/i18n.ts} +0 -1
  312. package/packages/bundled-features/src/shared/index.ts +6 -1
  313. package/packages/bundled-features/src/shared/purge-files-reaching-subject.ts +10 -2
  314. package/packages/bundled-features/src/shared/web/translate-or-raw.ts +0 -1
  315. package/packages/bundled-features/src/subscription-stripe/feature.ts +2 -0
  316. package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +19 -0
  317. package/packages/bundled-features/src/subscription-stripe/verify-webhook.ts +1 -0
  318. package/packages/bundled-features/src/tags/changes.json +6 -0
  319. package/packages/bundled-features/src/tags/feature.ts +1 -1
  320. package/packages/bundled-features/src/tags/handlers/assign-tag.write.ts +11 -7
  321. package/packages/bundled-features/src/tags/handlers/create-tag.write.ts +1 -1
  322. package/packages/bundled-features/src/tags/handlers/delete-tag.write.ts +4 -1
  323. package/packages/bundled-features/src/tags/handlers/remove-tag.write.ts +1 -1
  324. package/packages/bundled-features/src/tags/handlers/update-tag.write.ts +1 -1
  325. package/packages/bundled-features/src/tags/index.ts +12 -12
  326. package/packages/bundled-features/src/tags/screens.ts +1 -1
  327. package/packages/bundled-features/src/tags/web/client-plugin.tsx +1 -3
  328. package/packages/bundled-features/src/tags/{constants.ts → web/constants.ts} +4 -1
  329. package/packages/bundled-features/src/tags/web/entity-tags.tsx +1 -2
  330. package/packages/bundled-features/src/tags/web/i18n.ts +2 -2
  331. package/packages/bundled-features/src/tags/web/index.ts +2 -3
  332. package/packages/bundled-features/src/tags/web/tag-chip.tsx +0 -1
  333. package/packages/bundled-features/src/tags/web/tag-filter.tsx +1 -2
  334. package/packages/bundled-features/src/tags/web/tag-manager.tsx +5 -4
  335. package/packages/bundled-features/src/tags/web/tag-picker.tsx +0 -1
  336. package/packages/bundled-features/src/tags/web/tag-section.tsx +1 -2
  337. package/packages/bundled-features/src/tags/web/tags-cell.tsx +1 -2
  338. package/packages/bundled-features/src/template-resolver/README.md +1 -1
  339. package/packages/bundled-features/src/template-resolver/api.ts +75 -28
  340. package/packages/bundled-features/src/template-resolver/changes.json +63 -0
  341. package/packages/bundled-features/src/template-resolver/compile-document-body.ts +62 -4
  342. package/packages/bundled-features/src/template-resolver/compile-steps-block.ts +2 -2
  343. package/packages/bundled-features/src/template-resolver/document-ops.ts +5 -5
  344. package/packages/bundled-features/src/template-resolver/document-sanitizer.ts +39 -4
  345. package/packages/bundled-features/src/template-resolver/feature.ts +88 -38
  346. package/packages/bundled-features/src/template-resolver/handlers/by-slug.query.ts +6 -1
  347. package/packages/bundled-features/src/template-resolver/handlers/by-tenant.query.ts +6 -2
  348. package/packages/bundled-features/src/template-resolver/handlers/collection-item.query.ts +1 -1
  349. package/packages/bundled-features/src/template-resolver/handlers/collection-list.query.ts +1 -1
  350. package/packages/bundled-features/src/template-resolver/handlers/collection-set.write.ts +20 -7
  351. package/packages/bundled-features/src/template-resolver/handlers/collection-shared.ts +0 -6
  352. package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +69 -60
  353. package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +7 -1
  354. package/packages/bundled-features/src/template-resolver/handlers/discard-draft.write.ts +37 -14
  355. package/packages/bundled-features/src/template-resolver/handlers/document-base.ts +8 -2
  356. package/packages/bundled-features/src/template-resolver/handlers/document-blocks.query.ts +2 -2
  357. package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +5 -2
  358. package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +4 -2
  359. package/packages/bundled-features/src/template-resolver/handlers/import-bundle.write.ts +37 -28
  360. package/packages/bundled-features/src/template-resolver/handlers/layout-source.query.ts +10 -5
  361. package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +5 -2
  362. package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +132 -70
  363. package/packages/bundled-features/src/template-resolver/handlers/patch-draft.write.ts +106 -97
  364. package/packages/bundled-features/src/template-resolver/handlers/reset-to-system.write.ts +62 -49
  365. package/packages/bundled-features/src/template-resolver/handlers/resource-link-pdf.write.ts +44 -29
  366. package/packages/bundled-features/src/template-resolver/handlers/resource-remove.write.ts +52 -36
  367. package/packages/bundled-features/src/template-resolver/handlers/resource-shared.ts +1 -1
  368. package/packages/bundled-features/src/template-resolver/handlers/resource-upload.write.ts +127 -107
  369. package/packages/bundled-features/src/template-resolver/handlers/send-test-shared.ts +38 -0
  370. package/packages/bundled-features/src/template-resolver/handlers/send-test-status.query.ts +31 -0
  371. package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +106 -112
  372. package/packages/bundled-features/src/template-resolver/handlers/set.write.ts +111 -104
  373. package/packages/bundled-features/src/template-resolver/handlers/shared.ts +1 -1
  374. package/packages/bundled-features/src/template-resolver/handlers/template-gates.ts +6 -6
  375. package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +2 -2
  376. package/packages/bundled-features/src/template-resolver/handlers/toggle-status.write.ts +56 -28
  377. package/packages/bundled-features/src/template-resolver/handlers/upsert-tenant.write.ts +38 -29
  378. package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +8 -2
  379. package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +4 -2
  380. package/packages/bundled-features/src/template-resolver/i18n.ts +25 -0
  381. package/packages/bundled-features/src/template-resolver/index.ts +68 -60
  382. package/packages/bundled-features/src/template-resolver/layout-appearance.ts +1 -1
  383. package/packages/bundled-features/src/template-resolver/layout-regions.ts +1 -1
  384. package/packages/bundled-features/src/template-resolver/seeding.ts +16 -7
  385. package/packages/bundled-features/src/template-resolver/store-template-resource.ts +2 -2
  386. package/packages/bundled-features/src/template-resolver/table.ts +1 -1
  387. package/packages/bundled-features/src/template-resolver/template-bundle.ts +2 -2
  388. package/packages/bundled-features/src/template-resolver/template-delivery-policy.ts +82 -0
  389. package/packages/bundled-features/src/template-resolver/template-draft.ts +1 -1
  390. package/packages/bundled-features/src/template-resolver/template-edit-policy.ts +91 -0
  391. package/packages/bundled-features/src/template-resolver/template-meta.ts +2 -2
  392. package/packages/bundled-features/src/template-resolver/template-preview.ts +22 -12
  393. package/packages/bundled-features/src/template-resolver/template-usage.ts +1 -1
  394. package/packages/bundled-features/src/template-resolver/template-validation.ts +33 -12
  395. package/packages/bundled-features/src/template-resolver/testing.ts +2 -2
  396. package/packages/bundled-features/src/template-resolver/user-content-table.ts +1 -1
  397. package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +4 -5
  398. package/packages/bundled-features/src/template-resolver/{constants.ts → web/constants.ts} +10 -10
  399. package/packages/bundled-features/src/template-resolver/{document-blocks.ts → web/document-blocks.ts} +3 -2
  400. package/packages/bundled-features/src/template-resolver/web/document-editor-extensions.ts +31 -3
  401. package/packages/bundled-features/src/template-resolver/web/document-editor.tsx +0 -1
  402. package/packages/bundled-features/src/template-resolver/web/document-images.ts +28 -0
  403. package/packages/bundled-features/src/template-resolver/{document-schema.ts → web/document-schema.ts} +5 -1
  404. package/packages/bundled-features/src/template-resolver/{document-steps.ts → web/document-steps.ts} +0 -1
  405. package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +48 -11
  406. package/packages/bundled-features/src/template-resolver/web/document-toolbar-blocks.ts +4 -3
  407. package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +1 -2
  408. package/packages/bundled-features/src/template-resolver/web/i18n.ts +8 -1
  409. package/packages/bundled-features/src/template-resolver/web/index.ts +13 -14
  410. package/packages/bundled-features/src/template-resolver/{mail-constants.ts → web/mail-constants.ts} +0 -1
  411. package/packages/bundled-features/src/template-resolver/{pdf-constants.ts → web/pdf-constants.ts} +0 -1
  412. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor-lazy.tsx +0 -1
  413. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +5 -4
  414. package/packages/bundled-features/src/template-resolver/{placeholder-text.ts → web/placeholder-text.ts} +0 -1
  415. package/packages/bundled-features/src/template-resolver/{qualified-names.ts → web/qualified-names.ts} +2 -1
  416. package/packages/bundled-features/src/template-resolver/web/use-stable-by-key.ts +0 -1
  417. package/packages/bundled-features/src/tenant/handlers/add-member.write.ts +1 -1
  418. package/packages/bundled-features/src/tenant/index.ts +7 -7
  419. package/packages/bundled-features/src/tenant/invitation-table.ts +1 -1
  420. package/packages/bundled-features/src/tenant/invited-membership.ts +1 -1
  421. package/packages/bundled-features/src/tenant/last-tenant-admin.ts +1 -1
  422. package/packages/bundled-features/src/tenant/member-role-options.ts +1 -1
  423. package/packages/bundled-features/src/tenant/screens.ts +2 -2
  424. package/packages/bundled-features/src/tenant/web/client-plugin.tsx +1 -2
  425. package/packages/bundled-features/src/tenant/{constants.ts → web/constants.ts} +3 -6
  426. package/packages/bundled-features/src/tenant/web/index.ts +0 -1
  427. package/packages/bundled-features/src/tenant/web/member-roles-cell.tsx +0 -1
  428. package/packages/bundled-features/src/tenant/web/member-status-cell.tsx +0 -1
  429. package/packages/bundled-features/src/tenant-lifecycle/stages.ts +2 -2
  430. package/packages/bundled-features/src/tier-engine/entity.ts +1 -1
  431. package/packages/bundled-features/src/tier-engine/feature.ts +7 -7
  432. package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +1 -1
  433. package/packages/bundled-features/src/tier-engine/index.ts +8 -8
  434. package/packages/bundled-features/src/tier-engine/web/client-plugin.tsx +2 -3
  435. package/packages/bundled-features/src/tier-engine/{constants.ts → web/constants.ts} +2 -3
  436. package/packages/bundled-features/src/tier-engine/{i18n.ts → web/i18n.ts} +0 -1
  437. package/packages/bundled-features/src/tier-engine/web/index.ts +0 -1
  438. package/packages/bundled-features/src/user/handlers/create.write.ts +1 -1
  439. package/packages/bundled-features/src/user/handlers/update-roles.ts +1 -1
  440. package/packages/bundled-features/src/user/handlers/update.write.ts +1 -1
  441. package/packages/bundled-features/src/user/index.ts +1 -1
  442. package/packages/bundled-features/src/user/invited-global-roles.ts +1 -1
  443. package/packages/bundled-features/src/user/schema/user.ts +1 -1
  444. package/packages/bundled-features/src/user/{constants.ts → web/constants.ts} +0 -1
  445. package/packages/bundled-features/src/user-data-rights/changes.json +14 -0
  446. package/packages/bundled-features/src/user-data-rights/env.ts +1 -1
  447. package/packages/bundled-features/src/user-data-rights/feature.ts +9 -9
  448. package/packages/bundled-features/src/user-data-rights/index.ts +1 -1
  449. package/packages/bundled-features/src/user-data-rights/lib/resolve-tenant-model.ts +1 -1
  450. package/packages/bundled-features/src/user-data-rights/mail-notifications.ts +1 -1
  451. package/packages/bundled-features/src/user-data-rights/web/client-plugin.tsx +2 -3
  452. package/packages/bundled-features/src/user-data-rights/web/confirm-deletion-screen.tsx +0 -1
  453. package/packages/bundled-features/src/user-data-rights/{constants.ts → web/constants.ts} +11 -12
  454. package/packages/bundled-features/src/user-data-rights/web/default-translations.ts +91 -0
  455. package/packages/bundled-features/src/user-data-rights/web/i18n.ts +41 -91
  456. package/packages/bundled-features/src/user-data-rights/web/index.ts +1 -2
  457. package/packages/bundled-features/src/user-data-rights/web/privacy-center-screen.tsx +1 -2
  458. package/packages/bundled-features/src/user-data-rights/web/public-deletion-gate.tsx +0 -1
  459. package/packages/bundled-features/src/user-data-rights/web/request-deletion-screen.tsx +0 -1
  460. package/packages/bundled-features/src/user-data-rights-defaults/hooks/file-ref.userdata-hook.ts +10 -1
  461. package/packages/bundled-features/src/user-profile/feature.ts +2 -2
  462. package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +1 -1
  463. package/packages/bundled-features/src/user-profile/index.ts +3 -2
  464. package/packages/bundled-features/src/user-profile/web/client-plugin.ts +2 -3
  465. package/packages/bundled-features/src/user-profile/{constants.ts → web/constants.ts} +0 -1
  466. package/packages/bundled-features/src/user-profile/{i18n.ts → web/i18n.ts} +0 -1
  467. package/packages/bundled-features/src/user-profile/web/profile-screen.tsx +2 -3
  468. package/packages/bundled-features/src/waitlist/changes.json +6 -0
  469. package/packages/bundled-features/src/waitlist/entity.ts +1 -1
  470. package/packages/bundled-features/src/waitlist/feature.ts +5 -5
  471. package/packages/bundled-features/src/waitlist/handlers/export-purge.write.ts +3 -3
  472. package/packages/bundled-features/src/waitlist/handlers/invite.write.ts +5 -1
  473. package/packages/bundled-features/src/waitlist/handlers/reject.write.ts +1 -1
  474. package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +3 -3
  475. package/packages/bundled-features/src/waitlist/import.ts +1 -1
  476. package/packages/bundled-features/src/waitlist/index.ts +11 -11
  477. package/packages/bundled-features/src/waitlist/legacy-migrate.ts +1 -1
  478. package/packages/bundled-features/src/waitlist/link-on-signup.ts +1 -1
  479. package/packages/bundled-features/src/waitlist/mail.ts +7 -6
  480. package/packages/bundled-features/src/waitlist/payloads.ts +1 -1
  481. package/packages/bundled-features/src/waitlist/screens.ts +1 -1
  482. package/packages/bundled-features/src/waitlist/{constants.ts → web/constants.ts} +0 -1
  483. package/samples/apps/styleguide/src/features/content/web.tsx +0 -1
  484. package/samples/apps/ui-walkthrough/src/app/client.tsx +2 -0
  485. package/samples/apps/ui-walkthrough/src/app/help-action.tsx +37 -0
  486. package/samples/apps/ui-walkthrough/src/app/seed-rental.ts +17 -1
  487. package/samples/apps/ui-walkthrough/src/app/seed-templates.ts +13 -0
  488. package/samples/apps/ui-walkthrough/src/app/server.ts +2 -1
  489. package/samples/apps/ui-walkthrough/src/app/shell.tsx +6 -0
  490. package/samples/apps/ui-walkthrough/src/app/template-seed-data.ts +20 -0
  491. package/samples/apps/ui-walkthrough/src/features/billing/feature.ts +35 -7
  492. package/samples/apps/ui-walkthrough/src/features/billing/i18n.ts +6 -0
  493. package/samples/apps/ui-walkthrough/src/features/rental/entities.ts +27 -0
  494. package/samples/apps/ui-walkthrough/src/features/rental/feature.ts +120 -2
  495. package/samples/apps/ui-walkthrough/src/features/rental/i18n.ts +94 -0
  496. package/samples/apps/ui-walkthrough/src/features/rental/invoice-receive.ts +19 -0
  497. package/samples/apps/ui-walkthrough/src/features/rental/lease-support.ts +32 -1
  498. package/samples/apps/ui-walkthrough/src/features/rental/qualified-names.ts +5 -0
  499. package/samples/apps/ui-walkthrough/src/features/rental/receipts-upload.ts +70 -0
  500. package/samples/apps/ui-walkthrough/src/features/rental/screens.ts +156 -0
  501. package/samples/apps/ui-walkthrough/src/features/subscription-plans/feature.ts +103 -0
  502. package/samples/apps/ui-walkthrough/src/features/subscription-plans/i18n.ts +36 -0
  503. package/samples/apps/ui-walkthrough/src/features/subscription-plans/index.ts +2 -0
  504. package/samples/apps/ui-walkthrough/src/features/subscription-plans/seed.ts +21 -0
  505. package/samples/apps/ui-walkthrough/src/features/tasks/i18n.ts +8 -0
  506. package/samples/apps/ui-walkthrough/src/run-config.ts +43 -3
  507. package/samples/apps/use-all-bundled/e2e-billing-gallery/server.ts +1 -1
  508. package/samples/apps/use-all-bundled/feature-manifest.json +5 -5
  509. package/samples/apps/use-all-bundled/scripts/check-coverage.ts +1 -1
  510. package/samples/apps/use-all-bundled/src/app/dev-app-options.ts +3 -1
  511. package/samples/recipes/compose-stacks/README.md +1 -2
  512. package/samples/recipes/delivery-notifications/README.md +1 -1
  513. package/samples/recipes/embedded/README.md +8 -0
  514. package/samples/recipes/embedded/src/feature.ts +8 -0
  515. package/samples/recipes/embedded/src/handlers/invoice-receive.write.ts +37 -0
  516. package/samples/recipes/embedded/src/screens/invoice-receive.screen.ts +56 -0
  517. package/samples/recipes/legal-pages/README.md +2 -2
  518. package/samples/recipes/legal-pages/src/feature.ts +1 -1
  519. package/samples/recipes/reference-data/src/feature.ts +3 -7
  520. package/packages/bundled-features/src/admin-shell/i18n.ts +0 -54
  521. package/packages/bundled-features/src/audit/i18n.ts +0 -25
  522. package/packages/bundled-features/src/auth-mfa/web/qrcode-browser.d.ts +0 -11
  523. package/packages/bundled-features/src/jobs/i18n.ts +0 -55
  524. package/packages/bundled-features/src/managed-pages/i18n.ts +0 -63
  525. package/packages/bundled-features/src/presets/dsgvo-self-service.ts +0 -35
  526. package/packages/bundled-features/src/user-data-rights/i18n.ts +0 -44
@@ -15,7 +15,6 @@ import {
15
15
  readFileRefContent,
16
16
  } from "../../file-foundation/index.ts";
17
17
  import { attachmentFileNameSchema } from "../../shared/index.ts";
18
- import { TemplateResolverErrors } from "../qualified-names.ts";
19
18
  import {
20
19
  type PreparedTemplateFile,
21
20
  prepareTemplateAttachmentBeforeTransaction,
@@ -26,6 +25,12 @@ import {
26
25
  storePreparedTemplateAttachment,
27
26
  storePreparedTemplateResource,
28
27
  } from "../store-template-resource.ts";
28
+ import {
29
+ refuseTemplateEditById,
30
+ type TemplateEditPolicy,
31
+ templateEditAccess,
32
+ } from "../template-edit-policy.ts";
33
+ import { TemplateResolverErrors } from "../web/qualified-names.ts";
29
34
  import { RESOURCE_PURPOSES, resourceKeySchema, resourceTargetTenantId } from "./resource-shared.ts";
30
35
 
31
36
  type AttachmentTarget = {
@@ -48,127 +53,142 @@ function attachmentTargetOf(input: {
48
53
  // Purpose "image": stores a re-encoded image as a FileRef and, with templateId + key, links it into
49
54
  // the template; without templateId it only creates the FileRef (the branding logo upload path).
50
55
  // Purpose "attachment": stores the file as uploaded and links it as a mail attachment.
51
- export const resourceUploadWrite = defineWriteHandler({
52
- name: "resource-upload",
53
- schema: z
54
- .object({
55
- templateId: z.string().min(1).optional(),
56
- key: resourceKeySchema.optional(),
57
- /** An unclaimed upload of the caller (POST /api/files without entityType). */
58
- sourceFileRefId: z.uuid(),
59
- /** Cross-tenant target — SystemAdmin only. */
60
- tenantIdOverride: z.string().min(1).optional(),
61
- /** "attachment" links a PDF/PNG/JPEG as a file attached to every mail of the template. */
62
- purpose: z.enum(RESOURCE_PURPOSES).default("image"),
63
- /** Name the recipient sees; required for purpose "attachment". */
64
- fileName: z.string().optional(),
65
- })
66
- .refine(
67
- (p) => p.purpose === "attachment" || (p.templateId === undefined) === (p.key === undefined),
68
- {
69
- message: "templateId and key must be given together",
70
- path: ["key"],
71
- },
72
- ),
73
- access: { roles: ["TenantAdmin", "SystemAdmin"] },
74
- description:
75
- "Turns a file uploaded via POST /api/files (multipart, no entityType) into a template resource. With purpose image (default) the upload is checked against the resource-max-bytes cap, re-encoded to a supported image format and stored as a new file; with templateId and key it is linked into that template under the key, without them only the file is created. With purpose attachment, templateId, key and fileName are required: a PDF, PNG or JPEG is stored unchanged and linked into a mail template as a file attached to every mail sent from it, at most 10 per template. A cap above the files route's maxUploadSize has no effect.",
76
- beforeTransaction: async (event, ctx): Promise<BeforeTransactionResult<PreparedTemplateFile>> => {
77
- const { templateId, key, sourceFileRefId, purpose, fileName } = event.payload;
78
- const attachmentTarget =
79
- purpose === "attachment" ? attachmentTargetOf({ templateId, key, fileName }) : null;
80
- if (purpose === "attachment" && attachmentTarget === null) {
81
- return writeFailure(
82
- new UnprocessableError(TemplateResolverErrors.attachmentInvalid, {
83
- i18nKey: "templateResolver.errors.attachment.invalid",
84
- details: { code: "target_required" },
85
- }),
86
- );
87
- }
88
- const maxBytes = await readTemplateResourceMaxBytes(ctx.config, ctx.registry);
89
-
90
- // The source is read from the session tenant only: with tenantIdOverride the upload
91
- // sits in the admin's own tenant while the normalized file lands in the target tenant.
92
- // Only the caller's own unclaimed upload qualifies, so a record-attached image of
93
- // someone else cannot be copied into a template.
94
- let source: Awaited<ReturnType<typeof readFileRefContent>>;
95
- try {
96
- source = await readFileRefContent({
97
- db: ctx.db,
98
- filesForTenant: (tid) => fileContextForTenant(ctx, ctx.user.tenantId, tid),
99
- fileRefId: sourceFileRefId,
100
- allowedTenantIds: [event.user.tenantId],
101
- unclaimedUploadOf: event.user.id,
102
- maxBytes,
103
- });
104
- } catch (cause) {
105
- if (cause instanceof FileRefTooLargeError) {
56
+ export function makeResourceUploadWrite(policy: TemplateEditPolicy) {
57
+ return defineWriteHandler({
58
+ name: "resource-upload",
59
+ schema: z
60
+ .object({
61
+ templateId: z.string().min(1).optional(),
62
+ key: resourceKeySchema.optional(),
63
+ /** An unclaimed upload of the caller (POST /api/files without entityType). */
64
+ sourceFileRefId: z.uuid(),
65
+ /** Cross-tenant target — SystemAdmin only. */
66
+ tenantIdOverride: z.string().min(1).optional(),
67
+ /** "attachment" links a PDF/PNG/JPEG as a file attached to every mail of the template. */
68
+ purpose: z.enum(RESOURCE_PURPOSES).default("image"),
69
+ /** Name the recipient sees; required for purpose "attachment". */
70
+ fileName: z.string().optional(),
71
+ })
72
+ .refine(
73
+ (p) => p.purpose === "attachment" || (p.templateId === undefined) === (p.key === undefined),
74
+ {
75
+ message: "templateId and key must be given together",
76
+ path: ["key"],
77
+ },
78
+ ),
79
+ access: templateEditAccess(policy.tenantEditable),
80
+ description:
81
+ "Turns a file uploaded via POST /api/files (multipart, no entityType) into a template resource. With purpose image (default) the upload is checked against the resource-max-bytes cap, re-encoded to a supported image format and stored as a new file; with templateId and key it is linked into that template under the key, without them only the file is created. With purpose attachment, templateId, key and fileName are required: a PDF, PNG or JPEG is stored unchanged and linked into a mail template as a file attached to every mail sent from it, at most 10 per template. A cap above the files route's maxUploadSize has no effect. Only a SystemAdmin and the roles of the templates the app lists in tenantEditable may use it; such a role may link only into those templates.",
82
+ beforeTransaction: async (
83
+ event,
84
+ ctx,
85
+ ): Promise<BeforeTransactionResult<PreparedTemplateFile>> => {
86
+ const { templateId, key, sourceFileRefId, purpose, fileName } = event.payload;
87
+ const attachmentTarget =
88
+ purpose === "attachment" ? attachmentTargetOf({ templateId, key, fileName }) : null;
89
+ if (purpose === "attachment" && attachmentTarget === null) {
106
90
  return writeFailure(
107
- new UnprocessableError(TemplateResolverErrors.resourceTooLarge, {
108
- i18nKey: "templateResolver.errors.resource.tooLarge",
109
- details: { maxBytes, size: cause.size },
91
+ new UnprocessableError(TemplateResolverErrors.attachmentInvalid, {
92
+ i18nKey: "templateResolver.errors.attachment.invalid",
93
+ details: { code: "target_required" },
110
94
  }),
111
95
  );
112
96
  }
113
- throw cause;
114
- }
115
- if (source === null) return writeFailure(new NotFoundError("file-ref", sourceFileRefId));
97
+ if (templateId !== undefined) {
98
+ const refused = await refuseTemplateEditById(
99
+ ctx.db,
100
+ policy,
101
+ event.user,
102
+ resourceTargetTenantId(event.payload.tenantIdOverride, event.user.tenantId),
103
+ templateId,
104
+ );
105
+ if (refused !== null) return refused;
106
+ }
107
+ const maxBytes = await readTemplateResourceMaxBytes(ctx.config, ctx.registry);
116
108
 
117
- // The source upload stays as an unclaimed file: bundled-features cannot reach the fileRef executor to delete it.
118
- const tenantId = resourceTargetTenantId(event.payload.tenantIdOverride, event.user.tenantId);
119
- if (attachmentTarget !== null) {
120
- return prepareTemplateAttachmentBeforeTransaction({
109
+ // The source is read from the session tenant only: with tenantIdOverride the upload
110
+ // sits in the admin's own tenant while the normalized file lands in the target tenant.
111
+ // Only the caller's own unclaimed upload qualifies, so a record-attached image of
112
+ // someone else cannot be copied into a template.
113
+ let source: Awaited<ReturnType<typeof readFileRefContent>>;
114
+ try {
115
+ source = await readFileRefContent({
116
+ db: ctx.db,
117
+ filesForTenant: (tid) => fileContextForTenant(ctx, ctx.user.tenantId, tid),
118
+ fileRefId: sourceFileRefId,
119
+ allowedTenantIds: [event.user.tenantId],
120
+ unclaimedUploadOf: event.user.id,
121
+ maxBytes,
122
+ });
123
+ } catch (cause) {
124
+ if (cause instanceof FileRefTooLargeError) {
125
+ return writeFailure(
126
+ new UnprocessableError(TemplateResolverErrors.resourceTooLarge, {
127
+ i18nKey: "templateResolver.errors.resource.tooLarge",
128
+ details: { maxBytes, size: cause.size },
129
+ }),
130
+ );
131
+ }
132
+ throw cause;
133
+ }
134
+ if (source === null) return writeFailure(new NotFoundError("file-ref", sourceFileRefId));
135
+
136
+ // The source upload stays as an unclaimed file: bundled-features cannot reach the fileRef executor to delete it.
137
+ const tenantId = resourceTargetTenantId(event.payload.tenantIdOverride, event.user.tenantId);
138
+ if (attachmentTarget !== null) {
139
+ return prepareTemplateAttachmentBeforeTransaction({
140
+ ctx,
141
+ user: event.user,
142
+ tenantId,
143
+ bytes: source.content,
144
+ maxBytes,
145
+ ...attachmentTarget,
146
+ });
147
+ }
148
+ return prepareTemplateResourceBeforeTransaction({
121
149
  ctx,
122
150
  user: event.user,
123
151
  tenantId,
124
152
  bytes: source.content,
125
153
  maxBytes,
126
- ...attachmentTarget,
154
+ ...(templateId !== undefined && { templateId }),
155
+ ...(key !== undefined && { key }),
127
156
  });
128
- }
129
- return prepareTemplateResourceBeforeTransaction({
157
+ },
158
+ handler: async (
159
+ event,
130
160
  ctx,
131
- user: event.user,
132
- tenantId,
133
- bytes: source.content,
134
- maxBytes,
135
- ...(templateId !== undefined && { templateId }),
136
- ...(key !== undefined && { key }),
137
- });
138
- },
139
- handler: async (
140
- event,
141
- ctx,
142
- ): Promise<WriteResult<StoredTemplateResource | StoredTemplateAttachment>> => {
143
- const { templateId, key } = event.payload;
144
- const tenantId = resourceTargetTenantId(event.payload.tenantIdOverride, event.user.tenantId);
145
- const { prepared } = event;
146
- if (prepared.kind === "attachment") {
147
- // The phase accepts attachments only with a complete target, so these are set.
148
- if (templateId === undefined || key === undefined) {
149
- return writeFailure(
150
- new UnprocessableError(TemplateResolverErrors.attachmentInvalid, {
151
- i18nKey: "templateResolver.errors.attachment.invalid",
152
- details: { code: "target_required" },
153
- }),
154
- );
161
+ ): Promise<WriteResult<StoredTemplateResource | StoredTemplateAttachment>> => {
162
+ const { templateId, key } = event.payload;
163
+ const tenantId = resourceTargetTenantId(event.payload.tenantIdOverride, event.user.tenantId);
164
+ const { prepared } = event;
165
+ if (prepared.kind === "attachment") {
166
+ // The phase accepts attachments only with a complete target, so these are set.
167
+ if (templateId === undefined || key === undefined) {
168
+ return writeFailure(
169
+ new UnprocessableError(TemplateResolverErrors.attachmentInvalid, {
170
+ i18nKey: "templateResolver.errors.attachment.invalid",
171
+ details: { code: "target_required" },
172
+ }),
173
+ );
174
+ }
175
+ return storePreparedTemplateAttachment({
176
+ ctx,
177
+ user: event.user,
178
+ tenantId,
179
+ prepared,
180
+ templateId,
181
+ key,
182
+ });
155
183
  }
156
- return storePreparedTemplateAttachment({
184
+ return storePreparedTemplateResource({
157
185
  ctx,
158
186
  user: event.user,
159
187
  tenantId,
160
188
  prepared,
161
- templateId,
162
- key,
189
+ ...(templateId !== undefined && { templateId }),
190
+ ...(key !== undefined && { key }),
163
191
  });
164
- }
165
- return storePreparedTemplateResource({
166
- ctx,
167
- user: event.user,
168
- tenantId,
169
- prepared,
170
- ...(templateId !== undefined && { templateId }),
171
- ...(key !== undefined && { key }),
172
- });
173
- },
174
- });
192
+ },
193
+ });
194
+ }
@@ -0,0 +1,38 @@
1
+ import type { HandlerContext, Registry, SessionUser } from "@cosmicdrift/kumiko-framework/engine";
2
+ import {
3
+ type ChannelContext,
4
+ collectChannels,
5
+ createEmailAttachmentReader,
6
+ EMAIL_CHANNEL_NAME,
7
+ } from "../../delivery/index.ts";
8
+ import { fileContextForTenant } from "../../file-foundation/index.ts";
9
+
10
+ export function emailChannelOf(registry: Registry) {
11
+ return collectChannels(registry).find((channel) => channel.name === EMAIL_CHANNEL_NAME);
12
+ }
13
+
14
+ export function testMailChannelContext(ctx: HandlerContext, user: SessionUser): ChannelContext {
15
+ return {
16
+ db: ctx.db,
17
+ registry: ctx.registry,
18
+ sseBroker: undefined,
19
+ tenantId: user.tenantId,
20
+ secrets: ctx.secrets,
21
+ config: ctx.config,
22
+ readAttachment: createEmailAttachmentReader(
23
+ ctx.db,
24
+ ctx.registry,
25
+ user.tenantId,
26
+ ctx.config,
27
+ (tenantId) => fileContextForTenant(ctx, ctx.user.tenantId, tenantId),
28
+ ),
29
+ };
30
+ }
31
+
32
+ export async function resolveTestMailAddress(
33
+ channel: NonNullable<ReturnType<typeof emailChannelOf>>,
34
+ channelContext: ChannelContext,
35
+ user: SessionUser,
36
+ ): Promise<string | null> {
37
+ return (await channel.resolve?.(user.id, channelContext)) ?? null;
38
+ }
@@ -0,0 +1,31 @@
1
+ import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
2
+ import * as z from "zod";
3
+ import {
4
+ emailChannelOf,
5
+ resolveTestMailAddress,
6
+ testMailChannelContext,
7
+ } from "./send-test-shared.ts";
8
+
9
+ export type SendTestStatus =
10
+ | { readonly canSend: true; readonly reason: null }
11
+ | { readonly canSend: false; readonly reason: "no_channel" | "no_address" };
12
+
13
+ export const sendTestStatusQuery = defineQueryHandler({
14
+ name: "send-test-status",
15
+ schema: z.object({}),
16
+ access: { roles: ["TenantAdmin", "SystemAdmin"] },
17
+ description:
18
+ "Tells whether send-test can reach the calling user: canSend is true when an email channel is mounted and resolves an address for the caller, otherwise reason is no_channel or no_address; the address itself is never returned.",
19
+ handler: async (query, ctx): Promise<SendTestStatus> => {
20
+ const channel = emailChannelOf(ctx.registry);
21
+ if (channel === undefined) return { canSend: false, reason: "no_channel" };
22
+ const address = await resolveTestMailAddress(
23
+ channel,
24
+ testMailChannelContext(ctx, query.user),
25
+ query.user,
26
+ );
27
+ return address === null
28
+ ? { canSend: false, reason: "no_address" }
29
+ : { canSend: true, reason: null };
30
+ },
31
+ });
@@ -1,23 +1,23 @@
1
- import { defineWriteHandler, type Registry } from "@cosmicdrift/kumiko-framework/engine";
1
+ import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
2
2
  import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
3
3
  import * as z from "zod";
4
4
  import {
5
- collectChannels,
6
- createEmailAttachmentReader,
7
- EMAIL_CHANNEL_NAME,
8
- } from "../../delivery/index.ts";
9
- import { fileContextForTenant } from "../../file-foundation/index.ts";
10
- import { TEST_MAIL_SUBJECT_PREFIX } from "../constants.ts";
11
- import { TemplateResolverErrors } from "../qualified-names.ts";
5
+ refuseTemplateEdit,
6
+ type TemplateEditPolicy,
7
+ templateEditAccess,
8
+ } from "../template-edit-policy.ts";
12
9
  import { renderTemplatePreview } from "../template-preview.ts";
10
+ import { TEST_MAIL_SUBJECT_PREFIX } from "../web/constants.ts";
11
+ import { TemplateResolverErrors } from "../web/qualified-names.ts";
12
+ import {
13
+ emailChannelOf,
14
+ resolveTestMailAddress,
15
+ testMailChannelContext,
16
+ } from "./send-test-shared.ts";
13
17
  import { localeSchema, templateSlugSchema } from "./shared.ts";
14
18
 
15
19
  const TEST_MAIL_RATE_LIMIT = { limit: 10, windowSeconds: 3600 } as const;
16
20
 
17
- function emailChannelOf(registry: Registry) {
18
- return collectChannels(registry).find((channel) => channel.name === EMAIL_CHANNEL_NAME);
19
- }
20
-
21
21
  // Transport errors can carry provider responses or the address, so the client only gets a fixed code.
22
22
  function testMailFailed(code: "channel_error" | "not_sent") {
23
23
  return writeFailure(
@@ -34,105 +34,99 @@ function errorCodeOf(error: unknown): string | undefined {
34
34
  }
35
35
 
36
36
  // The only recipient is the caller's own address, resolved server-side, so the handler can never mail a third party.
37
- export const sendTestWrite = defineWriteHandler({
38
- name: "send-test",
39
- schema: z.object({
40
- slug: templateSlugSchema,
41
- locale: localeSchema,
42
- content: z.string().max(200_000).optional(),
43
- meta: z.unknown().optional(),
44
- variableSchema: z.record(z.string(), z.unknown()).default({}),
45
- variables: z.record(z.string(), z.unknown()).optional(),
46
- includeAttachments: z.boolean().default(true),
47
- prefixSubject: z.boolean().default(true),
48
- }),
49
- access: { roles: ["TenantAdmin", "SystemAdmin"] },
50
- rateLimit: { per: "user+handler", ...TEST_MAIL_RATE_LIMIT },
51
- description:
52
- "Sends the rendered preview of a mail template draft, or of the stored template when no content is given, to the calling user's own address with the subject prefixed by [TEST] unless prefixSubject is false, carrying the inline images and, unless includeAttachments is false, the files attached to the template; there is no recipient input, the sample values come from the declared variable examples unless variables replaces them (checked against the variable schema), and a draft that fails the write rules is rejected without sending.",
53
- handler: async (event, ctx) => {
54
- const { includeAttachments, prefixSubject, ...previewPayload } = event.payload;
55
- const outcome = await renderTemplatePreview(ctx, event.user, {
56
- ...previewPayload,
57
- kind: "mail",
58
- });
59
- if (!outcome.ok) return writeFailure(outcome.error);
60
- const { preview, sender } = outcome;
61
- const mailAttachments = includeAttachments
62
- ? outcome.mailAttachments
63
- : outcome.mailAttachments.filter((ref) => ref.kind === "inline");
64
-
65
- const channel = emailChannelOf(ctx.registry);
66
- if (channel === undefined) {
67
- return writeFailure(
68
- new UnprocessableError(TemplateResolverErrors.testMailNoChannel, {
69
- i18nKey: "templateResolver.errors.testMail.noChannel",
70
- }),
71
- );
72
- }
73
- const channelContext = {
74
- db: ctx.db,
75
- registry: ctx.registry,
76
- sseBroker: undefined,
77
- tenantId: event.user.tenantId,
78
- secrets: ctx.secrets,
79
- config: ctx.config,
80
- readAttachment: createEmailAttachmentReader(
81
- ctx.db,
82
- ctx.registry,
83
- event.user.tenantId,
84
- ctx.config,
85
- (tenantId) => fileContextForTenant(ctx, ctx.user.tenantId, tenantId),
86
- ),
87
- };
88
- const address = (await channel.resolve?.(event.user.id, channelContext)) ?? null;
89
- if (address === null) {
90
- return writeFailure(
91
- new UnprocessableError(TemplateResolverErrors.testMailNoAddress, {
92
- i18nKey: "templateResolver.errors.testMail.noAddress",
93
- }),
94
- );
95
- }
96
-
97
- const subject = `${prefixSubject ? TEST_MAIL_SUBJECT_PREFIX : ""}${preview.subject ?? ""}`;
98
- let sentStatus: string;
99
- try {
100
- const result = await channel.send(
101
- address,
102
- {
103
- // The template slug is the notification name, so the test mail takes the transport the real mail takes.
104
- notificationType: event.payload.slug,
105
- title: subject,
106
- body: undefined,
107
- data: undefined,
108
- locale: event.payload.locale,
109
- },
110
- channelContext,
111
- {
112
- subject,
113
- html: preview.html,
114
- text: preview.text ?? "",
115
- ...sender,
116
- attachments: mailAttachments,
117
- },
118
- );
119
- sentStatus = result.status;
120
- } catch (error) {
121
- // Class and code only: the message can carry the address or a provider response body.
122
- ctx.log?.warn("template test mail: channel threw", {
123
- tenantId: event.user.tenantId,
124
- errorClass: error instanceof Error ? error.name : typeof error,
125
- errorCode: errorCodeOf(error),
37
+ export function makeSendTestWrite(policy: TemplateEditPolicy) {
38
+ return defineWriteHandler({
39
+ name: "send-test",
40
+ schema: z.object({
41
+ slug: templateSlugSchema,
42
+ locale: localeSchema,
43
+ content: z.string().max(200_000).optional(),
44
+ meta: z.unknown().optional(),
45
+ variableSchema: z.record(z.string(), z.unknown()).default({}),
46
+ variables: z.record(z.string(), z.unknown()).optional(),
47
+ includeAttachments: z.boolean().default(true),
48
+ prefixSubject: z.boolean().default(true),
49
+ }),
50
+ access: templateEditAccess(policy.tenantEditable),
51
+ rateLimit: { per: "user+handler", ...TEST_MAIL_RATE_LIMIT },
52
+ description:
53
+ "Sends the rendered preview of a mail template draft, or of the stored template when no content is given, to the calling user's own address with the subject prefixed by [TEST] unless prefixSubject is false, carrying the inline images and, unless includeAttachments is false, the files attached to the template; there is no recipient input, the sample values come from the declared variable examples unless variables replaces them (checked against the variable schema), and a draft that fails the write rules is rejected without sending.",
54
+ handler: async (event, ctx) => {
55
+ // Sending a test is part of editing, so it follows the same edit policy as the editor writes.
56
+ const refused = refuseTemplateEdit(policy, event.user, {
57
+ slug: event.payload.slug,
58
+ kind: "mail",
126
59
  });
127
- return testMailFailed("channel_error");
128
- }
129
- if (sentStatus !== "sent") {
130
- ctx.log?.warn("template test mail: channel did not send", {
131
- tenantId: event.user.tenantId,
132
- status: sentStatus,
60
+ if (refused !== null) return refused;
61
+ const { includeAttachments, prefixSubject, ...previewPayload } = event.payload;
62
+ const outcome = await renderTemplatePreview(ctx, event.user, {
63
+ ...previewPayload,
64
+ kind: "mail",
133
65
  });
134
- return testMailFailed("not_sent");
135
- }
136
- return { isSuccess: true as const, data: { subject, checks: preview.checks } };
137
- },
138
- });
66
+ if (!outcome.ok) return writeFailure(outcome.error);
67
+ const { preview, sender } = outcome;
68
+ const mailAttachments = includeAttachments
69
+ ? outcome.mailAttachments
70
+ : outcome.mailAttachments.filter((ref) => ref.kind === "inline");
71
+
72
+ const channel = emailChannelOf(ctx.registry);
73
+ if (channel === undefined) {
74
+ return writeFailure(
75
+ new UnprocessableError(TemplateResolverErrors.testMailNoChannel, {
76
+ i18nKey: "templateResolver.errors.testMail.noChannel",
77
+ }),
78
+ );
79
+ }
80
+ const channelContext = testMailChannelContext(ctx, event.user);
81
+ const address = await resolveTestMailAddress(channel, channelContext, event.user);
82
+ if (address === null) {
83
+ return writeFailure(
84
+ new UnprocessableError(TemplateResolverErrors.testMailNoAddress, {
85
+ i18nKey: "templateResolver.errors.testMail.noAddress",
86
+ }),
87
+ );
88
+ }
89
+
90
+ const subject = `${prefixSubject ? TEST_MAIL_SUBJECT_PREFIX : ""}${preview.subject ?? ""}`;
91
+ let sentStatus: string;
92
+ try {
93
+ const result = await channel.send(
94
+ address,
95
+ {
96
+ // The template slug is the notification name, so the test mail takes the transport the real mail takes.
97
+ notificationType: event.payload.slug,
98
+ title: subject,
99
+ body: undefined,
100
+ data: undefined,
101
+ locale: event.payload.locale,
102
+ },
103
+ channelContext,
104
+ {
105
+ subject,
106
+ html: preview.html,
107
+ text: preview.text ?? "",
108
+ ...sender,
109
+ attachments: mailAttachments,
110
+ },
111
+ );
112
+ sentStatus = result.status;
113
+ } catch (error) {
114
+ // Class and code only: the message can carry the address or a provider response body.
115
+ ctx.log?.warn("template test mail: channel threw", {
116
+ tenantId: event.user.tenantId,
117
+ errorClass: error instanceof Error ? error.name : typeof error,
118
+ errorCode: errorCodeOf(error),
119
+ });
120
+ return testMailFailed("channel_error");
121
+ }
122
+ if (sentStatus !== "sent") {
123
+ ctx.log?.warn("template test mail: channel did not send", {
124
+ tenantId: event.user.tenantId,
125
+ status: sentStatus,
126
+ });
127
+ return testMailFailed("not_sent");
128
+ }
129
+ return { isSuccess: true as const, data: { subject, checks: preview.checks } };
130
+ },
131
+ });
132
+ }