@cosmicdrift/kumiko-samples 0.362.0 → 0.364.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 (321) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +23 -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/audit/changes.json +6 -0
  10. package/packages/bundled-features/src/audit/handlers/list.query.ts +10 -5
  11. package/packages/bundled-features/src/auth-email-password/bootstrap.ts +29 -8
  12. package/packages/bundled-features/src/auth-email-password/changes.json +51 -0
  13. package/packages/bundled-features/src/auth-email-password/email-rate-limits.ts +16 -0
  14. package/packages/bundled-features/src/auth-email-password/email-templates.ts +12 -138
  15. package/packages/bundled-features/src/auth-email-password/feature.ts +30 -9
  16. package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +6 -2
  17. package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +4 -5
  18. package/packages/bundled-features/src/auth-email-password/handlers/logout.write.ts +7 -2
  19. package/packages/bundled-features/src/auth-email-password/handlers/request-account-unlock.write.ts +2 -5
  20. package/packages/bundled-features/src/auth-email-password/handlers/request-email-verification.write.ts +2 -5
  21. package/packages/bundled-features/src/auth-email-password/handlers/request-password-reset.write.ts +2 -5
  22. package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +3 -4
  23. package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +2 -1
  24. package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +5 -13
  25. package/packages/bundled-features/src/auth-email-password/index.ts +2 -17
  26. package/packages/bundled-features/src/auth-email-password/magic-link-mail.ts +30 -22
  27. package/packages/bundled-features/src/auth-email-password/mail-notifications.ts +158 -0
  28. package/packages/bundled-features/src/auth-email-password/signup-activation.ts +2 -3
  29. package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +24 -5
  30. package/packages/bundled-features/src/auth-foundation/types.ts +8 -18
  31. package/packages/bundled-features/src/auth-mfa/feature.ts +8 -10
  32. package/packages/bundled-features/src/auth-mfa/hmac-token-codec.ts +4 -4
  33. package/packages/bundled-features/src/auth-mfa/mfa-challenge-token.ts +3 -3
  34. package/packages/bundled-features/src/auth-mfa/mfa-preauth-setup-token.ts +3 -3
  35. package/packages/bundled-features/src/auth-mfa/mfa-setup-token.ts +8 -8
  36. package/packages/bundled-features/src/billing-foundation/changes.json +43 -0
  37. package/packages/bundled-features/src/billing-foundation/constants.ts +5 -4
  38. package/packages/bundled-features/src/billing-foundation/consumer-protection/confirmation-mail.ts +90 -44
  39. package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +22 -1
  40. package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +3 -3
  41. package/packages/bundled-features/src/billing-foundation/consumer-protection/mail-notifications.ts +118 -0
  42. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +2 -0
  43. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts +11 -1
  44. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-mail.ts +39 -33
  45. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-notify.ts +2 -2
  46. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-pages.ts +1 -4
  47. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-receipt-limit.ts +41 -0
  48. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +10 -1
  49. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +36 -20
  50. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-texts.ts +9 -0
  51. package/packages/bundled-features/src/billing-foundation/consumer-protection/withdrawal-period.ts +35 -0
  52. package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +2 -2
  53. package/packages/bundled-features/src/billing-foundation/handlers/create-checkout-session.write.ts +11 -3
  54. package/packages/bundled-features/src/billing-foundation/handlers/create-portal-session.write.ts +4 -9
  55. package/packages/bundled-features/src/billing-foundation/handlers/list-subscriptions.query.ts +1 -1
  56. package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +12 -6
  57. package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +1 -1
  58. package/packages/bundled-features/src/billing-foundation/i18n.ts +3 -0
  59. package/packages/bundled-features/src/billing-foundation/subscription-tier-sync.ts +20 -11
  60. package/packages/bundled-features/src/billing-foundation/types.ts +7 -2
  61. package/packages/bundled-features/src/cap-counter/calendar-cap-usage-reservation.ts +2 -0
  62. package/packages/bundled-features/src/cap-counter/changes.json +6 -0
  63. package/packages/bundled-features/src/cap-counter/enforce-cap.ts +75 -5
  64. package/packages/bundled-features/src/cap-counter/index.ts +1 -0
  65. package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +3 -0
  66. package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +2 -1
  67. package/packages/bundled-features/src/cap-overview/web/cap-usage-cell.tsx +6 -4
  68. package/packages/bundled-features/src/channel-email/changes.json +19 -0
  69. package/packages/bundled-features/src/channel-email/email-channel.ts +135 -29
  70. package/packages/bundled-features/src/channel-email/feature.ts +6 -3
  71. package/packages/bundled-features/src/channel-email/index.ts +7 -1
  72. package/packages/bundled-features/src/channel-email/pii-guard.ts +13 -0
  73. package/packages/bundled-features/src/channel-email/smtp-transport.ts +15 -1
  74. package/packages/bundled-features/src/channel-email/types.ts +23 -0
  75. package/packages/bundled-features/src/channel-in-app/handlers/mark-read.write.ts +2 -2
  76. package/packages/bundled-features/src/config/changes.json +6 -0
  77. package/packages/bundled-features/src/config/handlers/set.write.ts +3 -7
  78. package/packages/bundled-features/src/config/resolver.ts +15 -12
  79. package/packages/bundled-features/src/config/write-helpers.ts +23 -0
  80. package/packages/bundled-features/src/crypto-shredding/changes.json +6 -0
  81. package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +48 -27
  82. package/packages/bundled-features/src/custom-fields/changes.json +6 -0
  83. package/packages/bundled-features/src/custom-fields/db/queries/user-data-rights.ts +2 -2
  84. package/packages/bundled-features/src/custom-fields/handlers/update-tenant-field.write.ts +0 -1
  85. package/packages/bundled-features/src/custom-fields/lib/value-schema.ts +10 -11
  86. package/packages/bundled-features/src/custom-fields/wire-user-data-rights.ts +7 -6
  87. package/packages/bundled-features/src/data-retention/changes.json +6 -0
  88. package/packages/bundled-features/src/data-retention/index.ts +1 -0
  89. package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +8 -6
  90. package/packages/bundled-features/src/delivery/attachment-ref.ts +105 -0
  91. package/packages/bundled-features/src/delivery/changes.json +66 -0
  92. package/packages/bundled-features/src/delivery/channel-context.ts +152 -4
  93. package/packages/bundled-features/src/delivery/constants.ts +2 -0
  94. package/packages/bundled-features/src/delivery/delivery-service.ts +104 -26
  95. package/packages/bundled-features/src/delivery/feature.ts +1 -1
  96. package/packages/bundled-features/src/delivery/index.ts +19 -2
  97. package/packages/bundled-features/src/delivery/jobs.ts +97 -17
  98. package/packages/bundled-features/src/delivery/notify-factory.ts +14 -9
  99. package/packages/bundled-features/src/delivery/testing.ts +9 -1
  100. package/packages/bundled-features/src/delivery/types.ts +34 -18
  101. package/packages/bundled-features/src/delivery/unsubscribe.ts +5 -2
  102. package/packages/bundled-features/src/derivatives-sharp/render.ts +1 -22
  103. package/packages/bundled-features/src/document-ingest-foundation/pages.ts +6 -3
  104. package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +4 -4
  105. package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +1 -1
  106. package/packages/bundled-features/src/file-foundation/changes.json +6 -0
  107. package/packages/bundled-features/src/file-foundation/index.ts +7 -0
  108. package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +89 -0
  109. package/packages/bundled-features/src/files-tenant-data/changes.json +6 -0
  110. package/packages/bundled-features/src/files-tenant-data/hooks.ts +4 -0
  111. package/packages/bundled-features/src/folders/web/folder-manager.tsx +5 -2
  112. package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +11 -11
  113. package/packages/bundled-features/src/form-draft/db/queries/draft-count.ts +2 -2
  114. package/packages/bundled-features/src/form-draft/db/queries/owned-file-refs.ts +2 -2
  115. package/packages/bundled-features/src/form-draft/release-file-refs.ts +3 -2
  116. package/packages/bundled-features/src/inbound-mail-foundation/changes.json +6 -0
  117. package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +1 -1
  118. package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +6 -6
  119. package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-accounts.query.ts +1 -1
  120. package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-messages.query.ts +1 -1
  121. package/packages/bundled-features/src/inbound-mail-foundation/handlers/scope-visibility.ts +2 -2
  122. package/packages/bundled-features/src/inbound-mail-foundation/oauth-access-token.ts +13 -8
  123. package/packages/bundled-features/src/inbound-mail-foundation/oauth-state.ts +7 -7
  124. package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +2 -6
  125. package/packages/bundled-features/src/inbound-mail-foundation/tenant-destroy-hook.ts +5 -13
  126. package/packages/bundled-features/src/inbound-mail-foundation/types.ts +4 -6
  127. package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +42 -25
  128. package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +4 -3
  129. package/packages/bundled-features/src/jobs/changes.json +19 -0
  130. package/packages/bundled-features/src/jobs/db/queries/stale-run-sweep.ts +12 -4
  131. package/packages/bundled-features/src/jobs/feature.ts +7 -4
  132. package/packages/bundled-features/src/jobs/handlers/catalog.query.ts +1 -1
  133. package/packages/bundled-features/src/jobs/handlers/list.query.ts +3 -1
  134. package/packages/bundled-features/src/jobs/handlers/stale-run-sweep.job.ts +5 -2
  135. package/packages/bundled-features/src/jobs/job-run-logger.ts +52 -17
  136. package/packages/bundled-features/src/jobs/tenant-job-run-table.ts +7 -2
  137. package/packages/bundled-features/src/ledger/changes.json +7 -0
  138. package/packages/bundled-features/src/ledger/entity.ts +3 -1
  139. package/packages/bundled-features/src/ledger/recurring.ts +1 -3
  140. package/packages/bundled-features/src/managed-pages/branding.ts +33 -9
  141. package/packages/bundled-features/src/managed-pages/changes.json +6 -0
  142. package/packages/bundled-features/src/managed-pages/feature.ts +1 -0
  143. package/packages/bundled-features/src/managed-pages/i18n.ts +1 -0
  144. package/packages/bundled-features/src/managed-pages/screens/branding-screen.ts +8 -0
  145. package/packages/bundled-features/src/page-render/branding.ts +3 -0
  146. package/packages/bundled-features/src/page-render/markdown.ts +1 -1
  147. package/packages/bundled-features/src/personal-access-tokens/feature.ts +2 -4
  148. package/packages/bundled-features/src/personal-access-tokens/handlers/list.query.ts +2 -1
  149. package/packages/bundled-features/src/personal-access-tokens/index.ts +1 -1
  150. package/packages/bundled-features/src/personal-access-tokens/revoke-for-user.ts +2 -1
  151. package/packages/bundled-features/src/renderer-foundation/README.md +11 -10
  152. package/packages/bundled-features/src/renderer-foundation/api.ts +14 -14
  153. package/packages/bundled-features/src/renderer-foundation/changes.json +39 -0
  154. package/packages/bundled-features/src/renderer-foundation/constants.ts +6 -8
  155. package/packages/bundled-features/src/renderer-foundation/feature.ts +7 -7
  156. package/packages/bundled-features/src/renderer-foundation/index.ts +13 -2
  157. package/packages/bundled-features/src/renderer-foundation/types.ts +134 -35
  158. package/packages/bundled-features/src/renderer-pdf/changes.json +12 -0
  159. package/packages/bundled-features/src/renderer-pdf/feature.ts +7 -1
  160. package/packages/bundled-features/src/renderer-pdf/render-table-pdf.ts +162 -82
  161. package/packages/bundled-features/src/secrets/changes.json +6 -0
  162. package/packages/bundled-features/src/secrets/feature.ts +4 -3
  163. package/packages/bundled-features/src/secrets/secrets-context.ts +23 -3
  164. package/packages/bundled-features/src/sessions/db/queries/cleanup.ts +2 -2
  165. package/packages/bundled-features/src/sessions/feature.ts +2 -4
  166. package/packages/bundled-features/src/sessions/handlers/list.query.ts +2 -1
  167. package/packages/bundled-features/src/shared/attachment-file-name.ts +39 -0
  168. package/packages/bundled-features/src/shared/changes.json +6 -0
  169. package/packages/bundled-features/src/shared/chunked-entity-migration.ts +4 -1
  170. package/packages/bundled-features/src/shared/index.ts +6 -0
  171. package/packages/bundled-features/src/shared/is-tenant-db.ts +1 -1
  172. package/packages/bundled-features/src/shared/mail-template-body.ts +42 -0
  173. package/packages/bundled-features/src/shared/sharp-input-limits.ts +26 -0
  174. package/packages/bundled-features/src/shared/signup-handover.ts +4 -4
  175. package/packages/bundled-features/src/subscription-stripe/changes.json +6 -0
  176. package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +19 -11
  177. package/packages/bundled-features/src/subscription-stripe/verify-webhook.ts +3 -1
  178. package/packages/bundled-features/src/tags/handlers/assign-tag.write.ts +5 -1
  179. package/packages/bundled-features/src/tags/web/tag-chip.tsx +2 -1
  180. package/packages/bundled-features/src/template-resolver/README.md +66 -8
  181. package/packages/bundled-features/src/template-resolver/api.ts +85 -9
  182. package/packages/bundled-features/src/template-resolver/changes.json +186 -0
  183. package/packages/bundled-features/src/template-resolver/compile-document-body.ts +278 -0
  184. package/packages/bundled-features/src/template-resolver/constants.ts +49 -11
  185. package/packages/bundled-features/src/template-resolver/document-sanitizer.ts +335 -0
  186. package/packages/bundled-features/src/template-resolver/document-schema.ts +345 -0
  187. package/packages/bundled-features/src/template-resolver/feature.ts +59 -6
  188. package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +188 -0
  189. package/packages/bundled-features/src/template-resolver/handlers/collection-set.write.ts +30 -2
  190. package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +160 -0
  191. package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +33 -0
  192. package/packages/bundled-features/src/template-resolver/handlers/discard-draft.write.ts +52 -0
  193. package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +138 -0
  194. package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +9 -6
  195. package/packages/bundled-features/src/template-resolver/handlers/import-bundle-system.write.ts +42 -0
  196. package/packages/bundled-features/src/template-resolver/handlers/import-bundle.write.ts +22 -0
  197. package/packages/bundled-features/src/template-resolver/handlers/layout-source.query.ts +151 -0
  198. package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +19 -18
  199. package/packages/bundled-features/src/template-resolver/handlers/override-diff.ts +49 -0
  200. package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +323 -0
  201. package/packages/bundled-features/src/template-resolver/handlers/preview.query.ts +25 -0
  202. package/packages/bundled-features/src/template-resolver/handlers/reset-to-system.write.ts +58 -0
  203. package/packages/bundled-features/src/template-resolver/handlers/resource-link-pdf.write.ts +34 -0
  204. package/packages/bundled-features/src/template-resolver/handlers/resource-remove.write.ts +50 -0
  205. package/packages/bundled-features/src/template-resolver/handlers/resource-shared.ts +24 -0
  206. package/packages/bundled-features/src/template-resolver/handlers/resource-upload.write.ts +135 -0
  207. package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +135 -0
  208. package/packages/bundled-features/src/template-resolver/handlers/set.write.ts +32 -3
  209. package/packages/bundled-features/src/template-resolver/handlers/shared.ts +7 -2
  210. package/packages/bundled-features/src/template-resolver/handlers/template-gates.ts +196 -0
  211. package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +233 -0
  212. package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +137 -0
  213. package/packages/bundled-features/src/template-resolver/handlers/toggle-status.write.ts +91 -17
  214. package/packages/bundled-features/src/template-resolver/handlers/upsert-system.write.ts +19 -71
  215. package/packages/bundled-features/src/template-resolver/handlers/upsert-tenant.write.ts +16 -77
  216. package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +31 -0
  217. package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +116 -0
  218. package/packages/bundled-features/src/template-resolver/index.ts +111 -0
  219. package/packages/bundled-features/src/template-resolver/layout-appearance.ts +40 -0
  220. package/packages/bundled-features/src/template-resolver/layout-regions.ts +206 -0
  221. package/packages/bundled-features/src/template-resolver/linked-resources.ts +102 -0
  222. package/packages/bundled-features/src/template-resolver/pdf-constants.ts +12 -0
  223. package/packages/bundled-features/src/template-resolver/preview-display-html.ts +36 -0
  224. package/packages/bundled-features/src/template-resolver/print-document.ts +58 -0
  225. package/packages/bundled-features/src/template-resolver/qualified-names.ts +34 -0
  226. package/packages/bundled-features/src/template-resolver/record-mapping.ts +26 -0
  227. package/packages/bundled-features/src/template-resolver/resource-image.ts +47 -0
  228. package/packages/bundled-features/src/template-resolver/seeding.ts +171 -20
  229. package/packages/bundled-features/src/template-resolver/store-template-resource.ts +416 -0
  230. package/packages/bundled-features/src/template-resolver/table.ts +27 -1
  231. package/packages/bundled-features/src/template-resolver/template-bundle.ts +79 -0
  232. package/packages/bundled-features/src/template-resolver/template-diff.ts +78 -0
  233. package/packages/bundled-features/src/template-resolver/template-draft.ts +106 -0
  234. package/packages/bundled-features/src/template-resolver/template-meta.ts +192 -0
  235. package/packages/bundled-features/src/template-resolver/template-preview.ts +676 -0
  236. package/packages/bundled-features/src/template-resolver/template-resources.ts +114 -0
  237. package/packages/bundled-features/src/template-resolver/template-usage.ts +68 -0
  238. package/packages/bundled-features/src/template-resolver/template-validation.ts +230 -0
  239. package/packages/bundled-features/src/template-resolver/testing.ts +5 -5
  240. package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +20 -13
  241. package/packages/bundled-features/src/template-resolver/web/document-editor-extensions.ts +215 -0
  242. package/packages/bundled-features/src/template-resolver/web/document-editor.tsx +37 -0
  243. package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +1065 -0
  244. package/packages/bundled-features/src/template-resolver/web/document-toolbar-blocks.ts +72 -0
  245. package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +26 -0
  246. package/packages/bundled-features/src/template-resolver/web/i18n.ts +121 -0
  247. package/packages/bundled-features/src/template-resolver/web/index.ts +26 -0
  248. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor-lazy.tsx +36 -0
  249. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +176 -0
  250. package/packages/bundled-features/src/template-resolver/web/placeholder-text.ts +37 -0
  251. package/packages/bundled-features/src/template-resolver/web/use-stable-by-key.ts +14 -0
  252. package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +3 -2
  253. package/packages/bundled-features/src/tenant/seeding.ts +41 -7
  254. package/packages/bundled-features/src/tenant-lifecycle/boot-checks.ts +1 -1
  255. package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +2 -1
  256. package/packages/bundled-features/src/tier-engine/changes.json +6 -0
  257. package/packages/bundled-features/src/tier-engine/feature.ts +11 -3
  258. package/packages/bundled-features/src/user/changes.json +6 -0
  259. package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +4 -4
  260. package/packages/bundled-features/src/user/handlers/detail.query.ts +3 -5
  261. package/packages/bundled-features/src/user/resolve-display-names.ts +8 -15
  262. package/packages/bundled-features/src/user/roles-input-schema.ts +2 -2
  263. package/packages/bundled-features/src/user/seeding.ts +19 -5
  264. package/packages/bundled-features/src/user-data-rights/changes.json +13 -0
  265. package/packages/bundled-features/src/user-data-rights/db/queries/forget-cleanup.ts +2 -2
  266. package/packages/bundled-features/src/user-data-rights/email-templates.ts +5 -149
  267. package/packages/bundled-features/src/user-data-rights/feature.ts +72 -35
  268. package/packages/bundled-features/src/user-data-rights/handlers/cancel-deletion.write.ts +2 -1
  269. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +5 -11
  270. package/packages/bundled-features/src/user-data-rights/lib/default-mailers.ts +134 -53
  271. package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +2 -2
  272. package/packages/bundled-features/src/user-data-rights/mail-notifications.ts +157 -0
  273. package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +1 -1
  274. package/packages/bundled-features/src/user-data-rights/web/privacy-center-screen.tsx +2 -1
  275. package/packages/bundled-features/src/user-profile/changes.json +1 -1
  276. package/packages/bundled-features/src/user-profile/web/profile-screen.tsx +2 -1
  277. package/packages/bundled-features/src/waitlist/changes.json +7 -0
  278. package/packages/bundled-features/src/waitlist/feature.ts +17 -0
  279. package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +4 -5
  280. package/packages/bundled-features/src/waitlist/mail.ts +69 -18
  281. package/packages/bundled-features/src/waitlist/options.ts +1 -1
  282. package/packages/bundled-features/src/workflow-runner/changes.json +6 -0
  283. package/packages/bundled-features/src/workflow-runner/event-trigger.ts +3 -3
  284. package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +7 -1
  285. package/samples/apps/ui-walkthrough/package.json +11 -0
  286. package/samples/apps/ui-walkthrough/playwright.config.ts +40 -7
  287. package/samples/apps/ui-walkthrough/src/app/auth-constants.ts +9 -0
  288. package/samples/apps/ui-walkthrough/src/app/client.tsx +6 -0
  289. package/samples/apps/ui-walkthrough/src/app/seed-templates.ts +239 -0
  290. package/samples/apps/ui-walkthrough/src/app/server.ts +5 -1
  291. package/samples/apps/ui-walkthrough/src/app/shell.tsx +12 -1
  292. package/samples/apps/ui-walkthrough/src/app/template-seed-data.ts +570 -0
  293. package/samples/apps/ui-walkthrough/src/features/billing/feature.ts +72 -0
  294. package/samples/apps/ui-walkthrough/src/features/billing/i18n.ts +18 -0
  295. package/samples/apps/ui-walkthrough/src/features/billing/index.ts +2 -0
  296. package/samples/apps/ui-walkthrough/src/features/billing/web/index.ts +7 -0
  297. package/samples/apps/ui-walkthrough/src/run-config.ts +41 -1
  298. package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +0 -2
  299. package/samples/apps/use-all-bundled/feature-manifest.json +118 -21
  300. package/samples/apps/use-all-bundled/kumiko/migrations/0045_template-resource-meta.rebuild.json +4 -0
  301. package/samples/apps/use-all-bundled/kumiko/migrations/0046_pdf-rendered.rebuild.json +4 -0
  302. package/samples/apps/use-all-bundled/package.json +2 -0
  303. package/samples/apps/use-all-bundled/playwright.config.ts +38 -3
  304. package/samples/apps/use-all-bundled/schema/generate.ts +13 -3
  305. package/samples/apps/use-all-bundled/schema/schema.generated.ts +3 -0
  306. package/samples/apps/use-all-bundled/scripts/check-coverage.ts +3 -0
  307. package/samples/apps/use-all-bundled/src/app/auth-constants.ts +3 -0
  308. package/samples/apps/use-all-bundled/src/app/client.tsx +4 -0
  309. package/samples/apps/use-all-bundled/src/app/seed-documents.ts +128 -0
  310. package/samples/apps/use-all-bundled/src/app/seed.ts +4 -0
  311. package/samples/apps/use-all-bundled/src/run-config.ts +11 -11
  312. package/samples/recipes/delivery-notifications/README.md +26 -25
  313. package/samples/recipes/delivery-notifications/package.json +2 -1
  314. package/samples/recipes/delivery-notifications/src/feature.ts +64 -58
  315. package/samples/recipes/seed-migration/README.md +1 -1
  316. package/packages/bundled-features/src/renderer-simple/changes.json +0 -27
  317. package/packages/bundled-features/src/renderer-simple/feature.ts +0 -66
  318. package/packages/bundled-features/src/renderer-simple/index.ts +0 -8
  319. package/packages/bundled-features/src/renderer-simple/mail-blocks.ts +0 -138
  320. package/packages/bundled-features/src/renderer-simple/resolve-variables.ts +0 -57
  321. package/packages/bundled-features/src/renderer-simple/simple-renderer.ts +0 -304
