@cosmicdrift/kumiko-samples 0.378.0 → 0.380.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 (509) 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-overview/feature.ts +1 -1
  132. package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +1 -1
  133. package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +2 -2
  134. package/packages/bundled-features/src/cap-overview/index.ts +9 -9
  135. package/packages/bundled-features/src/cap-overview/screens.ts +2 -2
  136. package/packages/bundled-features/src/cap-overview/web/cap-cards-panel.tsx +1 -2
  137. package/packages/bundled-features/src/cap-overview/web/cap-usage-bar.tsx +1 -2
  138. package/packages/bundled-features/src/cap-overview/web/cap-usage-cell.tsx +0 -2
  139. package/packages/bundled-features/src/cap-overview/web/client-plugin.tsx +3 -4
  140. package/packages/bundled-features/src/cap-overview/{constants.ts → web/constants.ts} +0 -1
  141. package/packages/bundled-features/src/cap-overview/web/index.ts +0 -1
  142. package/packages/bundled-features/src/cap-overview/{usage-math.ts → web/usage-math.ts} +1 -2
  143. package/packages/bundled-features/src/compliance-profiles/feature.ts +5 -5
  144. package/packages/bundled-features/src/compliance-profiles/index.ts +6 -6
  145. package/packages/bundled-features/src/compliance-profiles/web/client-plugin.tsx +2 -3
  146. package/packages/bundled-features/src/compliance-profiles/web/compliance-profile-catalog.tsx +1 -2
  147. package/packages/bundled-features/src/compliance-profiles/{constants.ts → web/constants.ts} +0 -1
  148. package/packages/bundled-features/src/compliance-profiles/web/i18n.ts +0 -1
  149. package/packages/bundled-features/src/compliance-profiles/web/index.ts +0 -1
  150. package/packages/bundled-features/src/config/index.ts +6 -6
  151. package/packages/bundled-features/src/config/web/client-plugin.ts +1 -2
  152. package/packages/bundled-features/src/config/{constants.ts → web/constants.ts} +0 -1
  153. package/packages/bundled-features/src/config/web/i18n.ts +0 -1
  154. package/packages/bundled-features/src/config/web/index.ts +1 -3
  155. package/packages/bundled-features/src/config/write-helpers.ts +1 -1
  156. package/packages/bundled-features/src/custom-fields/feature.ts +8 -8
  157. package/packages/bundled-features/src/custom-fields/handlers/clear-custom-field.write.ts +1 -1
  158. package/packages/bundled-features/src/custom-fields/handlers/define-tenant-field.write.ts +1 -1
  159. package/packages/bundled-features/src/custom-fields/handlers/delete-system-field.write.ts +1 -1
  160. package/packages/bundled-features/src/custom-fields/handlers/delete-tenant-field.write.ts +4 -4
  161. package/packages/bundled-features/src/custom-fields/handlers/set-custom-field.write.ts +1 -1
  162. package/packages/bundled-features/src/custom-fields/handlers/update-tenant-field.write.ts +1 -1
  163. package/packages/bundled-features/src/custom-fields/index.ts +8 -8
  164. package/packages/bundled-features/src/custom-fields/lib/value-schema.ts +1 -1
  165. package/packages/bundled-features/src/custom-fields/schemas.ts +1 -1
  166. package/packages/bundled-features/src/custom-fields/web/client-plugin.tsx +1 -2
  167. package/packages/bundled-features/src/custom-fields/{constants.ts → web/constants.ts} +18 -19
  168. package/packages/bundled-features/src/custom-fields/web/custom-fields-form-section.tsx +1 -2
  169. package/packages/bundled-features/src/custom-fields/web/i18n.ts +0 -1
  170. package/packages/bundled-features/src/custom-fields/web/index.ts +2 -3
  171. package/packages/bundled-features/src/custom-fields/wire-for-entity.ts +1 -2
  172. package/packages/bundled-features/src/delivery/address-opt-out.ts +1 -1
  173. package/packages/bundled-features/src/delivery/attempt-log-retention.ts +24 -8
  174. package/packages/bundled-features/src/delivery/changes.json +6 -0
  175. package/packages/bundled-features/src/delivery/constants.ts +2 -2
  176. package/packages/bundled-features/src/delivery/i18n.ts +1 -1
  177. package/packages/bundled-features/src/delivery/unsubscribe-page.ts +1 -1
  178. package/packages/bundled-features/src/delivery/web/client-plugin.tsx +6 -7
  179. package/packages/bundled-features/src/delivery/web/delivery-channel-cell.tsx +0 -1
  180. package/packages/bundled-features/src/delivery/web/delivery-error-cell.tsx +0 -1
  181. package/packages/bundled-features/src/delivery/web/delivery-status-cell.tsx +1 -2
  182. package/packages/bundled-features/src/delivery/web/delivery-time-cell.tsx +0 -1
  183. package/packages/bundled-features/src/delivery/web/delivery-type-cell.tsx +0 -1
  184. package/packages/bundled-features/src/delivery/web/index.ts +0 -1
  185. package/packages/bundled-features/src/delivery/{public-names.ts → web/public-names.ts} +0 -1
  186. package/packages/bundled-features/src/document-ingest-foundation/changes.json +6 -0
  187. package/packages/bundled-features/src/document-ingest-foundation/events.ts +4 -0
  188. package/packages/bundled-features/src/document-ingest-foundation/executor.ts +2 -1
  189. package/packages/bundled-features/src/document-ingest-foundation/feature.ts +18 -4
  190. package/packages/bundled-features/src/document-ingest-foundation/index.ts +1 -0
  191. package/packages/bundled-features/src/feature-toggles/feature.ts +7 -7
  192. package/packages/bundled-features/src/feature-toggles/global-feature-state-table.ts +14 -13
  193. package/packages/bundled-features/src/feature-toggles/handlers/list.query.ts +1 -1
  194. package/packages/bundled-features/src/feature-toggles/handlers/registered.query.ts +1 -1
  195. package/packages/bundled-features/src/feature-toggles/handlers/set.write.ts +3 -3
  196. package/packages/bundled-features/src/feature-toggles/index.ts +6 -6
  197. package/packages/bundled-features/src/feature-toggles/web/client-plugin.tsx +1 -2
  198. package/packages/bundled-features/src/feature-toggles/{constants.ts → web/constants.ts} +0 -1
  199. package/packages/bundled-features/src/feature-toggles/web/i18n.ts +0 -1
  200. package/packages/bundled-features/src/feature-toggles/web/index.ts +0 -1
  201. package/packages/bundled-features/src/files/changes.json +7 -0
  202. package/packages/bundled-features/src/folders/feature.ts +1 -1
  203. package/packages/bundled-features/src/folders/handlers/clear-folder.write.ts +1 -1
  204. package/packages/bundled-features/src/folders/handlers/delete-folder.write.ts +1 -1
  205. package/packages/bundled-features/src/folders/handlers/set-folder.write.ts +1 -1
  206. package/packages/bundled-features/src/folders/index.ts +8 -8
  207. package/packages/bundled-features/src/folders/web/client-plugin.tsx +1 -3
  208. package/packages/bundled-features/src/folders/{constants.ts → web/constants.ts} +0 -1
  209. package/packages/bundled-features/src/folders/web/folder-manager.tsx +1 -2
  210. package/packages/bundled-features/src/folders/web/folder-section.tsx +1 -2
  211. package/packages/bundled-features/src/folders/web/i18n.ts +0 -1
  212. package/packages/bundled-features/src/folders/web/index.ts +1 -2
  213. package/packages/bundled-features/src/folders/web/tree.ts +0 -1
  214. package/packages/bundled-features/src/form-draft/changes.json +7 -0
  215. package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +25 -11
  216. package/packages/bundled-features/src/form-draft/decrypt-draft.ts +24 -0
  217. package/packages/bundled-features/src/form-draft/entity.ts +6 -9
  218. package/packages/bundled-features/src/form-draft/feature.ts +1 -1
  219. package/packages/bundled-features/src/form-draft/handlers/cleanup.job.ts +26 -5
  220. package/packages/bundled-features/src/form-draft/handlers/discard.write.ts +1 -1
  221. package/packages/bundled-features/src/form-draft/handlers/get.query.ts +6 -7
  222. package/packages/bundled-features/src/form-draft/handlers/list.query.ts +13 -7
  223. package/packages/bundled-features/src/form-draft/handlers/save.write.ts +5 -5
  224. package/packages/bundled-features/src/form-draft/index.ts +8 -8
  225. package/packages/bundled-features/src/form-draft/lookup.ts +10 -4
  226. package/packages/bundled-features/src/form-draft/schemas.ts +1 -1
  227. package/packages/bundled-features/src/form-draft/{constants.ts → web/constants.ts} +0 -1
  228. package/packages/bundled-features/src/form-draft-user-data/hooks.ts +2 -2
  229. package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +10 -10
  230. package/packages/bundled-features/src/jobs/feature.ts +7 -7
  231. package/packages/bundled-features/src/jobs/handlers/retry.write.ts +1 -1
  232. package/packages/bundled-features/src/jobs/handlers/trigger.write.ts +1 -1
  233. package/packages/bundled-features/src/jobs/index.ts +1 -1
  234. package/packages/bundled-features/src/jobs/job-run-table.ts +4 -3
  235. package/packages/bundled-features/src/jobs/web/client-plugin.tsx +2 -3
  236. package/packages/bundled-features/src/jobs/{constants.ts → web/constants.ts} +0 -1
  237. package/packages/bundled-features/src/jobs/web/default-translations.ts +7 -0
  238. package/packages/bundled-features/src/jobs/web/i18n.ts +53 -7
  239. package/packages/bundled-features/src/jobs/web/index.ts +0 -1
  240. package/packages/bundled-features/src/ledger/changes.json +7 -0
  241. package/packages/bundled-features/src/ledger/entity.ts +1 -1
  242. package/packages/bundled-features/src/ledger/feature.ts +1 -1
  243. package/packages/bundled-features/src/ledger/handlers/confirm-schedule-period.write.ts +2 -10
  244. package/packages/bundled-features/src/ledger/handlers/create-transaction.write.ts +1 -1
  245. package/packages/bundled-features/src/ledger/handlers/reports.query.ts +1 -1
  246. package/packages/bundled-features/src/ledger/handlers/reverse-transaction.write.ts +1 -1
  247. package/packages/bundled-features/src/ledger/index.ts +13 -13
  248. package/packages/bundled-features/src/ledger/recurring.ts +1 -1
  249. package/packages/bundled-features/src/ledger/reports.ts +1 -1
  250. package/packages/bundled-features/src/ledger/schemas.ts +1 -1
  251. package/packages/bundled-features/src/ledger/{constants.ts → web/constants.ts} +0 -1
  252. package/packages/bundled-features/src/ledger/web/index.ts +11 -12
  253. package/packages/bundled-features/src/legal-pages/README.md +1 -1
  254. package/packages/bundled-features/src/legal-pages/feature.ts +5 -5
  255. package/packages/bundled-features/src/legal-pages/index.ts +11 -11
  256. package/packages/bundled-features/src/legal-pages/lib/boot-check.ts +1 -1
  257. package/packages/bundled-features/src/legal-pages/lib/validate-routes.ts +1 -1
  258. package/packages/bundled-features/src/legal-pages/web/client-plugin.ts +1 -2
  259. package/packages/bundled-features/src/legal-pages/{constants.ts → web/constants.ts} +6 -8
  260. package/packages/bundled-features/src/legal-pages/web/index.ts +0 -1
  261. package/packages/bundled-features/src/managed-pages/feature.ts +1 -1
  262. package/packages/bundled-features/src/managed-pages/web/client-plugin.tsx +1 -2
  263. package/packages/bundled-features/src/managed-pages/web/default-translations.ts +17 -0
  264. package/packages/bundled-features/src/managed-pages/web/i18n.ts +59 -16
  265. package/packages/bundled-features/src/managed-pages/web/index.ts +1 -2
  266. package/packages/bundled-features/src/metrics/compute.ts +1 -1
  267. package/packages/bundled-features/src/metrics/feature.ts +6 -6
  268. package/packages/bundled-features/src/metrics/index.ts +8 -8
  269. package/packages/bundled-features/src/metrics/types.ts +1 -1
  270. package/packages/bundled-features/src/metrics/validate.ts +1 -1
  271. package/packages/bundled-features/src/metrics/{constants.ts → web/constants.ts} +0 -1
  272. package/packages/bundled-features/src/metrics/window.ts +1 -1
  273. package/packages/bundled-features/src/notes-history/feature.ts +1 -1
  274. package/packages/bundled-features/src/notes-history/handlers/add-note.write.ts +1 -1
  275. package/packages/bundled-features/src/notes-history/index.ts +8 -8
  276. package/packages/bundled-features/src/notes-history/web/client-plugin.tsx +1 -3
  277. package/packages/bundled-features/src/notes-history/{constants.ts → web/constants.ts} +0 -1
  278. package/packages/bundled-features/src/notes-history/web/i18n.ts +0 -1
  279. package/packages/bundled-features/src/notes-history/web/index.ts +2 -3
  280. package/packages/bundled-features/src/notes-history/web/notes-section.tsx +2 -3
  281. package/packages/bundled-features/src/personal-access-tokens/feature.ts +12 -10
  282. package/packages/bundled-features/src/personal-access-tokens/handlers/available-scopes.query.ts +1 -1
  283. package/packages/bundled-features/src/personal-access-tokens/handlers/create.write.ts +1 -1
  284. package/packages/bundled-features/src/personal-access-tokens/handlers/revoke.write.ts +1 -1
  285. package/packages/bundled-features/src/personal-access-tokens/hash.ts +1 -1
  286. package/packages/bundled-features/src/personal-access-tokens/i18n.ts +2 -2
  287. package/packages/bundled-features/src/personal-access-tokens/index.ts +5 -5
  288. package/packages/bundled-features/src/personal-access-tokens/resolver.ts +1 -1
  289. package/packages/bundled-features/src/personal-access-tokens/schema/api-token.ts +3 -3
  290. package/packages/bundled-features/src/personal-access-tokens/screens.ts +2 -2
  291. package/packages/bundled-features/src/personal-access-tokens/{constants.ts → web/constants.ts} +0 -1
  292. package/packages/bundled-features/src/personal-access-tokens/{scopes.ts → web/scopes.ts} +0 -1
  293. package/packages/bundled-features/src/presets/changes.json +7 -0
  294. package/packages/bundled-features/src/presets/index.ts +0 -1
  295. package/packages/bundled-features/src/presets/security-baseline.ts +1 -1
  296. package/packages/bundled-features/src/renderer-foundation/types.ts +1 -1
  297. package/packages/bundled-features/src/secrets/feature.ts +1 -1
  298. package/packages/bundled-features/src/secrets/handlers/delete.write.ts +1 -1
  299. package/packages/bundled-features/src/secrets/handlers/list.query.ts +1 -1
  300. package/packages/bundled-features/src/secrets/handlers/set.write.ts +1 -1
  301. package/packages/bundled-features/src/secrets/i18n.ts +1 -1
  302. package/packages/bundled-features/src/secrets/index.ts +8 -8
  303. package/packages/bundled-features/src/secrets/{constants.ts → web/constants.ts} +0 -1
  304. package/packages/bundled-features/src/secrets/write-gate.ts +1 -1
  305. package/packages/bundled-features/src/sessions/feature.ts +7 -4
  306. package/packages/bundled-features/src/sessions/schema/user-session.ts +2 -3
  307. package/packages/bundled-features/src/sessions/{i18n.ts → web/i18n.ts} +0 -1
  308. package/packages/bundled-features/src/shared/index.ts +6 -1
  309. package/packages/bundled-features/src/shared/purge-files-reaching-subject.ts +10 -2
  310. package/packages/bundled-features/src/shared/web/translate-or-raw.ts +0 -1
  311. package/packages/bundled-features/src/subscription-stripe/feature.ts +2 -0
  312. package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +19 -0
  313. package/packages/bundled-features/src/subscription-stripe/verify-webhook.ts +1 -0
  314. package/packages/bundled-features/src/tags/changes.json +6 -0
  315. package/packages/bundled-features/src/tags/feature.ts +1 -1
  316. package/packages/bundled-features/src/tags/handlers/assign-tag.write.ts +11 -7
  317. package/packages/bundled-features/src/tags/handlers/create-tag.write.ts +1 -1
  318. package/packages/bundled-features/src/tags/handlers/delete-tag.write.ts +4 -1
  319. package/packages/bundled-features/src/tags/handlers/remove-tag.write.ts +1 -1
  320. package/packages/bundled-features/src/tags/handlers/update-tag.write.ts +1 -1
  321. package/packages/bundled-features/src/tags/index.ts +12 -12
  322. package/packages/bundled-features/src/tags/screens.ts +1 -1
  323. package/packages/bundled-features/src/tags/web/client-plugin.tsx +1 -3
  324. package/packages/bundled-features/src/tags/{constants.ts → web/constants.ts} +4 -1
  325. package/packages/bundled-features/src/tags/web/entity-tags.tsx +1 -2
  326. package/packages/bundled-features/src/tags/web/i18n.ts +2 -2
  327. package/packages/bundled-features/src/tags/web/index.ts +2 -3
  328. package/packages/bundled-features/src/tags/web/tag-chip.tsx +0 -1
  329. package/packages/bundled-features/src/tags/web/tag-filter.tsx +1 -2
  330. package/packages/bundled-features/src/tags/web/tag-manager.tsx +5 -4
  331. package/packages/bundled-features/src/tags/web/tag-picker.tsx +0 -1
  332. package/packages/bundled-features/src/tags/web/tag-section.tsx +1 -2
  333. package/packages/bundled-features/src/tags/web/tags-cell.tsx +1 -2
  334. package/packages/bundled-features/src/template-resolver/README.md +1 -1
  335. package/packages/bundled-features/src/template-resolver/api.ts +75 -28
  336. package/packages/bundled-features/src/template-resolver/changes.json +43 -0
  337. package/packages/bundled-features/src/template-resolver/compile-document-body.ts +1 -1
  338. package/packages/bundled-features/src/template-resolver/compile-steps-block.ts +2 -2
  339. package/packages/bundled-features/src/template-resolver/document-ops.ts +5 -5
  340. package/packages/bundled-features/src/template-resolver/document-sanitizer.ts +4 -4
  341. package/packages/bundled-features/src/template-resolver/feature.ts +13 -4
  342. package/packages/bundled-features/src/template-resolver/handlers/by-slug.query.ts +6 -1
  343. package/packages/bundled-features/src/template-resolver/handlers/by-tenant.query.ts +6 -2
  344. package/packages/bundled-features/src/template-resolver/handlers/collection-item.query.ts +1 -1
  345. package/packages/bundled-features/src/template-resolver/handlers/collection-list.query.ts +1 -1
  346. package/packages/bundled-features/src/template-resolver/handlers/collection-set.write.ts +19 -2
  347. package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +2 -2
  348. package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +7 -1
  349. package/packages/bundled-features/src/template-resolver/handlers/discard-draft.write.ts +1 -1
  350. package/packages/bundled-features/src/template-resolver/handlers/document-base.ts +8 -2
  351. package/packages/bundled-features/src/template-resolver/handlers/document-blocks.query.ts +2 -2
  352. package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +5 -2
  353. package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +4 -2
  354. package/packages/bundled-features/src/template-resolver/handlers/layout-source.query.ts +10 -5
  355. package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +5 -2
  356. package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +68 -10
  357. package/packages/bundled-features/src/template-resolver/handlers/patch-draft.write.ts +1 -1
  358. package/packages/bundled-features/src/template-resolver/handlers/resource-shared.ts +1 -1
  359. package/packages/bundled-features/src/template-resolver/handlers/resource-upload.write.ts +1 -1
  360. package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +2 -2
  361. package/packages/bundled-features/src/template-resolver/handlers/set.write.ts +1 -1
  362. package/packages/bundled-features/src/template-resolver/handlers/shared.ts +1 -1
  363. package/packages/bundled-features/src/template-resolver/handlers/template-gates.ts +6 -6
  364. package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +2 -2
  365. package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +8 -2
  366. package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +4 -2
  367. package/packages/bundled-features/src/template-resolver/i18n.ts +25 -0
  368. package/packages/bundled-features/src/template-resolver/index.ts +60 -59
  369. package/packages/bundled-features/src/template-resolver/layout-appearance.ts +1 -1
  370. package/packages/bundled-features/src/template-resolver/layout-regions.ts +1 -1
  371. package/packages/bundled-features/src/template-resolver/seeding.ts +16 -7
  372. package/packages/bundled-features/src/template-resolver/store-template-resource.ts +2 -2
  373. package/packages/bundled-features/src/template-resolver/table.ts +1 -1
  374. package/packages/bundled-features/src/template-resolver/template-bundle.ts +2 -2
  375. package/packages/bundled-features/src/template-resolver/template-delivery-policy.ts +82 -0
  376. package/packages/bundled-features/src/template-resolver/template-draft.ts +1 -1
  377. package/packages/bundled-features/src/template-resolver/template-edit-policy.ts +2 -2
  378. package/packages/bundled-features/src/template-resolver/template-meta.ts +2 -2
  379. package/packages/bundled-features/src/template-resolver/template-preview.ts +14 -10
  380. package/packages/bundled-features/src/template-resolver/template-usage.ts +1 -1
  381. package/packages/bundled-features/src/template-resolver/template-validation.ts +2 -2
  382. package/packages/bundled-features/src/template-resolver/testing.ts +2 -2
  383. package/packages/bundled-features/src/template-resolver/user-content-table.ts +1 -1
  384. package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +4 -5
  385. package/packages/bundled-features/src/template-resolver/{constants.ts → web/constants.ts} +7 -10
  386. package/packages/bundled-features/src/template-resolver/{document-blocks.ts → web/document-blocks.ts} +0 -1
  387. package/packages/bundled-features/src/template-resolver/web/document-editor-extensions.ts +2 -3
  388. package/packages/bundled-features/src/template-resolver/web/document-editor.tsx +0 -1
  389. package/packages/bundled-features/src/template-resolver/{document-images.ts → web/document-images.ts} +0 -1
  390. package/packages/bundled-features/src/template-resolver/{document-schema.ts → web/document-schema.ts} +0 -1
  391. package/packages/bundled-features/src/template-resolver/{document-steps.ts → web/document-steps.ts} +0 -1
  392. package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +6 -7
  393. package/packages/bundled-features/src/template-resolver/web/document-toolbar-blocks.ts +2 -3
  394. package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +1 -2
  395. package/packages/bundled-features/src/template-resolver/web/i18n.ts +2 -1
  396. package/packages/bundled-features/src/template-resolver/web/index.ts +13 -14
  397. package/packages/bundled-features/src/template-resolver/{mail-constants.ts → web/mail-constants.ts} +0 -1
  398. package/packages/bundled-features/src/template-resolver/{pdf-constants.ts → web/pdf-constants.ts} +0 -1
  399. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor-lazy.tsx +0 -1
  400. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +5 -4
  401. package/packages/bundled-features/src/template-resolver/{placeholder-text.ts → web/placeholder-text.ts} +0 -1
  402. package/packages/bundled-features/src/template-resolver/{qualified-names.ts → web/qualified-names.ts} +0 -1
  403. package/packages/bundled-features/src/template-resolver/web/use-stable-by-key.ts +0 -1
  404. package/packages/bundled-features/src/tenant/handlers/add-member.write.ts +1 -1
  405. package/packages/bundled-features/src/tenant/index.ts +7 -7
  406. package/packages/bundled-features/src/tenant/invitation-table.ts +1 -1
  407. package/packages/bundled-features/src/tenant/invited-membership.ts +1 -1
  408. package/packages/bundled-features/src/tenant/last-tenant-admin.ts +1 -1
  409. package/packages/bundled-features/src/tenant/member-role-options.ts +1 -1
  410. package/packages/bundled-features/src/tenant/screens.ts +2 -2
  411. package/packages/bundled-features/src/tenant/web/client-plugin.tsx +1 -2
  412. package/packages/bundled-features/src/tenant/{constants.ts → web/constants.ts} +3 -6
  413. package/packages/bundled-features/src/tenant/web/index.ts +0 -1
  414. package/packages/bundled-features/src/tenant/web/member-roles-cell.tsx +0 -1
  415. package/packages/bundled-features/src/tenant/web/member-status-cell.tsx +0 -1
  416. package/packages/bundled-features/src/tenant-lifecycle/stages.ts +2 -2
  417. package/packages/bundled-features/src/tier-engine/entity.ts +1 -1
  418. package/packages/bundled-features/src/tier-engine/feature.ts +7 -7
  419. package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +1 -1
  420. package/packages/bundled-features/src/tier-engine/index.ts +8 -8
  421. package/packages/bundled-features/src/tier-engine/web/client-plugin.tsx +2 -3
  422. package/packages/bundled-features/src/tier-engine/{constants.ts → web/constants.ts} +2 -3
  423. package/packages/bundled-features/src/tier-engine/{i18n.ts → web/i18n.ts} +0 -1
  424. package/packages/bundled-features/src/tier-engine/web/index.ts +0 -1
  425. package/packages/bundled-features/src/user/handlers/create.write.ts +1 -1
  426. package/packages/bundled-features/src/user/handlers/update-roles.ts +1 -1
  427. package/packages/bundled-features/src/user/handlers/update.write.ts +1 -1
  428. package/packages/bundled-features/src/user/index.ts +1 -1
  429. package/packages/bundled-features/src/user/invited-global-roles.ts +1 -1
  430. package/packages/bundled-features/src/user/schema/user.ts +1 -1
  431. package/packages/bundled-features/src/user/{constants.ts → web/constants.ts} +0 -1
  432. package/packages/bundled-features/src/user-data-rights/changes.json +14 -0
  433. package/packages/bundled-features/src/user-data-rights/env.ts +1 -1
  434. package/packages/bundled-features/src/user-data-rights/feature.ts +9 -9
  435. package/packages/bundled-features/src/user-data-rights/index.ts +1 -1
  436. package/packages/bundled-features/src/user-data-rights/lib/resolve-tenant-model.ts +1 -1
  437. package/packages/bundled-features/src/user-data-rights/mail-notifications.ts +1 -1
  438. package/packages/bundled-features/src/user-data-rights/web/client-plugin.tsx +2 -3
  439. package/packages/bundled-features/src/user-data-rights/web/confirm-deletion-screen.tsx +0 -1
  440. package/packages/bundled-features/src/user-data-rights/{constants.ts → web/constants.ts} +11 -12
  441. package/packages/bundled-features/src/user-data-rights/web/default-translations.ts +91 -0
  442. package/packages/bundled-features/src/user-data-rights/web/i18n.ts +41 -91
  443. package/packages/bundled-features/src/user-data-rights/web/index.ts +1 -2
  444. package/packages/bundled-features/src/user-data-rights/web/privacy-center-screen.tsx +1 -2
  445. package/packages/bundled-features/src/user-data-rights/web/public-deletion-gate.tsx +0 -1
  446. package/packages/bundled-features/src/user-data-rights/web/request-deletion-screen.tsx +0 -1
  447. package/packages/bundled-features/src/user-data-rights-defaults/hooks/file-ref.userdata-hook.ts +10 -1
  448. package/packages/bundled-features/src/user-profile/feature.ts +2 -2
  449. package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +1 -1
  450. package/packages/bundled-features/src/user-profile/index.ts +3 -2
  451. package/packages/bundled-features/src/user-profile/web/client-plugin.ts +2 -3
  452. package/packages/bundled-features/src/user-profile/{constants.ts → web/constants.ts} +0 -1
  453. package/packages/bundled-features/src/user-profile/{i18n.ts → web/i18n.ts} +0 -1
  454. package/packages/bundled-features/src/user-profile/web/profile-screen.tsx +2 -3
  455. package/packages/bundled-features/src/waitlist/changes.json +6 -0
  456. package/packages/bundled-features/src/waitlist/entity.ts +1 -1
  457. package/packages/bundled-features/src/waitlist/feature.ts +5 -5
  458. package/packages/bundled-features/src/waitlist/handlers/export-purge.write.ts +3 -3
  459. package/packages/bundled-features/src/waitlist/handlers/invite.write.ts +5 -1
  460. package/packages/bundled-features/src/waitlist/handlers/reject.write.ts +1 -1
  461. package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +3 -3
  462. package/packages/bundled-features/src/waitlist/import.ts +1 -1
  463. package/packages/bundled-features/src/waitlist/index.ts +11 -11
  464. package/packages/bundled-features/src/waitlist/legacy-migrate.ts +1 -1
  465. package/packages/bundled-features/src/waitlist/link-on-signup.ts +1 -1
  466. package/packages/bundled-features/src/waitlist/mail.ts +7 -6
  467. package/packages/bundled-features/src/waitlist/payloads.ts +1 -1
  468. package/packages/bundled-features/src/waitlist/screens.ts +1 -1
  469. package/packages/bundled-features/src/waitlist/{constants.ts → web/constants.ts} +0 -1
  470. package/samples/apps/styleguide/src/features/content/web.tsx +0 -1
  471. package/samples/apps/ui-walkthrough/src/app/client.tsx +2 -0
  472. package/samples/apps/ui-walkthrough/src/app/seed-rental.ts +17 -1
  473. package/samples/apps/ui-walkthrough/src/app/seed-templates.ts +11 -0
  474. package/samples/apps/ui-walkthrough/src/app/server.ts +2 -1
  475. package/samples/apps/ui-walkthrough/src/features/billing/feature.ts +35 -7
  476. package/samples/apps/ui-walkthrough/src/features/billing/i18n.ts +6 -0
  477. package/samples/apps/ui-walkthrough/src/features/rental/entities.ts +27 -0
  478. package/samples/apps/ui-walkthrough/src/features/rental/feature.ts +61 -7
  479. package/samples/apps/ui-walkthrough/src/features/rental/i18n.ts +48 -24
  480. package/samples/apps/ui-walkthrough/src/features/rental/invoice-receive.ts +0 -1
  481. package/samples/apps/ui-walkthrough/src/features/rental/lease-support.ts +17 -1
  482. package/samples/apps/ui-walkthrough/src/features/rental/qualified-names.ts +3 -0
  483. package/samples/apps/ui-walkthrough/src/features/rental/receipts-upload.ts +70 -0
  484. package/samples/apps/ui-walkthrough/src/features/rental/screens.ts +90 -13
  485. package/samples/apps/ui-walkthrough/src/features/rental/web/index.ts +0 -2
  486. package/samples/apps/ui-walkthrough/src/features/subscription-plans/feature.ts +103 -0
  487. package/samples/apps/ui-walkthrough/src/features/subscription-plans/i18n.ts +36 -0
  488. package/samples/apps/ui-walkthrough/src/features/subscription-plans/index.ts +2 -0
  489. package/samples/apps/ui-walkthrough/src/features/subscription-plans/seed.ts +21 -0
  490. package/samples/apps/ui-walkthrough/src/run-config.ts +11 -0
  491. package/samples/apps/use-all-bundled/e2e-billing-gallery/server.ts +1 -1
  492. package/samples/apps/use-all-bundled/feature-manifest.json +4 -4
  493. package/samples/apps/use-all-bundled/scripts/check-coverage.ts +1 -1
  494. package/samples/apps/use-all-bundled/src/app/dev-app-options.ts +3 -1
  495. package/samples/recipes/compose-stacks/README.md +1 -2
  496. package/samples/recipes/embedded/README.md +5 -5
  497. package/samples/recipes/embedded/src/handlers/invoice-receive.write.ts +0 -1
  498. package/samples/recipes/embedded/src/screens/invoice-receive.screen.ts +9 -12
  499. package/samples/recipes/legal-pages/README.md +2 -2
  500. package/samples/recipes/legal-pages/src/feature.ts +1 -1
  501. package/samples/recipes/reference-data/src/feature.ts +3 -7
  502. package/packages/bundled-features/src/admin-shell/i18n.ts +0 -54
  503. package/packages/bundled-features/src/audit/i18n.ts +0 -25
  504. package/packages/bundled-features/src/auth-mfa/web/qrcode-browser.d.ts +0 -11
  505. package/packages/bundled-features/src/jobs/i18n.ts +0 -55
  506. package/packages/bundled-features/src/managed-pages/i18n.ts +0 -63
  507. package/packages/bundled-features/src/presets/dsgvo-self-service.ts +0 -35
  508. package/packages/bundled-features/src/user-data-rights/i18n.ts +0 -44
  509. package/samples/apps/ui-walkthrough/src/features/rental/web/upload-standard-layout.tsx +0 -61