@@ -37,6 +37,7 @@ import { generateId } from "@cosmicdrift/kumiko-framework/utils";
37
37
  import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
38
38
  import { ADMIN_EMAIL, BETA_TENANT_ID, DEMO_NOTE_ID, DEV_TENANT_ID } from "./auth-constants";
39
39
  import { noteEntity } from "./notes-feature";
40
+ import { seedPdfDocuments } from "./seed-documents";
40
41
 
41
42
  // Event-store executors mirror the real handlers (createEntityExecutor pairs the
42
43
  // projection table + executor), so seeded rows are proper event streams, not
@@ -392,6 +393,9 @@ export const seedScreenshotData: SeedFn = async (stack) => {
392
393
  await stack.http.writeOk("auth-email-password:write:invite-create", invitee, inviter);
393
394
  }
394
395
 
396
+ // PDF template editor — system invoice + letterhead, one live and one draft tenant override.
397
+ await seedPdfDocuments(stack, devAdminId);
398
+
395
399
  // reply-snippets — entries for the rich collection. Written through the
396
400
  // collection's own set-handler, the same path the editor uses, so the rows
397
401
  // carry whatever the handler derives instead of a hand-built projection.
@@ -39,7 +39,6 @@ import { createComplianceProfilesFeature } from "@cosmicdrift/kumiko-bundled-fea
39
39
  import { complianceProfilesOpsFeature } from "@cosmicdrift/kumiko-bundled-features/compliance-profiles-ops";
40
40
  import { customFieldsFeature } from "@cosmicdrift/kumiko-bundled-features/custom-fields";
41
41
  import { createDataRetentionFeature } from "@cosmicdrift/kumiko-bundled-features/data-retention";
42
- import type { NotificationRenderer } from "@cosmicdrift/kumiko-bundled-features/delivery";
43
42
  import { createDeliveryFeature } from "@cosmicdrift/kumiko-bundled-features/delivery";
44
43
  import { derivativesSharpFeature } from "@cosmicdrift/kumiko-bundled-features/derivatives-sharp";
45
44
  import { documentIngestFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/document-ingest-foundation";
@@ -77,7 +76,6 @@ import { securityBaselineFeatures } from "@cosmicdrift/kumiko-bundled-features/p
77
76
  import { readinessFeature } from "@cosmicdrift/kumiko-bundled-features/readiness";
78
77
  import { createRendererFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/renderer-foundation";
79
78
  import { createRendererPdfFeature } from "@cosmicdrift/kumiko-bundled-features/renderer-pdf";
80
- import { createRendererSimpleFeature } from "@cosmicdrift/kumiko-bundled-features/renderer-simple";
81
79
  import { createSecretsFeature } from "@cosmicdrift/kumiko-bundled-features/secrets";
82
80
  import { createSeoFeature } from "@cosmicdrift/kumiko-bundled-features/seo";
83
81
  import { createStepDispatcherFeature } from "@cosmicdrift/kumiko-bundled-features/step-dispatcher";
@@ -95,8 +93,10 @@ import { createUserDataRightsDefaultsFeature } from "@cosmicdrift/kumiko-bundled
95
93
  import { createUserProfileFeature } from "@cosmicdrift/kumiko-bundled-features/user-profile";
96
94
  import { createWaitlistFeature } from "@cosmicdrift/kumiko-bundled-features/waitlist";
97
95
  import { workflowRunnerFeature } from "@cosmicdrift/kumiko-bundled-features/workflow-runner";
96
+ import { createDocumentDesignerFeature } from "@cosmicdrift/kumiko-document-designer";
98
97
  import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
99
98
  import { localeDe } from "@cosmicdrift/kumiko-locale-de";
99
+ import { createRendererPuppeteerClientFeature } from "@cosmicdrift/kumiko-renderer-puppeteer-client";
100
100
  import { DEV_TENANT_ID } from "./app/auth-constants";
101
101
  import { CAP_OVERVIEW_CAPS } from "./app/cap-overview-caps";
102
102
  import { capOverviewLabelsFeature } from "./app/cap-overview-labels-feature";
@@ -114,10 +114,6 @@ const stubPushTransport: PushTransport = {
114
114
  /* smoke-only */
115
115
  },
116
116
  };
117
- const stubRenderer: NotificationRenderer = {
118
- name: "smoke",
119
- render: async () => "<smoke/>",
120
- };
121
117
 
122
118
  // Fixed dev-only signing key for the reset/verify/unlock magic-links (and,
123
119
  // below, tenant-handover's row-bound grant). The factory rejects anything
@@ -170,7 +166,6 @@ export const APP_FEATURES = [
170
166
  createChannelInAppFeature(),
171
167
  createChannelEmailFeature({
172
168
  transport: stubEmailTransport,
173
- renderer: stubRenderer,
174
169
  resolveEmail: async () => "smoke@use-all-bundled.local",
175
170
  }),
176
171
  createChannelPushFeature({
@@ -297,7 +292,7 @@ export const APP_FEATURES = [
297
292
  collections: [
298
293
  {
299
294
  id: "reply-snippets",
300
- kind: "mail-html",
295
+ kind: "snippet",
301
296
  access: { roles: ["TenantAdmin", "TenantMember", "SystemAdmin"] },
302
297
  // Without an explicit workspace the node lands in none of the three
303
298
  // shells and the collection is unreachable in the UI.
@@ -317,7 +312,7 @@ export const APP_FEATURES = [
317
312
  },
318
313
  {
319
314
  id: "signatures",
320
- kind: "mail-html",
315
+ kind: "snippet",
321
316
  ownership: "user",
322
317
  access: { roles: ["TenantAdmin", "TenantMember", "SystemAdmin"] },
323
318
  nav: {
@@ -336,8 +331,10 @@ export const APP_FEATURES = [
336
331
  // collection-labels: the app-side nav labels for the two collections above.
337
332
  collectionLabelsFeature,
338
333
  createRendererFoundationFeature(),
339
- createRendererSimpleFeature(),
340
334
  createRendererPdfFeature(),
335
+ // PDF printing for document templates; the print service is reached through
336
+ // PUPPETEER_SERVICE_URL + PUPPETEER_AUTH_TOKEN and only asked when a PDF is generated.
337
+ createRendererPuppeteerClientFeature(),
341
338
 
342
339
  // managed-pages: requires config (auto-bundled). Smoke resolver never serves
343
340
  // (boot-only). allowCustomCss:true boot-validates the CSS code path + emits
@@ -346,6 +343,9 @@ export const APP_FEATURES = [
346
343
  // covered — not standalone-mounted here (smoke = one mount per subpath-export).
347
344
  createManagedPagesFeature({ resolveApexTenant: () => null, allowCustomCss: true }),
348
345
 
346
+ // Template overview, layout editor and PDF template editor; needs template-resolver and managed-pages above.
347
+ createDocumentDesignerFeature(),
348
+
349
349
  // seo: requires config (auto-bundled). Smoke resolver never serves (boot-
350
350
  // only) — includeLegalPages:true gives the boot-check a real source
351
351
  // without needing real page data; managedPages reuses the same never-
@@ -363,7 +363,7 @@ export const APP_FEATURES = [
363
363
  // admin-shell: requires tenant (auto-mounted) + audit + jobs + tier-engine,
364
364
  // all mounted above. includeCapOverview:true exercises the my-caps /
365
365
  // tenant-caps nav entries end-to-end — cap-overview is mounted below.
366
- createAdminShellFeature({ includeCapOverview: true }),
366
+ createAdminShellFeature({ includeCapOverview: true, includeDocumentDesigner: true }),
367
367
 
368
368
  // app-author-grade
369
369
  customFieldsFeature,
@@ -1,48 +1,49 @@
1
1
  # Sample: Delivery Notifications
2
2
 
3
- Shows how a feature sends notifications to multiple channels (InApp + Email + Push) without knowing the channel internals.
3
+ Shows how a feature sends notifications to multiple channels (InApp + Email + Push) without knowing the channel internals, and how the mail text lives in the template store where a tenant can override it.
4
4
 
5
5
  ## What you learn here
6
6
 
7
- **Declarative notifications:** `r.notification()` fires automatically after a CRUD handler — the feature author writes zero `ctx.notify()` calls.
7
+ **Declarative notifications:** `r.notification()` declares the mail variables, the default mail text, the subject and the short inApp/push texts in one place.
8
8
 
9
- **Per-channel templates:** Each channel only receives the data it needs. InApp gets a short `title/body`, Email gets structured `sections` for the renderer, Push gets compact text.
9
+ **Mail text is stored data:** The boot seed (`seedNotificationTemplates`) writes the declared `template` and `meta` into the template store as system templates. The email channel renders them with `renderer-mail-html`. A notification routed to email without `template` and `meta` fails its delivery.
10
+
11
+ **Tenant override:** A tenant admin overrides the text through the template-resolver handlers (`upsertTenant`, then `publish`). The next mail for that tenant uses the override; the system text stays for everyone else.
12
+
13
+ **Sending:** The write handler calls `ctx.notify(type, { to, data })`. The `data` keys are the template variables; sending fails loudly when a required variable is missing.
10
14
 
11
15
  **Swappable transports:** Email uses `InMemoryTransport` in tests; in a production setup it would be SMTP. Same interface, different implementation.
12
16
 
13
17
  ## Feature composition
14
18
 
15
- The test loads **7 features** together:
19
+ The test loads these features together:
16
20
 
17
21
  ```
18
- config → tenant/system config
19
- tenant → memberships (delivery needs tenant:query:resolve-user-ids)
20
- delivery → core: ctx.notify, DeliveryLog, Preferences, extension points
21
- channel-inApp → r.useExtension("deliveryChannel", "inApp", ...)
22
- channel-email → r.useExtension("deliveryChannel", "email", ...) + renderer + transport
23
- channel-push → r.useExtension("deliveryChannel", "push", ...) + transport
24
- renderer-simple → r.useExtension("renderer", "simple", { kinds: ["notification"], render })
25
- support → our business logic (tickets + notification definitions)
22
+ config → tenant/system config
23
+ tenant → memberships (delivery needs tenant:query:resolve-user-ids)
24
+ template-resolver → template store, override handlers
25
+ renderer-foundation + renderer-mail-html → renders kind "mail"
26
+ delivery → core: ctx.notify, DeliveryLog, Preferences, extension points
27
+ channel-inApp / channel-email / channel-push
28
+ support → our business logic (tickets + the notification declaration)
26
29
  ```
27
30
 
28
- The `support` feature `r.requires("delivery")` — channels are features that attach to delivery, but `support` doesn't need to know them.
31
+ The `support` feature `r.requires("delivery")`: channels are features that attach to delivery, but `support` doesn't need to know them.
29
32
 
30
33
  ## Flow
31
34
 
32
- 1. Admin calls `support:write:ticket:create` over HTTP
33
- 2. CrudExecutor inserts the ticket, returns the `SaveContext`
34
- 3. Lifecycle pipeline: postSave hook fires `r.notification("ticket-assigned")`
35
- 4. `recipient(result)` → assigneeId or `null` (skip)
36
- 5. `data(result)` → raw data (title, description, ticketId, priority)
37
- 6. For each registered channel:
38
- - `templates[channelName](data)` transforms into channel-specific format
39
- - InApp: DB insert + SSE push
40
- - Email: renderer → HTML → transport
41
- - Push: transport
42
- 7. DeliveryLog entry per channel
35
+ 1. The boot seed stores the `ticket-assigned` mail template
36
+ 2. Admin calls `support:write:ticket:create` over HTTP
37
+ 3. The handler inserts the ticket and calls `ctx.notify` for the assignee (no assignee, no notification)
38
+ 4. For each registered channel:
39
+ - InApp: `templates.inApp(data)`, DB insert + SSE push
40
+ - Email: stored template rendered with `data` as variables, then transport
41
+ - Push: `templates.push(data)`, transport
42
+ 5. DeliveryLog entry per channel
43
43
 
44
44
  ## Tests
45
45
 
46
46
  - **E2E happy path:** Ticket with assignee → InApp + Email + Push + 3 DeliveryLog entries
47
- - **Recipient null skip:** Ticket without assignee → no notifications
47
+ - **Tenant override:** a published tenant template replaces the system mail text
48
+ - **Skip:** Ticket without assignee → no notifications
48
49
  - **Access control:** Non-Admin/Support can't create tickets
@@ -4,6 +4,7 @@
4
4
  "private": true,
5
5
  "dependencies": {
6
6
  "@cosmicdrift/kumiko-bundled-features": "workspace:*",
7
- "@cosmicdrift/kumiko-framework": "workspace:*"
7
+ "@cosmicdrift/kumiko-framework": "workspace:*",
8
+ "@cosmicdrift/kumiko-renderer-mail-html": "workspace:*"
8
9
  }
9
10
  }
@@ -1,17 +1,21 @@
1
1
  // Delivery Notifications Sample
2
2
  //
3
- // Shows how a feature sends notifications via multiple channels (inApp + email + push).
4
- // Uses r.notification() for declarative notifications with per-channel templates.
5
- //
6
- // Flow: Admin assigns a support ticket to a user → the user gets notified
7
- // - InApp: toast + badge in the app
8
- // - Email: full HTML with rendered content
9
- // - Push: native notification
3
+ // A support ticket is created and its assignee is notified via inApp, email
4
+ // and push. The mail is a stored template: `r.notification` declares the
5
+ // variables, the default text and the subject, the boot seed writes the text
6
+ // into the template store, and a tenant can override it there.
10
7
  //
11
8
  // The feature code only declares WHAT to notify. HOW is handled by Delivery.
12
9
 
13
10
  import { buildEntityTable, createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
14
- import { createEntity, createTextField, defineFeature } from "@cosmicdrift/kumiko-framework/engine";
11
+ import {
12
+ createEntity,
13
+ createTextField,
14
+ defineFeature,
15
+ QnTypes,
16
+ qn,
17
+ templateVariable,
18
+ } from "@cosmicdrift/kumiko-framework/engine";
15
19
  import { z } from "zod";
16
20
 
17
21
  // --- Entity ---
@@ -40,69 +44,71 @@ function ticketExecutor() {
40
44
 
41
45
  // --- Feature ---
42
46
 
47
+ export const TICKET_ASSIGNED = qn("support", QnTypes.notify, "ticket-assigned");
48
+
43
49
  export const supportFeature = defineFeature("support", (r) => {
44
50
  r.requires("delivery");
45
51
 
46
52
  r.entity("ticket", ticketEntity);
47
53
 
48
- // Real CRUD handler (not stub) — returns SaveContext for lifecycle hooks
49
- const createHandler = r.writeHandler(
50
- "ticket:create",
51
- z.object({
52
- title: z.string().min(1),
53
- description: z.string().optional(),
54
- assigneeId: z.uuid().optional(),
55
- priority: z.enum(["low", "normal", "critical"]),
56
- status: z.string().default("open"),
57
- }),
58
- async (event, ctx) => ticketExecutor().create(event.payload, event.user, ctx.db),
59
- { access: { roles: ["Admin", "Support"] } },
60
- );
61
-
62
- // Declarative notification: fires automatically after ticket.create postSave.
63
- //
64
- // - recipient: returns assignee ID, or null to skip (no assignee = no notification)
65
- // - data: extracts raw fields from the save result
66
- // - templates: per-channel transformations
67
- // inApp → short title/body for toast
68
- // email → structured template (header, sections, button) for renderer
69
- // push → short title/body for native notification
70
54
  r.notification("ticket-assigned", {
71
- trigger: { on: createHandler },
72
- recipient: (result) => {
73
- const assigneeId = result.data["assigneeId"] as string | undefined;
74
- return assigneeId ?? null;
55
+ variables: {
56
+ ticketId: templateVariable.text({ required: true }),
57
+ title: templateVariable.text({ required: true }),
58
+ description: templateVariable.text({ required: true }),
59
+ priority: templateVariable.text({ required: true }),
60
+ url: templateVariable.link({ required: true }),
75
61
  },
76
- data: (result) => ({
77
- ticketId: result.id,
78
- title: result.data["title"] as string,
79
- description: (result.data["description"] as string) ?? "",
80
- priority: result.data["priority"] as string,
81
- }),
62
+ template: {
63
+ en: {
64
+ body: '<h1>New ticket: <span data-variable="title"></span></h1><p><span data-variable="description"></span></p><p>Priority: <span data-variable="priority"></span></p><p><a data-button href="{{url}}">Open ticket</a></p>',
65
+ },
66
+ },
67
+ meta: { en: { subject: "Support ticket #{{ticketId}} ({{priority}})" } },
68
+ // inApp and push build their own short text; email renders the stored
69
+ // template with the notify data as variables.
82
70
  templates: {
83
71
  inApp: (data) => ({
84
- title: `Neues Ticket: ${data["title"]}`,
85
- body: (data["description"] as string) || "Dir wurde ein Ticket zugewiesen.",
86
- }),
87
- email: (data) => ({
88
- subject: `Support-Ticket #${data["ticketId"]} (${data["priority"]})`,
89
- header: `Neues Ticket: ${data["title"]}`,
90
- sections: [
91
- { text: (data["description"] as string) || "Kein Beschreibungstext." },
92
- { text: `Prioritaet: ${data["priority"]}` },
93
- {
94
- button: {
95
- label: "Ticket oeffnen",
96
- url: `/support/tickets/${data["ticketId"]}`,
97
- },
98
- },
99
- ],
100
- footer: "Automatische Benachrichtigung — nicht antworten.",
72
+ title: `New ticket: ${data["title"]}`,
73
+ body: (data["description"] as string) || "A ticket was assigned to you.",
101
74
  }),
102
75
  push: (data) => ({
103
- title: "Neues Ticket",
76
+ title: "New ticket",
104
77
  body: `${data["title"]} (${data["priority"]})`,
105
78
  }),
106
79
  },
107
80
  });
81
+
82
+ r.writeHandler(
83
+ "ticket:create",
84
+ z.object({
85
+ title: z.string().min(1),
86
+ description: z.string().optional(),
87
+ assigneeId: z.uuid().optional(),
88
+ priority: z.enum(["low", "normal", "critical"]),
89
+ status: z.string().default("open"),
90
+ }),
91
+ async (event, ctx) => {
92
+ const created = await ticketExecutor().create(event.payload, event.user, ctx.db);
93
+ const assigneeId = event.payload.assigneeId;
94
+ // No assignee, no notification.
95
+ if (!created.isSuccess || assigneeId === undefined) return created;
96
+ if (ctx.notify === undefined) {
97
+ throw new Error("ticket:create needs ctx.notify, mount the delivery feature");
98
+ }
99
+ const ticketId = created.data.id;
100
+ await ctx.notify(TICKET_ASSIGNED, {
101
+ to: assigneeId,
102
+ data: {
103
+ ticketId,
104
+ title: event.payload.title,
105
+ description: event.payload.description ?? "",
106
+ priority: event.payload.priority,
107
+ url: `https://app.example.com/support/tickets/${ticketId}`,
108
+ },
109
+ });
110
+ return created;
111
+ },
112
+ { access: { roles: ["Admin", "Support"] } },
113
+ );
108
114
  });
@@ -40,7 +40,7 @@ Bei typo / drift fail-t mit klarer message + exit-code 1 — CI nutzt es als pr
40
40
  `findTemplateResources({ slug?, kind?, status?, locale?, tenantId? })` replaces a raw `SELECT … FROM read_template_resources`. It defaults to the system tenant and returns `[]` when template-resolver is not mounted:
41
41
 
42
42
  ```ts
43
- for (const t of await ctx.findTemplateResources({ kind: "notification", status: "active" })) {
43
+ for (const t of await ctx.findTemplateResources({ kind: "mail", status: "active" })) {
44
44
  await ctx.systemWriteAs("template-resolver:write:archive", { id: t.id }, SYSTEM_TENANT_ID, [
45
45
  "SystemAdmin",
46
46
  ]);
@@ -1,27 +0,0 @@
1
- [
2
- {
3
- "version": "0.352.0",
4
- "type": "improvement",
5
- "title": "Badge, stages track and chips for the simple mail renderer",
6
- "detail": "Use badge, { stages } and { chips } in the template variables; the notification RenderResponse carries the matching plain text in text."
7
- },
8
- {
9
- "version": "0.344.0",
10
- "type": "improvement",
11
- "title": "Heading and Markdown sections in the simple mail renderer",
12
- "detail": "`createSimpleRenderer` accepts two more section kinds: `{ heading: string }` (rendered as an escaped `<h2>`) and `{ markdown: string }` (rendered with the hardened Markdown renderer; raw HTML is escaped and unsafe link targets are replaced by `#`). Existing section kinds render as before."
13
- },
14
- {
15
- "version": "0.341.0",
16
- "type": "improvement",
17
- "title": "Localized mail branding, locale through delivery, logoPath",
18
- "detail": "`ctx.notify` accepts `locale`, and the locale now travels through the delivery jobs (`delivery.render`, `delivery.send`) to `NotificationRenderer.render`, where `RendererInput.locale` is available. Jobs queued before this release have no locale and keep working. The email channel and `renderer-simple` pass it on; the auth mails and the consumer-protection mails set it to the language they already translate their content into. This lands in the same release as the `NotifyOptions` change from #3508.\n`MailBranding` for `renderer-simple` can now be localized: `footerText`, `footerLinks[].label` and `footerLinks[].url` take a plain string or a map of locale to string. The renderer picks the exact locale, then the language part (`de-AT` falls back to `de`), then the new optional `MailBranding.defaultLocale`, then the first entry. Every URL in a map must be an absolute http(s) URL, otherwise `createSimpleRenderer` throws at boot. Without a locale and with plain strings the HTML is unchanged.\n`MailBranding` also gets `logoPath` and `baseUrl`, so an app can point at a raster logo on its own origin (`logoPath: \"/logo.png\"`, `baseUrl` the same base as `auth.mail.baseUrl`). `logoPath` and `logoUrl` exclude each other, `logoPath` needs a valid `baseUrl`, must start with a single `/` and must stay on the base origin; violations throw at boot. Use PNG or JPEG, because mail clients block SVG."
19
- },
20
- {
21
- "version": "0.339.0",
22
- "type": "improvement",
23
- "title": "createRendererSimpleFeature({ mailBranding }) brands every notification mail, including the auth mails, with product name or logo, primary color and footer text and links",
24
- "detail": "renderer-simple accepts mailBranding for all framework notification mails",
25
- "migration": "No code change needed."
26
- }
27
- ]
@@ -1,66 +0,0 @@
1
- import { defineFeature, type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
2
- import type { NotificationRenderer } from "../delivery/index.js";
3
- import {
4
- RENDERER_EXTENSION,
5
- type RendererContext,
6
- RendererError,
7
- type RenderRequest,
8
- type RenderResponse,
9
- } from "../renderer-foundation/index.js";
10
- import { resolveNotificationVariables } from "./resolve-variables.js";
11
- import { createSimpleRenderer, type MailBranding, simpleRenderer } from "./simple-renderer.js";
12
-
13
- // Adapter: simpleRenderer.render hat `Promise<string>`-Signatur (Legacy
14
- // NotificationRenderer-Contract), renderer-foundation erwartet
15
- // `Promise<RenderResponse>` mit discriminated union. Mapper bewahrt
16
- // die simpleRenderer-Implementierung (Template-Strings → HTML mit
17
- // Inline-CSS) und packt sie in den RendererPlugin-Contract.
18
- //
19
- // Exported damit der Adapter-Pfad direkt testbar ist (unit-test).
20
- export async function adaptToFoundation(
21
- req: RenderRequest,
22
- ctx: RendererContext,
23
- renderer: NotificationRenderer = simpleRenderer,
24
- ): Promise<RenderResponse> {
25
- if (req.kind !== "notification") {
26
- // Defensiver Guard — Foundation wählt Plugins nur für matching kinds,
27
- // dieser Pfad sollte unter normalen Umständen nie erreicht werden.
28
- throw new RendererError(
29
- `renderer-simple supports only kind="notification", got "${req.kind}"`,
30
- "invalid_payload",
31
- );
32
- }
33
- const variables = await resolveNotificationVariables(req, ctx);
34
- const input = { template: req.payload.template ?? "", variables, locale: req.payload.locale };
35
- const html = await renderer.render(input);
36
- const text = renderer.renderText ? await renderer.renderText(input) : undefined;
37
- return text === undefined ? { kind: "notification", html } : { kind: "notification", html, text };
38
- }
39
-
40
- export type RendererSimpleOptions = {
41
- /** Branding applied to every notification mail, including the framework auth mails. */
42
- readonly mailBranding?: MailBranding;
43
- };
44
-
45
- export function createRendererSimpleFeature(options?: RendererSimpleOptions): FeatureDefinition {
46
- const renderer = options?.mailBranding
47
- ? createSimpleRenderer(options.mailBranding)
48
- : simpleRenderer;
49
- return defineFeature("renderer-simple", (r) => {
50
- r.describe(
51
- 'Default renderer plugin for `kind="notification"`: takes a structured `EmailTemplateData` variable map (with `header`, `sections[]` of text/button objects, and optional `footer`; falls back to `title`/`body` if no structured fields are present) and returns rendered HTML with inline CSS. Requires `renderer-foundation`; sufficient for plain notification emails \u2014 swap it for `renderer-mail-html` if you need MJML/Markdown layouts.',
52
- );
53
- r.uiHints({
54
- displayLabel: "Renderer \u00b7 Simple",
55
- category: "notifications",
56
- recommended: false,
57
- });
58
- r.requires("renderer-foundation");
59
- r.optionalRequires("template-resolver");
60
-
61
- r.useExtension(RENDERER_EXTENSION, "simple", {
62
- kinds: ["notification"] as const,
63
- render: (req, ctx) => adaptToFoundation(req, ctx, renderer),
64
- });
65
- });
66
- }
@@ -1,8 +0,0 @@
1
- export { createRendererSimpleFeature, type RendererSimpleOptions } from "./feature.js";
2
- export type { MailBadge, MailChip, MailStage, MailStageState, MailTone } from "./mail-blocks.js";
3
- export {
4
- createSimpleRenderer,
5
- type LocalizedText,
6
- type MailBranding,
7
- simpleRenderer,
8
- } from "./simple-renderer.js";
@@ -1,138 +0,0 @@
1
- import { escapeHtml, escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
2
-
3
- export type MailTone = "neutral" | "info" | "success" | "warning" | "danger";
4
- export type MailStageState = "done" | "current" | "upcoming";
5
-
6
- export type MailBadge = { readonly label: string; readonly tone?: MailTone };
7
- export type MailStage = { readonly label: string; readonly state: MailStageState };
8
- export type MailChip = { readonly label: string; readonly tone?: MailTone };
9
-
10
- // Literal types (as const) keep the colors compile-time known, so they can go into style
11
- // attributes without escaping.
12
- const TONE_COLORS = {
13
- neutral: { bg: "#f3f4f6", fg: "#374151" },
14
- info: { bg: "#dbeafe", fg: "#1e40af" },
15
- success: { bg: "#dcfce7", fg: "#166534" },
16
- warning: { bg: "#fef3c7", fg: "#92400e" },
17
- danger: { bg: "#fee2e2", fg: "#991b1b" },
18
- } as const satisfies Readonly<Record<MailTone, { readonly bg: string; readonly fg: string }>>;
19
-
20
- type ToneColors = (typeof TONE_COLORS)[MailTone];
21
- type PillMargin = "0" | "0 6px 6px 0";
22
-
23
- const STAGE_STATES: readonly MailStageState[] = ["done", "current", "upcoming"];
24
- const STAGE_DONE_COLOR = "#16a34a";
25
- const STAGE_UPCOMING_BG = "#e5e7eb";
26
- const STAGE_UPCOMING_FG = "#9ca3af";
27
-
28
- function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {
29
- return typeof value === "object" && value !== null;
30
- }
31
-
32
- function isMailTone(value: unknown): value is MailTone {
33
- return typeof value === "string" && Object.hasOwn(TONE_COLORS, value);
34
- }
35
-
36
- function toneColors(tone: unknown): ToneColors {
37
- return TONE_COLORS[isMailTone(tone) ? tone : "neutral"];
38
- }
39
-
40
- function isStageState(value: unknown): value is MailStageState {
41
- return STAGE_STATES.some((state) => state === value);
42
- }
43
-
44
- // Template data is runtime data (variables of a stored template): entries that do not match are
45
- // skipped instead of failing the whole mail.
46
- function isStage(value: unknown): value is MailStage {
47
- return isRecord(value) && typeof value["label"] === "string" && isStageState(value["state"]);
48
- }
49
-
50
- function isChip(value: unknown): value is MailChip {
51
- return isRecord(value) && typeof value["label"] === "string";
52
- }
53
-
54
- export function parseBadge(value: unknown): MailBadge | undefined {
55
- return isChip(value) && value.label !== "" ? value : undefined;
56
- }
57
-
58
- function validStages(value: unknown): readonly MailStage[] {
59
- return Array.isArray(value) ? value.filter(isStage) : [];
60
- }
61
-
62
- function validChips(value: unknown): readonly MailChip[] {
63
- return Array.isArray(value) ? value.filter(isChip) : [];
64
- }
65
-
66
- function pill(chip: MailChip, margin: PillMargin): string {
67
- const { bg, fg } = toneColors(chip.tone);
68
- return `<span style="display:inline-block;margin:${margin};padding:2px 10px;border-radius:999px;background:${bg};color:${fg};font-size:12px;font-weight:600;line-height:18px">${escapeHtml(chip.label)}</span>`;
69
- }
70
-
71
- export function renderBadge(badge: MailBadge): string {
72
- return `<p style="margin:0 0 12px">${pill(badge, "0")}</p>`;
73
- }
74
-
75
- export function renderChips(chips: unknown): string {
76
- const valid = validChips(chips);
77
- if (valid.length === 0) return "";
78
- return `<p style="margin:0 0 16px">${valid.map((chip) => pill(chip, "0 6px 6px 0")).join("")}</p>`;
79
- }
80
-
81
- function stageMarker(stage: MailStage, position: number, primaryColor: string): string {
82
- const base =
83
- "display:inline-block;width:24px;height:24px;line-height:24px;border-radius:12px;text-align:center;font-size:12px;font-weight:700";
84
- const primary = escapeHtmlAttr(primaryColor);
85
- if (stage.state === "done") {
86
- return `<span style="${base};background:${STAGE_DONE_COLOR};color:#fff">&#10003;</span>`;
87
- }
88
- if (stage.state === "current") {
89
- return `<span style="${base};background:#fff;border:2px solid ${primary};line-height:20px;width:20px;height:20px;color:${primary}">${position}</span>`;
90
- }
91
- return `<span style="${base};background:${STAGE_UPCOMING_BG};color:${STAGE_UPCOMING_FG}">${position}</span>`;
92
- }
93
-
94
- function stageCell(stage: MailStage, position: number, primaryColor: string): string {
95
- const labelColor = stage.state === "upcoming" ? STAGE_UPCOMING_FG : "#111";
96
- const weight = stage.state === "current" ? 700 : 400;
97
- return `<td align="center" valign="top" style="padding:0 4px;font-family:sans-serif">${stageMarker(stage, position, primaryColor)}<div style="margin:4px 0 0;font-size:12px;font-weight:${weight};color:${labelColor}">${escapeHtml(stage.label)}</div></td>`;
98
- }
99
-
100
- function stageConnector(previous: MailStage): string {
101
- const color = previous.state === "done" ? STAGE_DONE_COLOR : STAGE_UPCOMING_BG;
102
- return `<td valign="top" style="padding:11px 0 0"><div style="width:24px;height:2px;line-height:2px;font-size:2px;background:${color}">&nbsp;</div></td>`;
103
- }
104
-
105
- export function renderStages(stages: unknown, primaryColor: string): string {
106
- const valid = validStages(stages);
107
- if (valid.length === 0) return "";
108
- const cellsHtml = valid
109
- .map((stage, index) => {
110
- const previous = valid[index - 1];
111
- const connector = previous ? stageConnector(previous) : "";
112
- return `${connector}${stageCell(stage, index + 1, primaryColor)}`;
113
- })
114
- .join("");
115
- return `<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:0 0 16px"><tr>${cellsHtml}</tr></table>`;
116
- }
117
-
118
- const STAGE_TEXT_MARKER: Readonly<Record<MailStageState, string>> = {
119
- done: "✓",
120
- current: "▶",
121
- upcoming: "○",
122
- };
123
-
124
- export function badgeText(badge: MailBadge): string {
125
- return `[${badge.label}]`;
126
- }
127
-
128
- export function stagesText(stages: unknown): string {
129
- return validStages(stages)
130
- .map((stage) => `${STAGE_TEXT_MARKER[stage.state]} ${stage.label}`)
131
- .join(" → ");
132
- }
133
-
134
- export function chipsText(chips: unknown): string {
135
- return validChips(chips)
136
- .map((chip) => chip.label)
137
- .join(" · ");
138
- }