@@ -0,0 +1,70 @@
1
+ import { createEntityExecutor, type WriteHandlerFn } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { UnprocessableError } from "@cosmicdrift/kumiko-framework/errors";
3
+ import {
4
+ bindPendingFileRef,
5
+ FILE_REF_ENTITY_NAME,
6
+ fileRefEntity,
7
+ loadPendingFileRef,
8
+ } from "@cosmicdrift/kumiko-framework/files";
9
+ import { z } from "zod";
10
+ import { receiptEntity } from "./entities";
11
+
12
+ const UPLOAD_SCREEN_QN = "rental:screen:upload-standard-layout";
13
+ const UPLOAD_FIELD = "belege";
14
+
15
+ const { executor: receiptExecutor } = createEntityExecutor("receipt", receiptEntity);
16
+ const { executor: fileRefExecutor } = createEntityExecutor(FILE_REF_ENTITY_NAME, fileRefEntity);
17
+
18
+ export const receiptsUploadPayloadSchema = z.object({
19
+ belege: z.array(z.uuid()).min(1).max(10),
20
+ });
21
+
22
+ const unknownFile = () =>
23
+ new UnprocessableError("unknown_upload", { i18nKey: "rental.upload.unknownFile" });
24
+
25
+ // The form only carries file ids. Each one must be the caller's own, still unbound upload of this
26
+ // form; everything is checked before the first receipt is written so a bad id creates nothing.
27
+ export const uploadReceipts: WriteHandlerFn<z.infer<typeof receiptsUploadPayloadSchema>> = async (
28
+ event,
29
+ ctx,
30
+ ) => {
31
+ const { user, payload } = event;
32
+ const pendingNames: { readonly fileRefId: string; readonly fileName: string }[] = [];
33
+ for (const fileRefId of new Set(payload.belege)) {
34
+ const pending = await loadPendingFileRef({
35
+ fileRefId,
36
+ entityName: UPLOAD_SCREEN_QN,
37
+ fieldName: UPLOAD_FIELD,
38
+ user,
39
+ db: ctx.db,
40
+ });
41
+ if (pending === undefined) throw unknownFile();
42
+ // fileName is personal data and may be encrypted, so only the executor's detail returns it.
43
+ const row = await fileRefExecutor.detail({ id: fileRefId }, user, ctx.db);
44
+ if (row === null) throw unknownFile();
45
+ pendingNames.push({ fileRefId, fileName: String(row["fileName"]) });
46
+ }
47
+
48
+ const belege: { readonly label: string }[] = [];
49
+ for (const { fileRefId, fileName } of pendingNames) {
50
+ const created = await receiptExecutor.create(
51
+ { datei: fileRefId, dateiname: fileName },
52
+ user,
53
+ ctx.db,
54
+ );
55
+ if (!created.isSuccess) return created;
56
+ const outcome = await bindPendingFileRef({
57
+ fileRefId,
58
+ entityName: UPLOAD_SCREEN_QN,
59
+ fieldName: UPLOAD_FIELD,
60
+ entityId: String(created.data.id),
61
+ bindTo: { entityName: "receipt", fieldName: "datei" },
62
+ registry: ctx.registry,
63
+ user,
64
+ db: ctx.db,
65
+ });
66
+ if (outcome !== "bound") throw unknownFile();
67
+ belege.push({ label: fileName });
68
+ }
69
+ return { isSuccess: true, data: { count: belege.length, belege } };
70
+ };
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  createDateField,
3
- createDecimalField,
4
3
  createEmbeddedListField,
4
+ createFilesField,
5
+ createNumberField,
5
6
  createSelectField,
6
7
  createTextField,
7
8
  } from "@cosmicdrift/kumiko-framework/engine";
@@ -12,6 +13,7 @@ import type {
12
13
  EntityListScreenDefinition,
13
14
  ProjectionDetailScreenDefinition,
14
15
  } from "@cosmicdrift/kumiko-framework/ui-types";
16
+ import { RECEIPT_ACCEPT } from "./entities";
15
17
  import { PAYMENT_METHODS, POSITION_KINDS, POSITION_MEASURES } from "./lease-support";
16
18
  import { RENTAL_QUERIES, RENTAL_WRITES } from "./qualified-names";
17
19
 
@@ -362,10 +364,50 @@ export const rentalDashboardScreen: DashboardScreenDefinition = {
362
364
  valueField: "rent",
363
365
  subField: "rentSub",
364
366
  },
367
+ {
368
+ kind: "text",
369
+ id: "profile-hint",
370
+ label: "rental.dashboard.profileHint.title",
371
+ blocks: [
372
+ { kind: "paragraph", text: "rental.dashboard.profileHint.text" },
373
+ {
374
+ kind: "link",
375
+ text: "rental.dashboard.profileHint.link",
376
+ screen: "management-profile",
377
+ roles: ["Admin"],
378
+ },
379
+ ],
380
+ },
365
381
  ],
366
382
  access: OPEN_ACCESS,
367
383
  };
368
384
 
385
+ export const managementProfileScreen: ActionFormScreenDefinition = {
386
+ id: "management-profile",
387
+ type: "actionForm",
388
+ handler: RENTAL_WRITES.managementProfileSave,
389
+ load: { query: RENTAL_QUERIES.managementProfile, singleton: true },
390
+ fields: {
391
+ name: createTextField({ personal: false, reason: "is_business_data", required: true }),
392
+ anschrift: createTextField({ personal: false, reason: "is_business_data" }),
393
+ zahlungsziel: createNumberField({ integer: true, required: true }),
394
+ },
395
+ fieldLabels: {
396
+ name: "rental:entity:__action-form__:field:profileName",
397
+ anschrift: "rental:entity:__action-form__:field:anschrift",
398
+ zahlungsziel: "rental:entity:__action-form__:field:zahlungsziel",
399
+ },
400
+ layout: {
401
+ sections: [
402
+ {
403
+ description: "rental.managementProfile.hint",
404
+ fields: ["name", "anschrift", "zahlungsziel"],
405
+ },
406
+ ],
407
+ },
408
+ access: OPEN_ACCESS,
409
+ };
410
+
369
411
  export const adjustRentScreen: ActionFormScreenDefinition = {
370
412
  id: "adjust-rent",
371
413
  type: "actionForm",
@@ -473,8 +515,13 @@ export const receiveInvoiceLines = createEmbeddedListField(
473
515
  vatAmount: { type: "money" },
474
516
  },
475
517
  {
518
+ minItems: 1,
476
519
  derived: { vatAmount: { op: "percent", from: ["amount", "vatRate"] } },
477
- totals: ["amount", "vatAmount"],
520
+ totals: [
521
+ { label: "rental.total.net", from: ["amount"] },
522
+ { label: "rental.total.vat", from: ["vatAmount"] },
523
+ { label: "rental.total.gross", from: ["amount", "vatAmount"] },
524
+ ],
478
525
  },
479
526
  );
480
527
 
@@ -491,29 +538,23 @@ export const receiveInvoiceScreen: ActionFormScreenDefinition = {
491
538
  }),
492
539
  lines: receiveInvoiceLines,
493
540
  netTotal: { type: "money", currency: EUR },
494
- vatRate: createDecimalField({ precision: 5, scale: 2 }),
495
541
  vatTotal: { type: "money", currency: EUR },
496
542
  grossTotal: { type: "money", currency: EUR },
497
543
  },
498
544
  derive: {
499
545
  netTotal: { op: "listSum", list: "lines", cell: "amount" },
500
- vatTotal: {
501
- op: "listSum",
502
- list: "lines",
503
- cell: "vatAmount",
504
- whenEmpty: { op: "percent", from: ["netTotal", "vatRate"] },
505
- },
546
+ vatTotal: { op: "listSum", list: "lines", cell: "vatAmount" },
506
547
  grossTotal: { op: "sum", from: ["netTotal", "vatTotal"] },
507
548
  },
508
549
  layout: {
509
550
  width: "full",
510
551
  sections: [
511
552
  { fields: ["supplier", "invoiceNumber"] },
512
- { title: "rental.section.invoiceLines", columns: 1, fields: ["lines"] },
513
553
  {
514
- title: "rental.section.invoiceTotals",
515
- description: "rental.section.invoiceTotalsHint",
516
- fields: ["netTotal", "vatRate", "vatTotal", "grossTotal"],
554
+ title: "rental.section.invoiceLines",
555
+ description: "rental.section.invoiceLines.description",
556
+ columns: 1,
557
+ fields: ["lines"],
517
558
  },
518
559
  ],
519
560
  },
@@ -597,3 +638,39 @@ export const leaseUnterlagenScreen: ProjectionDetailScreenDefinition = {
597
638
  },
598
639
  access: OPEN_ACCESS,
599
640
  };
641
+
642
+ export const uploadReceiptsScreen: ActionFormScreenDefinition = {
643
+ id: "upload-standard-layout",
644
+ type: "actionForm",
645
+ description: "rental.upload.screenDescription",
646
+ handler: RENTAL_WRITES.receiptsUpload,
647
+ fields: {
648
+ belege: createFilesField({
649
+ personal: false,
650
+ reason: "is_business_data",
651
+ accept: RECEIPT_ACCEPT,
652
+ maxSize: "10MB",
653
+ maxCount: 10,
654
+ display: "dropzone",
655
+ }),
656
+ },
657
+ fieldLabels: { belege: "rental:entity:__action-form__:field:belege" },
658
+ layout: {
659
+ sections: [
660
+ {
661
+ icon: "upload",
662
+ title: "rental.upload.zoneTitle",
663
+ description: "rental.upload.description",
664
+ fields: ["belege"],
665
+ },
666
+ ],
667
+ },
668
+ submitLabel: "rental.upload.submit",
669
+ successFacts: [
670
+ { field: "count", label: "rental.upload.factCount" },
671
+ { field: "belege", label: "rental.upload.factFiles" },
672
+ ],
673
+ resetOnSuccess: true,
674
+ cancelTarget: false,
675
+ access: OPEN_ACCESS,
676
+ };
@@ -1,12 +1,10 @@
1
1
  import type { ClientFeatureDefinition } from "@cosmicdrift/kumiko-renderer/web";
2
2
  import { rentalTranslations } from "../i18n";
3
3
  import { LeaseHubHeader, LeaseHubHistory } from "./lease-hub-components";
4
- import { UploadStandardLayout } from "./upload-standard-layout";
5
4
 
6
5
  export const rentalClient: ClientFeatureDefinition = {
7
6
  name: "rental",
8
7
  translations: rentalTranslations,
9
- components: { "upload-standard-layout": UploadStandardLayout },
10
8
  extensionSectionComponents: {
11
9
  LeaseHubHeader,
12
10
  LeaseHubHistory,
@@ -0,0 +1,103 @@
1
+ import {
2
+ createBillingFoundationFeature,
3
+ type ProviderPrice,
4
+ type SubscriptionProviderPlugin,
5
+ } from "@cosmicdrift/kumiko-bundled-features/billing-foundation";
6
+ import { defineFeature, i18nKey } from "@cosmicdrift/kumiko-framework/engine";
7
+ import { toKeyFirst } from "../translations";
8
+ import { subscriptionPlansTranslations } from "./i18n";
9
+
10
+ export const PLAN_PROVIDER = "nordlicht-mock-provider";
11
+ export const PLAN_TERMS_SLUG = "portal-terms";
12
+ const PLAN_PATH = "/billing-plans";
13
+
14
+ const PRICE_IDS = { starter: "price_portal_starter", pro: "price_portal_professional" };
15
+
16
+ const PRICES: readonly ProviderPrice[] = [
17
+ {
18
+ priceId: PRICE_IDS.starter,
19
+ unitAmount: 1900,
20
+ currency: "eur",
21
+ interval: "month",
22
+ intervalCount: 1,
23
+ active: true,
24
+ metadata: {},
25
+ },
26
+ {
27
+ priceId: PRICE_IDS.pro,
28
+ unitAmount: 4900,
29
+ currency: "eur",
30
+ interval: "month",
31
+ intervalCount: 1,
32
+ active: true,
33
+ metadata: {},
34
+ },
35
+ ];
36
+
37
+ // The sample talks to no payment provider: prices are fixed and checkout just returns to the plans screen.
38
+ const mockProvider: SubscriptionProviderPlugin = {
39
+ verifyAndParseWebhook: async (rawBody) => JSON.parse(rawBody),
40
+ priceToTier: { [PRICE_IDS.starter]: "starter", [PRICE_IDS.pro]: "pro" },
41
+ retrievePrices: async (_ctx, priceIds) => PRICES.filter((p) => priceIds.includes(p.priceId)),
42
+ createCheckoutSession: async (_ctx, options) => ({ url: options.successUrl }),
43
+ cancelSubscription: async () => {},
44
+ };
45
+
46
+ export function createPortalBillingFeature(baseUrl: string) {
47
+ return createBillingFoundationFeature({
48
+ baseUrl,
49
+ catalog: {
50
+ plans: ["starter", "pro"],
51
+ tierLabelKey: (tier) => `subscription-plans:plan.${tier}.label`,
52
+ benefits: (tier) => [
53
+ { labelKey: `subscription-plans:plan.${tier}.benefit.units` },
54
+ { labelKey: `subscription-plans:plan.${tier}.benefit.support` },
55
+ ],
56
+ resolveCurrentTier: async () => "free",
57
+ viewRoles: ["TenantAdmin", "SystemAdmin"],
58
+ successPath: PLAN_PATH,
59
+ cancelPath: PLAN_PATH,
60
+ providerName: PLAN_PROVIDER,
61
+ contact: {
62
+ labelKey: "subscription-plans:plan.portfolio.label",
63
+ benefits: [{ labelKey: "subscription-plans:plan.portfolio.benefit" }],
64
+ actionLabelKey: "subscription-plans:plan.portfolio.action",
65
+ href: "mailto:vertrieb@nordlicht-immobilien.de",
66
+ },
67
+ },
68
+ consumerProtection: {
69
+ termsTextBlock: PLAN_TERMS_SLUG,
70
+ vatNote: { de: "Preise inkl. USt.", en: "Prices include VAT." },
71
+ operatorEmail: "abrechnung@nordlicht-immobilien.de",
72
+ legalLinks: {
73
+ de: {
74
+ terms: "https://nordlicht-immobilien.de/agb",
75
+ withdrawal: "https://nordlicht-immobilien.de/widerruf",
76
+ privacy: "https://nordlicht-immobilien.de/datenschutz",
77
+ },
78
+ en: {
79
+ terms: "https://nordlicht-immobilien.de/terms",
80
+ withdrawal: "https://nordlicht-immobilien.de/withdrawal",
81
+ privacy: "https://nordlicht-immobilien.de/privacy",
82
+ },
83
+ },
84
+ },
85
+ });
86
+ }
87
+
88
+ // Named after the screen it opens: "billing" already belongs to the invoice mails.
89
+ export const subscriptionPlansFeature = defineFeature("subscription-plans", (r) => {
90
+ r.describe("Plan overview of the tenant admin: nav entry, plan labels and the mock provider.");
91
+ r.requires({ features: ["billing-foundation"] });
92
+ r.translations({ keys: toKeyFirst(subscriptionPlansTranslations) });
93
+ r.useExtension("subscriptionProvider", PLAN_PROVIDER, mockProvider);
94
+ r.nav({
95
+ id: "subscription-plans",
96
+ label: i18nKey("subscription-plans.nav.plans"),
97
+ icon: "file",
98
+ screen: "billing-foundation:screen:billing-plans",
99
+ order: 50,
100
+ access: { roles: ["TenantAdmin", "SystemAdmin"] },
101
+ });
102
+ return {};
103
+ });
@@ -0,0 +1,36 @@
1
+ import type { TranslationsByLocale } from "@cosmicdrift/kumiko-renderer";
2
+
3
+ export const subscriptionPlansTranslations: TranslationsByLocale = {
4
+ de: {
5
+ "subscription-plans.nav.plans": "Tarif und Abrechnung",
6
+ "subscription-plans:plan.free.label": "Basis",
7
+ "subscription-plans:plan.free.benefit.units": "Bis zu 3 Wohneinheiten",
8
+ "subscription-plans:plan.free.benefit.support": "Hilfe-Center",
9
+ "subscription-plans:plan.starter.label": "Starter",
10
+ "subscription-plans:plan.starter.benefit.units": "Bis zu 25 Wohneinheiten",
11
+ "subscription-plans:plan.starter.benefit.support": "Support per E-Mail",
12
+ "subscription-plans:plan.pro.label": "Professional",
13
+ "subscription-plans:plan.pro.benefit.units": "Bis zu 150 Wohneinheiten",
14
+ "subscription-plans:plan.pro.benefit.support": "Telefonischer Support",
15
+ "subscription-plans:plan.portfolio.label": "Portfolio",
16
+ "subscription-plans:plan.portfolio.benefit":
17
+ "Größere Bestände mit individuellen Konditionen und festem Ansprechpartner",
18
+ "subscription-plans:plan.portfolio.action": "Vertrieb kontaktieren",
19
+ },
20
+ en: {
21
+ "subscription-plans.nav.plans": "Plan and billing",
22
+ "subscription-plans:plan.free.label": "Basic",
23
+ "subscription-plans:plan.free.benefit.units": "Up to 3 residential units",
24
+ "subscription-plans:plan.free.benefit.support": "Help center",
25
+ "subscription-plans:plan.starter.label": "Starter",
26
+ "subscription-plans:plan.starter.benefit.units": "Up to 25 residential units",
27
+ "subscription-plans:plan.starter.benefit.support": "Support by email",
28
+ "subscription-plans:plan.pro.label": "Professional",
29
+ "subscription-plans:plan.pro.benefit.units": "Up to 150 residential units",
30
+ "subscription-plans:plan.pro.benefit.support": "Support by phone",
31
+ "subscription-plans:plan.portfolio.label": "Portfolio",
32
+ "subscription-plans:plan.portfolio.benefit":
33
+ "Larger portfolios with individual terms and a dedicated contact",
34
+ "subscription-plans:plan.portfolio.action": "Contact sales",
35
+ },
36
+ };
@@ -0,0 +1,2 @@
1
+ export { createPortalBillingFeature, subscriptionPlansFeature } from "./feature";
2
+ export { seedPlanTerms } from "./seed";
@@ -0,0 +1,21 @@
1
+ import { seedTextBlock } from "@cosmicdrift/kumiko-bundled-features/template-resolver/seeding";
2
+ import type { SeedFn } from "@cosmicdrift/kumiko-dev-server";
3
+ import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
4
+ import { PLAN_TERMS_SLUG } from "./feature";
5
+
6
+ const TERMS_BY_LOCALE: Readonly<Record<string, string>> = {
7
+ de: "Diese Bedingungen gelten für alle kostenpflichtigen Tarife des Nordlicht Mieterportals. Der Vertrag läuft monatlich und ist zum Ende des Abrechnungszeitraums kündbar.",
8
+ en: "These terms apply to every paid plan of the Nordlicht tenant portal. The contract renews monthly and can be terminated at the end of the billing period.",
9
+ };
10
+
11
+ export const seedPlanTerms: SeedFn = async (stack) => {
12
+ for (const [locale, content] of Object.entries(TERMS_BY_LOCALE)) {
13
+ await seedTextBlock(stack.db, {
14
+ tenantId: SYSTEM_TENANT_ID,
15
+ slug: PLAN_TERMS_SLUG,
16
+ locale,
17
+ title: "Nutzungsbedingungen",
18
+ content,
19
+ });
20
+ }
21
+ };
@@ -9,6 +9,7 @@ import {
9
9
  createChannelEmailFeature,
10
10
  createInMemoryTransport,
11
11
  } from "@cosmicdrift/kumiko-bundled-features/channel-email";
12
+ import { createComplianceProfilesFeature } from "@cosmicdrift/kumiko-bundled-features/compliance-profiles";
12
13
  import { createDeliveryFeature } from "@cosmicdrift/kumiko-bundled-features/delivery";
13
14
  import { createFilesFeature } from "@cosmicdrift/kumiko-bundled-features/files";
14
15
  import { createManagedPagesFeature } from "@cosmicdrift/kumiko-bundled-features/managed-pages";
@@ -18,6 +19,7 @@ import {
18
19
  type TenantEditableTemplate,
19
20
  templateEditAccess,
20
21
  } from "@cosmicdrift/kumiko-bundled-features/template-resolver";
22
+ import { createTenantLifecycleFeature } from "@cosmicdrift/kumiko-bundled-features/tenant-lifecycle";
21
23
  import { createDocumentDesignerFeature } from "@cosmicdrift/kumiko-document-designer";
22
24
  import { localeDe } from "@cosmicdrift/kumiko-locale-de";
23
25
  import { localeEs } from "@cosmicdrift/kumiko-locale-es";
@@ -26,6 +28,10 @@ import { createRendererPuppeteerClientFeature } from "@cosmicdrift/kumiko-render
26
28
  import { TENANT_ADMIN_EMAIL } from "./app/auth-constants";
27
29
  import { billingFeature } from "./features/billing";
28
30
  import { rentalFeature } from "./features/rental";
31
+ import {
32
+ createPortalBillingFeature,
33
+ subscriptionPlansFeature,
34
+ } from "./features/subscription-plans";
29
35
  import { taskFeature } from "./features/tasks";
30
36
  import { vehiclesFeature } from "./features/vehicles";
31
37
 
@@ -58,6 +64,11 @@ export const APP_FEATURES = [
58
64
  rentalFeature,
59
65
  vehiclesFeature,
60
66
  billingFeature,
67
+ createPortalBillingFeature(`http://localhost:${process.env["PORT"] ?? "4173"}`),
68
+ subscriptionPlansFeature,
69
+ // billing-foundation stores the subscription as tenant data and keeps the compliance profile.
70
+ createComplianceProfilesFeature(),
71
+ createTenantLifecycleFeature(),
61
72
  createTemplateResolverFeature({ tenantEditable: TENANT_EDITABLE_TEMPLATES }),
62
73
  // Only the branding rail of the layout editor needs managed-pages; the sample
63
74
  // serves no public pages, so no host resolves to a tenant.
@@ -72,7 +72,7 @@ await runDevApp({
72
72
  },
73
73
  extraContext: (deps) => ({
74
74
  ...createDeliveryTestContext(deps),
75
- templateResolver: createTemplateResolverApi(deps.db),
75
+ templateResolver: createTemplateResolverApi(deps.db, deps.registry),
76
76
  }),
77
77
  extraRoutes: [
78
78
  ...createE2eSeedRoutes({ mailOutbox: galleryMailTransport }),
@@ -21,7 +21,7 @@
21
21
  },
22
22
  {
23
23
  "name": "admin-shell",
24
- "description": "Registers tenant-admin and platform-admin workspaces with provider nav into owner-feature screens (`tenant:screen:members`, `audit:screen:audit-log`, `tenant:screen:tenant-list`, `jobs:screen:job-runs`, optional `tier-engine:screen:tier-admin`, optional `cap-overview:screen:my-caps`/`cap-overview:screen:tenant-cap-list`, optional `document-designer` template navs) and the two overview dashboards, built from the metrics list (`metrics` option, default DEFAULT_METRICS). Mount after user, tenant, audit, jobs, metrics and metrics-system; pass `workspaceIds` to match app URL conventions (e.g. Studio `d`/`s`, PublicStatus `admin`/`sysadmin`). Client: `adminShellClient()`, `tenantClient()`, `auditClient()`, `jobsClient()`, optional `tierEngineClient()`.",
24
+ "description": "Registers tenant-admin and platform-admin workspaces with provider nav into owner-feature screens (`tenant:screen:members`, `audit:screen:audit-log`, `audit:screen:audit-log-all-tenants`, `tenant:screen:tenant-list`, `jobs:screen:job-runs`, optional `tier-engine:screen:tier-admin`, optional `cap-overview:screen:my-caps`/`cap-overview:screen:tenant-cap-list`, optional `document-designer` template navs) and the two overview dashboards, built from the metrics list (`metrics` option, default DEFAULT_METRICS). Mount after user, tenant, audit, jobs, metrics and metrics-system; pass `workspaceIds` to match app URL conventions (e.g. Studio `d`/`s`, PublicStatus `admin`/`sysadmin`). Client: `adminShellClient()`, `tenantClient()`, `auditClient()`, `jobsClient()`, optional `tierEngineClient()`.",
25
25
  "toggleableDefault": null,
26
26
  "requires": [
27
27
  "user",
@@ -69,7 +69,7 @@
69
69
  },
70
70
  {
71
71
  "name": "audit",
72
- "description": "Exposes the framework's event store as a paginated, filterable audit log via the `audit:query:list` handler (accessible to `Admin` and `SystemAdmin` roles). No separate table or projection — the event store is the audit trail by construction: every entity write already records who, when, what entity, and the event payload with PII stripped. A SystemAdmin can pass `scope: \"system\"` to read the app-instance system events such as `app.started`. Filter by `aggregateType`, `aggregateId`, `eventType`, `userId`, or time range. Also records `audit:event:escape-hatch-used` whenever a handler uses one of the framework's escape hatches (unsafeRaw, acknowledgeCrossTenant, db.global() writes, or a granted identity switch).",
72
+ "description": "Exposes the framework's event store as a paginated, filterable audit log via the `audit:query:list` handler (accessible to `Admin` and `SystemAdmin` roles). No separate table or projection — the event store is the audit trail by construction: every entity write already records who, when, what entity, and the event payload with PII stripped. A SystemAdmin can pass `scope: \"system\"` to read the app-instance system events such as `app.started`, and reads the events of every tenant (system tenant excluded) through `audit:query:list-all-tenants` and the `audit-log-all-tenants` screen. Filter by `aggregateType`, `aggregateId`, `eventType`, `userId`, or time range. Also records `audit:event:escape-hatch-used` whenever a handler uses one of the framework's escape hatches (unsafeRaw, acknowledgeCrossTenant, db.global() writes, or a granted identity switch).",
73
73
  "toggleableDefault": null,
74
74
  "requires": [
75
75
  "tenant",
@@ -2209,7 +2209,7 @@
2209
2209
  },
2210
2210
  {
2211
2211
  "name": "renderer-puppeteer-client",
2212
- "description": "A `renderer-foundation` plugin that prints documents to PDF through a separately deployed `puppeteer-service`. A `document-pdf` request names a stored template (`{ slug, locale, variables }`): the plugin resolves the template and its `pdf-layout` (header, signature and footer regions), embeds every image as a data URI, turns repeating table header rows into a real table head and sends the finished HTML to the service, which performs no network fetches itself. The page format, margins, file name pattern and title come from the template meta. A write handler renders with `renderPdfBeforeTransaction(ctx, { slug, locale, variables })` as its `beforeTransaction` phase, so the print service call holds no database connection, and stores the result with `storeRenderedPdf(ctx, event.prepared)` in the handler: a file plus a `rendered-pdf` record that keeps the template and layout version, so a stored document can always be traced to the text it was created from. The `puppeteer-status` query tells a TenantAdmin whether the service answers; while it does not, rendering fails with a `puppeteer_unavailable` error (HTTP 503) and no PDF is created. `image-snapshot` requests still take ready HTML in `payload.content`, which must embed all resources as data: URIs because the service fetches no http(s) resources. Configure the endpoint and token with `PUPPETEER_SERVICE_URL` and `PUPPETEER_AUTH_TOKEN`; the client retries 5xx and network errors with exponential backoff (default 3 retries), 4xx fails at once. The feature adds the `rendered_pdfs` table, so apps that mount it need a schema migration.",
2212
+ "description": "A `renderer-foundation` plugin that prints documents to PDF through a separately deployed `puppeteer-service`. A `document-pdf` request names a stored template (`{ slug, locale, variables }`): the plugin resolves the template and its `pdf-layout` (header, signature and footer regions), embeds every image as a data URI, turns repeating table header rows into a real table head and sends the finished HTML to the service, which performs no network fetches itself. The page format, margins, file name pattern and title come from the template meta. A write handler renders with `renderPdfBeforeTransaction(ctx, { slug, locale, variables })` as its `beforeTransaction` phase, so the print service call holds no database connection, and stores the result with `storeRenderedPdf(ctx, event.prepared)` in the handler: a file plus a `rendered-pdf` record that keeps the template and layout version, so a stored document can always be traced to the text it was created from. The `puppeteer-status` query tells a TenantAdmin whether the service answers; while it does not, rendering fails with a `puppeteer_unavailable` error (HTTP 503) and no PDF is created. `image-snapshot` requests still take ready HTML in `payload.content`, which must embed all resources as data: URIs because the service fetches no http(s) resources. Configure the endpoint and token with `PUPPETEER_SERVICE_URL` and `PUPPETEER_AUTH_TOKEN`; the client retries 502, 503 and network errors with exponential backoff (default 3 retries); 4xx, other 5xx and client timeouts fail at once, because the render has already run. The feature adds the `rendered_pdfs` table, so apps that mount it need a schema migration.",
2213
2213
  "toggleableDefault": null,
2214
2214
  "requires": [
2215
2215
  "renderer-foundation"
@@ -2602,7 +2602,7 @@
2602
2602
  },
2603
2603
  {
2604
2604
  "name": "template-resolver",
2605
- "description": "Every piece of editable text lives here, in one entity: mail bodies, notification texts, PDF document templates, AI prompts and plain text blocks. What a record is used for is the `kind` (`notification`, `mail`, `mail-layout`, `document-pdf`, `pdf-layout`, `ai-prompt`, `text-block`, `snippet`, `image-snapshot`).\n\nReading is one call — `ctx.templateResolver.resolveTemplate({ tenantId, slug, kind, locale })`. It walks four levels: tenant+locale, system+locale, tenant+fallback-locale, system+fallback-locale. A tenant overrides a system default by simply having its own record; no application code changes. Writing programmatically goes through the `upsertSystem`, `upsertTenant`, `publish` and `archive` write handlers; a SystemAdmin retires or restores a system default with `publishSystem` and `archiveSystem`, and a tenant drops its override with `resetToSystem`. Saving a draft over an active template keeps the active version served and stores the edit beside it (column `draft`); `publish` makes it the served version, `discardDraft` / `discardDraftSystem` throws it away.\n\nEditing templates is for SystemAdmin only. A TenantAdmin edits only the templates the app unlocks at the mount site, `createTemplateResolverFeature({ tenantEditable: [{ kind: \"mail\", slug: \"status-update\", access: { roles: [\"TenantAdmin\"] } }] })`, and every write handler enforces that on the server (override, draft, publish, archive, reset, copy-locale, import, resources, `set`). Each entry carries the `access` roles allowed to edit it, because a bundled feature cannot know the host's roles. Reading and delivery do not depend on it: a tenant override that exists is served. A collection mounted without `access` is SystemAdmin only.\n\nText an editor should be able to change belongs in a collection, declared at mount: `createTemplateResolverFeature({ collections: [{ id, kind, access: { roles }, nav }] })`. It appears in the navigation, and `access` is part of the mount because a bundled feature cannot know the host's roles. Each collection gets its own `<id>-list` / `<id>-item` / `<id>-set` handlers, so the dispatcher enforces the separation.\n\nHow a collection is edited follows from `contentFormat`: `plain` gives a text area, `rich` a small WYSIWYG (bold, italic, headings, lists, links), `markdown` a text area that stores markdown text (same insertable chips as `plain`). All three offer the collection's `variableSchema` as insertable chips and a preview rendered with sample data — an editor sees what `{{firstName}}` becomes without sending a mail. An app can register its own editor for a format; the last `clientFeature` that registers a format wins (a conflict logs a warning).\n\nA collection is tenant-wide by default. With `ownership: \"user\"` every user keeps their own entries — mail signatures being the obvious case. Those rows live in the separate `user-content-entry` entity and count as user data, so mounting one also requires the `template-resolver-user-data` feature and a migration on the app side.\n\n`validate` dry-runs the write pipeline for a draft (sanitizer, layout rules, placeholders) and reports what the template uses and misses. `export-bundle` / `import-bundle` (and `import-bundle-system`) move one template with all locales and resource files between tenants or environments; `copy-locale` starts a translation from an existing locale. Layouts are structured: a mail or PDF layout is `data-header` / `data-content` / `data-signature` / `data-footer` regions, and mjml layout source is system-only.\n\n`preview` renders a draft (or the stored template) with sample values taken from the `example` of each declared variable, through the same gates and the same mail renderer as a real send, and returns subject, html, `displayHtml` (the html with cid images as data URIs for an editor), text, attachments (inline images and files) and `checks` (images resolved, plain text present, size under 10 MiB, required placeholders used); nothing is saved or sent. `send-test` mails that rendering to the calling user's own address only, subject prefixed with `[TEST]`, at most 10 per user and hour; it needs `delivery`, `channel-email` and `renderer-mail-html` to be mounted. `send-test-status` tells an editor beforehand whether that send can work for the caller (`canSend`, else `reason` `no_channel` or `no_address`), never returning the address.\n\nImages used by a template (`<img data-resource=\"logo\">`) are uploaded with `resourceUpload`, which re-encodes them, stores a file and links it under the key; `resourceRemove` unlinks it. `linkedResources` is written only through these two handlers. With `purpose: \"attachment\"` and a `fileName`, `resourceUpload` stores a PDF, PNG or JPEG as a file attachment of a mail template (original bytes, at most 10) that the mail renderer attaches after the inline images. `resourceLinkPdf` instead links a `document-pdf` template under a key: every mail sent from the template then carries that PDF, rendered at send time with the mail's own variables and locale, under a file name pattern such as `invoice-{{number}}.pdf`; files and linked PDFs share the limit of 10.\n\nReplaces the former `text-content` feature; its blocks now live here as kind `text-block`.",
2605
+ "description": "Every piece of editable text lives here, in one entity: mail bodies, notification texts, PDF document templates, AI prompts and plain text blocks. What a record is used for is the `kind` (`notification`, `mail`, `mail-layout`, `document-pdf`, `pdf-layout`, `ai-prompt`, `text-block`, `snippet`, `image-snapshot`).\n\nReading is one call — `ctx.templateResolver.resolveTemplate({ tenantId, slug, kind, locale })`. It walks four levels: tenant+locale, system+locale, tenant+fallback-locale, system+fallback-locale. A tenant overrides a system default by simply having its own record; no application code changes. Writing programmatically goes through the `upsertSystem`, `upsertTenant`, `publish` and `archive` write handlers; a SystemAdmin retires or restores a system default with `publishSystem` and `archiveSystem`, and a tenant drops its override with `resetToSystem`. Saving a draft over an active template keeps the active version served and stores the edit beside it (column `draft`); `publish` makes it the served version, `discardDraft` / `discardDraftSystem` throws it away.\n\nEditing templates is for SystemAdmin only. A TenantAdmin edits only the templates the app unlocks at the mount site, `createTemplateResolverFeature({ tenantEditable: [{ kind: \"mail\", slug: \"status-update\", access: { roles: [\"TenantAdmin\"] } }] })`, and every write handler enforces that on the server (override, draft, publish, archive, reset, copy-locale, import, resources, `set`). Each entry carries the `access` roles allowed to edit it, because a bundled feature cannot know the host's roles. The same list decides delivery: a tenant override is served (mail, PDF, layouts, text blocks) only while its template is listed in tenantEditable; otherwise the system version is served. A locked override stays stored, a SystemAdmin sees it in the overview marked as not delivered, and listing the template makes it effective again. Tenant rows of a collection mounted with `access` are always served. A collection mounted without `access` is SystemAdmin only.\n\nText an editor should be able to change belongs in a collection, declared at mount: `createTemplateResolverFeature({ collections: [{ id, kind, access: { roles }, nav }] })`. It appears in the navigation, and `access` is part of the mount because a bundled feature cannot know the host's roles. Each collection gets its own `<id>-list` / `<id>-item` / `<id>-set` handlers, so the dispatcher enforces the separation.\n\nHow a collection is edited follows from `contentFormat`: `plain` gives a text area, `rich` a small WYSIWYG (bold, italic, headings, lists, links), `markdown` a text area that stores markdown text (same insertable chips as `plain`). All three offer the collection's `variableSchema` as insertable chips and a preview rendered with sample data — an editor sees what `{{firstName}}` becomes without sending a mail. An app can register its own editor for a format; the last `clientFeature` that registers a format wins (a conflict logs a warning).\n\nA collection is tenant-wide by default. With `ownership: \"user\"` every user keeps their own entries — mail signatures being the obvious case. Those rows live in the separate `user-content-entry` entity and count as user data, so mounting one also requires the `template-resolver-user-data` feature and a migration on the app side.\n\n`validate` dry-runs the write pipeline for a draft (sanitizer, layout rules, placeholders) and reports what the template uses and misses. `export-bundle` / `import-bundle` (and `import-bundle-system`) move one template with all locales and resource files between tenants or environments; `copy-locale` starts a translation from an existing locale. Layouts are structured: a mail or PDF layout is `data-header` / `data-content` / `data-signature` / `data-footer` regions, and mjml layout source is system-only.\n\n`preview` renders a draft (or the stored template) with sample values taken from the `example` of each declared variable, through the same gates and the same mail renderer as a real send, and returns subject, html, `displayHtml` (the html with cid images as data URIs for an editor), text, attachments (inline images and files) and `checks` (images resolved, plain text present, size under 10 MiB, required placeholders used); nothing is saved or sent. `send-test` mails that rendering to the calling user's own address only, subject prefixed with `[TEST]`, at most 10 per user and hour; it needs `delivery`, `channel-email` and `renderer-mail-html` to be mounted. `send-test-status` tells an editor beforehand whether that send can work for the caller (`canSend`, else `reason` `no_channel` or `no_address`), never returning the address.\n\nImages used by a template (`<img data-resource=\"logo\">`) are uploaded with `resourceUpload`, which re-encodes them, stores a file and links it under the key; `resourceRemove` unlinks it. `linkedResources` is written only through these two handlers. With `purpose: \"attachment\"` and a `fileName`, `resourceUpload` stores a PDF, PNG or JPEG as a file attachment of a mail template (original bytes, at most 10) that the mail renderer attaches after the inline images. `resourceLinkPdf` instead links a `document-pdf` template under a key: every mail sent from the template then carries that PDF, rendered at send time with the mail's own variables and locale, under a file name pattern such as `invoice-{{number}}.pdf`; files and linked PDFs share the limit of 10.\n\nReplaces the former `text-content` feature; its blocks now live here as kind `text-block`.",
2606
2606
  "toggleableDefault": null,
2607
2607
  "requires": [],
2608
2608
  "optionalRequires": [],
@@ -28,7 +28,7 @@ const FRAMEWORK_ROOT = resolve(ROOT, "..", "..", "..");
28
28
  const EXPECTED_HELD_BACK = new Set([
29
29
  "foundation-shared", // utilities (requireDefined/requireNonEmpty), kein feature
30
30
  "page-render", // shared HTML render/cache helpers (legal/managed-pages), kein defineFeature
31
- "presets", // deprecated feature-bundle helpers (dsgvoSelfServiceFeatures, securityBaselineFeatures), kein defineFeature
31
+ "presets", // deprecated feature-bundle helpers (securityBaselineFeatures), kein defineFeature
32
32
  ]);
33
33
 
34
34
  // Sub-paths in bundled-features's package.json exports (./tenant/seeding,
@@ -34,7 +34,9 @@ export const devAppOptions = {
34
34
  htmlPath: "./public/index.html",
35
35
  watchDirs: ["./src", "../../../packages/*/src"],
36
36
  anonymousAccess: { defaultTenantId: SYSTEM_TENANT_ID },
37
- extraContext: ({ db }) => ({ templateResolver: createTemplateResolverApi(db) }),
37
+ extraContext: ({ db, registry }) => ({
38
+ templateResolver: createTemplateResolverApi(db, registry),
39
+ }),
38
40
  seeds: [seedScreenshotData],
39
41
  auth: {
40
42
  admin: {
@@ -18,8 +18,7 @@ the option.
18
18
  `securityBaselineFeatures()`.
19
19
  - **`composeGdprStack({ tenantLifecycle, selfService })`**: `data-retention`
20
20
  and `compliance-profiles`, optionally `tenant-lifecycle`. `selfService` adds
21
- `sessions`, `user-data-rights` and `user-profile` (needs `mail`) and
22
- replaces `dsgvoSelfServiceFeatures()`.
21
+ `sessions`, `user-data-rights` and `user-profile` (needs `mail`).
23
22
  - **`composeSaasStack({ billing, tiers, caps, waitlist })`**:
24
23
  `billing-foundation` and `tier-engine`, plus `cap-counter`/`cap-overview`
25
24
  and `waitlist` when configured. `billing: "stripe"` leaves
@@ -21,11 +21,11 @@ object and an embedded list rendered by a declarative `entityEdit` screen.
21
21
 
22
22
  - **Derived head totals** (`src/screens/invoice-receive.screen.ts`): an
23
23
  `actionForm` declares `derive` so net, VAT, and gross follow the lines
24
- (`listSum`), or follow a typed net and VAT rate while the list is empty
25
- (`whenEmpty`, `percent`). The renderer shows them read-only and live; the
26
- handler wraps its schema with `withFormDerivations(schema, screen)`
27
- (`src/handlers/invoice-receive.write.ts`), so totals sent by the client are
28
- replaced on the server.
24
+ (`listSum`); the list requires at least one line (`minItems: 1`), so there
25
+ is no lineless input to cover. The head totals are not laid out; the lines footer shows Net, VAT, and Gross through labelled `totals`
26
+ entries (`{ label, from }`). The handler wraps its schema with
27
+ `withFormDerivations(schema, screen)` (`src/handlers/invoice-receive.write.ts`),
28
+ so totals sent by the client are replaced on the server.
29
29
 
30
30
  ## When to reach for it
31
31
 
@@ -16,7 +16,6 @@ const receiveInput = withFormDerivations(
16
16
  invoiceNumber: z.string().min(1),
17
17
  lines: fieldToZod(invoiceReceiveLines, ["EUR"]),
18
18
  netTotal: moneyPayload,
19
- vatRate: z.number().optional(),
20
19
  vatTotal: moneyPayload,
21
20
  grossTotal: moneyPayload,
22
21
  }),
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  type ActionFormScreenDefinition,
3
- createDecimalField,
4
3
  createEmbeddedListField,
5
4
  createTextField,
6
5
  } from "@cosmicdrift/kumiko-framework/engine";
@@ -15,13 +14,18 @@ export const invoiceReceiveLines = createEmbeddedListField(
15
14
  vatAmount: { type: "money" },
16
15
  },
17
16
  {
17
+ minItems: 1,
18
18
  derived: { vatAmount: { op: "percent", from: ["amount", "vatRate"] } },
19
- totals: ["amount", "vatAmount"],
19
+ totals: [
20
+ { label: "invoice.total.net", from: ["amount"] },
21
+ { label: "invoice.total.vat", from: ["vatAmount"] },
22
+ { label: "invoice.total.gross", from: ["amount", "vatAmount"] },
23
+ ],
20
24
  },
21
25
  );
22
26
 
23
- // Net/VAT/gross are derived: with line items they are the sum of the lines,
24
- // without lines the net amount and the VAT rate are typed and VAT/gross follow.
27
+ // Net/VAT/gross are derived on the server from the lines (at least one is required)
28
+ // and shown only in the list footer, so the head totals are declared but not laid out.
25
29
  export const invoiceReceiveScreen: ActionFormScreenDefinition = {
26
30
  id: "invoice-receive",
27
31
  type: "actionForm",
@@ -35,25 +39,18 @@ export const invoiceReceiveScreen: ActionFormScreenDefinition = {
35
39
  }),
36
40
  lines: invoiceReceiveLines,
37
41
  netTotal: { type: "money", currency: euro },
38
- vatRate: createDecimalField({ precision: 5, scale: 2 }),
39
42
  vatTotal: { type: "money", currency: euro },
40
43
  grossTotal: { type: "money", currency: euro },
41
44
  },
42
45
  derive: {
43
46
  netTotal: { op: "listSum", list: "lines", cell: "amount" },
44
- vatTotal: {
45
- op: "listSum",
46
- list: "lines",
47
- cell: "vatAmount",
48
- whenEmpty: { op: "percent", from: ["netTotal", "vatRate"] },
49
- },
47
+ vatTotal: { op: "listSum", list: "lines", cell: "vatAmount" },
50
48
  grossTotal: { op: "sum", from: ["netTotal", "vatTotal"] },
51
49
  },
52
50
  layout: {
53
51
  sections: [
54
52
  { columns: 2, fields: ["supplier", "invoiceNumber"] },
55
53
  { columns: 1, fields: ["lines"] },
56
- { columns: 2, fields: ["netTotal", "vatRate", "vatTotal", "grossTotal"] },
57
54
  ],
58
55
  },
59
56
  };