@cosmicdrift/kumiko-samples 0.389.0 → 0.390.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 (296) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +25 -13
  3. package/packages/bundled-features/src/audit/feature.ts +1 -1
  4. package/packages/bundled-features/src/auth-email-password/auth-user-row.ts +1 -1
  5. package/packages/bundled-features/src/auth-email-password/bootstrap.ts +13 -1
  6. package/packages/bundled-features/src/auth-email-password/changes.json +6 -0
  7. package/packages/bundled-features/src/auth-email-password/feature.ts +2 -2
  8. package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +7 -7
  9. package/packages/bundled-features/src/auth-email-password/handlers/confirm-account-unlock.write.ts +3 -3
  10. package/packages/bundled-features/src/auth-email-password/handlers/confirm-token-flow.ts +5 -5
  11. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +9 -7
  12. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +11 -7
  13. package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +4 -4
  14. package/packages/bundled-features/src/auth-email-password/handlers/invite-info.query.ts +2 -2
  15. package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts +9 -7
  16. package/packages/bundled-features/src/auth-email-password/handlers/login.write.ts +23 -16
  17. package/packages/bundled-features/src/auth-email-password/handlers/reset-password.write.ts +3 -2
  18. package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +12 -12
  19. package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +12 -8
  20. package/packages/bundled-features/src/auth-email-password/handlers/switch-tenant-mfa-gate.write.ts +4 -2
  21. package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +2 -2
  22. package/packages/bundled-features/src/auth-email-password/handlers/verify-email.write.ts +3 -2
  23. package/packages/bundled-features/src/auth-email-password/seeding.ts +14 -9
  24. package/packages/bundled-features/src/auth-foundation/feature.ts +13 -4
  25. package/packages/bundled-features/src/auth-mfa/handlers/disable.write.ts +5 -5
  26. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +24 -14
  27. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm.write.ts +9 -3
  28. package/packages/bundled-features/src/auth-mfa/handlers/enable-start-preauth.write.ts +6 -1
  29. package/packages/bundled-features/src/auth-mfa/handlers/enable-start.write.ts +3 -1
  30. package/packages/bundled-features/src/auth-mfa/handlers/reencrypt.job.ts +14 -9
  31. package/packages/bundled-features/src/auth-mfa/handlers/regenerate-recovery.write.ts +5 -4
  32. package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +25 -14
  33. package/packages/bundled-features/src/auth-mfa/mfa-code-verifier.ts +10 -4
  34. package/packages/bundled-features/src/auth-mfa/mfa-status-checker.ts +9 -3
  35. package/packages/bundled-features/src/auth-mfa/verify-factor.ts +1 -1
  36. package/packages/bundled-features/src/billing-foundation/billing-info-query.ts +2 -2
  37. package/packages/bundled-features/src/billing-foundation/changes.json +6 -0
  38. package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +1 -1
  39. package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +5 -5
  40. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +10 -7
  41. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts +15 -12
  42. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-open-cancels.ts +22 -0
  43. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-provider-lock.ts +4 -0
  44. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-receipt-limit.ts +4 -4
  45. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +17 -58
  46. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +10 -8
  47. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-revoke.ts +18 -15
  48. package/packages/bundled-features/src/billing-foundation/events.ts +1 -1
  49. package/packages/bundled-features/src/billing-foundation/feature.ts +8 -3
  50. package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +1 -1
  51. package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +5 -5
  52. package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +3 -3
  53. package/packages/bundled-features/src/billing-foundation/plan-catalog.ts +2 -2
  54. package/packages/bundled-features/src/billing-foundation/projection.ts +1 -1
  55. package/packages/bundled-features/src/billing-foundation/types.ts +1 -1
  56. package/packages/bundled-features/src/cap-counter/aggregate-id.ts +1 -1
  57. package/packages/bundled-features/src/cap-counter/book-cap-usage.ts +5 -4
  58. package/packages/bundled-features/src/cap-counter/cap-reservation.ts +1 -1
  59. package/packages/bundled-features/src/cap-counter/constants.ts +1 -1
  60. package/packages/bundled-features/src/cap-counter/feature.ts +2 -2
  61. package/packages/bundled-features/src/cap-counter/handlers/increment-rolling.write.ts +1 -1
  62. package/packages/bundled-features/src/cap-counter/reservation-chain.ts +1 -1
  63. package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +4 -4
  64. package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +1 -1
  65. package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +1 -1
  66. package/packages/bundled-features/src/compliance-profiles/seeding.ts +12 -7
  67. package/packages/bundled-features/src/config/feature.ts +6 -6
  68. package/packages/bundled-features/src/config/handlers/cascade.query.ts +1 -1
  69. package/packages/bundled-features/src/config/handlers/readiness.query.ts +1 -1
  70. package/packages/bundled-features/src/config/handlers/reencrypt.job.ts +23 -13
  71. package/packages/bundled-features/src/config/handlers/reset.write.ts +3 -3
  72. package/packages/bundled-features/src/config/handlers/selected-extensions.query.ts +1 -1
  73. package/packages/bundled-features/src/config/handlers/set.write.ts +4 -4
  74. package/packages/bundled-features/src/config/handlers/values.query.ts +1 -1
  75. package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +8 -8
  76. package/packages/bundled-features/src/crypto-shredding/resolve-file-bytes-deleter.ts +3 -3
  77. package/packages/bundled-features/src/custom-fields/changes.json +6 -0
  78. package/packages/bundled-features/src/custom-fields/handlers/clear-custom-field.write.ts +1 -1
  79. package/packages/bundled-features/src/custom-fields/handlers/delete-system-field.write.ts +1 -1
  80. package/packages/bundled-features/src/custom-fields/handlers/delete-tenant-field.write.ts +1 -1
  81. package/packages/bundled-features/src/custom-fields/handlers/set-custom-field.write.ts +1 -1
  82. package/packages/bundled-features/src/custom-fields/index.ts +4 -0
  83. package/packages/bundled-features/src/data-retention/feature.ts +1 -1
  84. package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +12 -2
  85. package/packages/bundled-features/src/delivery/attempt-log.ts +1 -1
  86. package/packages/bundled-features/src/delivery/delivery-service.ts +6 -5
  87. package/packages/bundled-features/src/delivery/feature.ts +2 -2
  88. package/packages/bundled-features/src/delivery/jobs.ts +11 -11
  89. package/packages/bundled-features/src/delivery/testing.ts +1 -1
  90. package/packages/bundled-features/src/document-ingest-foundation/events.ts +1 -1
  91. package/packages/bundled-features/src/feature-toggles/handlers/registered.query.ts +1 -1
  92. package/packages/bundled-features/src/feature-toggles/handlers/set.write.ts +1 -1
  93. package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +1 -1
  94. package/packages/bundled-features/src/file-derivatives/feature.ts +2 -2
  95. package/packages/bundled-features/src/file-derivatives/handlers/public-variant-by-file-ref.query.ts +5 -1
  96. package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +7 -5
  97. package/packages/bundled-features/src/file-foundation/feature.ts +1 -1
  98. package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +7 -5
  99. package/packages/bundled-features/src/file-provider-s3/feature.ts +4 -1
  100. package/packages/bundled-features/src/files-tenant-data/handlers/sweep-orphaned-derivatives.job.ts +8 -8
  101. package/packages/bundled-features/src/form-draft/feature.ts +1 -1
  102. package/packages/bundled-features/src/form-draft/handlers/cleanup.job.ts +22 -12
  103. package/packages/bundled-features/src/form-draft/handlers/discard.write.ts +3 -3
  104. package/packages/bundled-features/src/form-draft/handlers/get.query.ts +1 -1
  105. package/packages/bundled-features/src/foundation-shared/config-helpers.ts +1 -1
  106. package/packages/bundled-features/src/inbound-mail-foundation/events.ts +1 -1
  107. package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +1 -1
  108. package/packages/bundled-features/src/inbound-mail-foundation/handlers/account-state.ts +2 -2
  109. package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +1 -1
  110. package/packages/bundled-features/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +1 -1
  111. package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +10 -10
  112. package/packages/bundled-features/src/inbound-mail-foundation/handlers/update-account.write.ts +1 -1
  113. package/packages/bundled-features/src/inbound-mail-foundation/projection.ts +1 -1
  114. package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +1 -1
  115. package/packages/bundled-features/src/inbound-provider-imap/feature.ts +4 -1
  116. package/packages/bundled-features/src/jobs/changes.json +6 -0
  117. package/packages/bundled-features/src/jobs/events.ts +1 -1
  118. package/packages/bundled-features/src/jobs/handlers/consumer.write.ts +7 -4
  119. package/packages/bundled-features/src/jobs/handlers/projection-rebuild.job.ts +1 -1
  120. package/packages/bundled-features/src/jobs/handlers/reindex-entity.job.ts +11 -5
  121. package/packages/bundled-features/src/jobs/handlers/retention-cleanup.job.ts +1 -1
  122. package/packages/bundled-features/src/jobs/handlers/retry.write.ts +1 -1
  123. package/packages/bundled-features/src/jobs/handlers/stale-run-sweep.job.ts +1 -1
  124. package/packages/bundled-features/src/jobs/handlers/trigger.write.ts +2 -2
  125. package/packages/bundled-features/src/jobs/job-run-logger.ts +139 -90
  126. package/packages/bundled-features/src/legal-pages/README.md +1 -1
  127. package/packages/bundled-features/src/legal-pages/lib/boot-check.ts +12 -8
  128. package/packages/bundled-features/src/mail-foundation/feature.ts +2 -2
  129. package/packages/bundled-features/src/mail-transport-smtp/changes.json +6 -0
  130. package/packages/bundled-features/src/mail-transport-smtp/feature.ts +4 -1
  131. package/packages/bundled-features/src/managed-pages/seeding.ts +13 -8
  132. package/packages/bundled-features/src/notes-history/handlers/add-note.write.ts +1 -1
  133. package/packages/bundled-features/src/personal-access-tokens/feature.ts +1 -1
  134. package/packages/bundled-features/src/personal-access-tokens/handlers/create.write.ts +2 -2
  135. package/packages/bundled-features/src/personal-access-tokens/handlers/revoke.write.ts +1 -1
  136. package/packages/bundled-features/src/rate-limiting/handlers/status.query.ts +2 -2
  137. package/packages/bundled-features/src/readiness/handlers/status.query.ts +4 -1
  138. package/packages/bundled-features/src/renderer-foundation/api.ts +3 -3
  139. package/packages/bundled-features/src/secrets/announce-secret-change.ts +4 -4
  140. package/packages/bundled-features/src/secrets/feature.ts +9 -9
  141. package/packages/bundled-features/src/secrets/handlers/delete.write.ts +4 -3
  142. package/packages/bundled-features/src/secrets/handlers/rotate.job.ts +13 -8
  143. package/packages/bundled-features/src/secrets/handlers/set.write.ts +4 -3
  144. package/packages/bundled-features/src/secrets/secrets-context.ts +16 -6
  145. package/packages/bundled-features/src/secrets/write-gate.ts +1 -1
  146. package/packages/bundled-features/src/seo/feature.ts +1 -1
  147. package/packages/bundled-features/src/sessions/changes.json +12 -0
  148. package/packages/bundled-features/src/sessions/feature.ts +1 -1
  149. package/packages/bundled-features/src/sessions/handlers/cleanup.job.ts +2 -2
  150. package/packages/bundled-features/src/sessions/handlers/detail.query.ts +5 -2
  151. package/packages/bundled-features/src/sessions/handlers/list.query.ts +5 -2
  152. package/packages/bundled-features/src/sessions/handlers/mine.query.ts +5 -2
  153. package/packages/bundled-features/src/sessions/handlers/revoke-all-for-user.write.ts +3 -3
  154. package/packages/bundled-features/src/sessions/handlers/revoke-all-others.write.ts +1 -1
  155. package/packages/bundled-features/src/sessions/handlers/revoke.write.ts +1 -1
  156. package/packages/bundled-features/src/sessions/screens.ts +8 -4
  157. package/packages/bundled-features/src/sessions/session-revoked-event.ts +1 -1
  158. package/packages/bundled-features/src/sessions/web/session-device-cell.tsx +10 -12
  159. package/packages/bundled-features/src/sessions/web/user-agent.ts +12 -0
  160. package/packages/bundled-features/src/shared/index.ts +15 -0
  161. package/packages/bundled-features/src/shared/purge-files-reaching-subject.ts +12 -2
  162. package/packages/bundled-features/src/shared/require-for-tenant.ts +7 -5
  163. package/packages/bundled-features/src/shared/stream-tenant-backfill.ts +201 -0
  164. package/packages/bundled-features/src/shared/stream-tenant-derived-state.ts +107 -0
  165. package/packages/bundled-features/src/step-dispatcher/feature.ts +1 -1
  166. package/packages/bundled-features/src/subscription-dodo/plugin-methods.ts +1 -1
  167. package/packages/bundled-features/src/subscription-dodo/runtime.ts +7 -7
  168. package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +5 -3
  169. package/packages/bundled-features/src/subscription-stripe/runtime.ts +7 -7
  170. package/packages/bundled-features/src/template-resolver/api.ts +3 -3
  171. package/packages/bundled-features/src/template-resolver/changes.json +18 -0
  172. package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +5 -4
  173. package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +2 -2
  174. package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +1 -1
  175. package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +2 -2
  176. package/packages/bundled-features/src/template-resolver/handlers/send-test-shared.ts +1 -1
  177. package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +2 -2
  178. package/packages/bundled-features/src/template-resolver/handlers/tenant-override-selection.ts +6 -2
  179. package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +4 -2
  180. package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +1 -1
  181. package/packages/bundled-features/src/template-resolver/seeding.ts +12 -2
  182. package/packages/bundled-features/src/template-resolver/store-template-resource.ts +18 -13
  183. package/packages/bundled-features/src/template-resolver/template-resources.ts +4 -4
  184. package/packages/bundled-features/src/template-resolver/testing.ts +2 -2
  185. package/packages/bundled-features/src/template-resolver/web/document-insert-fields.tsx +222 -0
  186. package/packages/bundled-features/src/template-resolver/web/document-insert-model.ts +164 -0
  187. package/packages/bundled-features/src/template-resolver/web/document-insert-panel.tsx +115 -0
  188. package/packages/bundled-features/src/template-resolver/web/document-tiptap-bubble.tsx +177 -0
  189. package/packages/bundled-features/src/template-resolver/web/document-tiptap-content.tsx +38 -0
  190. package/packages/bundled-features/src/template-resolver/web/document-tiptap-context.ts +119 -0
  191. package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +51 -1098
  192. package/packages/bundled-features/src/template-resolver/web/document-tiptap-helpers.ts +161 -0
  193. package/packages/bundled-features/src/template-resolver/web/document-tiptap-layout.tsx +83 -0
  194. package/packages/bundled-features/src/template-resolver/web/document-tiptap-toolbar-groups.tsx +153 -0
  195. package/packages/bundled-features/src/template-resolver/web/document-tiptap-toolbar.tsx +182 -0
  196. package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +18 -0
  197. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +19 -6
  198. package/packages/bundled-features/src/template-resolver/web/use-document-tiptap-model.ts +138 -0
  199. package/packages/bundled-features/src/tenant/changes.json +19 -0
  200. package/packages/bundled-features/src/tenant/handlers/cancel-invitation.write.ts +1 -1
  201. package/packages/bundled-features/src/tenant/handlers/memberships.query.ts +1 -1
  202. package/packages/bundled-features/src/tenant/handlers/remove-member.write.ts +2 -2
  203. package/packages/bundled-features/src/tenant/handlers/update-member-roles.write.ts +1 -1
  204. package/packages/bundled-features/src/tenant/index.ts +5 -0
  205. package/packages/bundled-features/src/tenant/invited-membership.ts +6 -1
  206. package/packages/bundled-features/src/tenant/schema/tenant.ts +4 -1
  207. package/packages/bundled-features/src/tenant/seeding.ts +65 -62
  208. package/packages/bundled-features/src/tenant/stream-tenant-backfill.ts +115 -0
  209. package/packages/bundled-features/src/tenant/testing.ts +77 -0
  210. package/packages/bundled-features/src/tenant-handover/events.ts +1 -1
  211. package/packages/bundled-features/src/tenant-handover/handlers/claim.write.ts +1 -1
  212. package/packages/bundled-features/src/tenant-lifecycle/feature.ts +5 -5
  213. package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +22 -10
  214. package/packages/bundled-features/src/tenant-lifecycle/handlers/request-destruction.write.ts +22 -11
  215. package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +23 -12
  216. package/packages/bundled-features/src/tenant-lifecycle/stages.ts +7 -3
  217. package/packages/bundled-features/src/tenant-lifecycle/testing.ts +9 -8
  218. package/packages/bundled-features/src/tier-engine/entity.ts +1 -1
  219. package/packages/bundled-features/src/tier-engine/feature.ts +8 -3
  220. package/packages/bundled-features/src/tier-engine/handlers/active-tier.query.ts +3 -3
  221. package/packages/bundled-features/src/tier-engine/handlers/get-tenant-tier.query.ts +7 -1
  222. package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +3 -3
  223. package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +71 -97
  224. package/packages/bundled-features/src/user/handlers/find-for-auth.query.ts +2 -2
  225. package/packages/bundled-features/src/user/handlers/update-roles.ts +2 -2
  226. package/packages/bundled-features/src/user/index.ts +2 -0
  227. package/packages/bundled-features/src/user/invited-global-roles.ts +6 -1
  228. package/packages/bundled-features/src/user/principal-status.ts +1 -1
  229. package/packages/bundled-features/src/user/seeding.ts +24 -6
  230. package/packages/bundled-features/src/user/testing.ts +22 -0
  231. package/packages/bundled-features/src/user-data-rights/COMPLIANCE.md +1 -1
  232. package/packages/bundled-features/src/user-data-rights/README.md +1 -1
  233. package/packages/bundled-features/src/user-data-rights/audit-download.ts +12 -2
  234. package/packages/bundled-features/src/user-data-rights/changes.json +24 -0
  235. package/packages/bundled-features/src/user-data-rights/feature.ts +29 -24
  236. package/packages/bundled-features/src/user-data-rights/handlers/confirm-deletion-by-token.write.ts +1 -1
  237. package/packages/bundled-features/src/user-data-rights/handlers/deletion-grace-period.ts +1 -1
  238. package/packages/bundled-features/src/user-data-rights/handlers/download-by-job.query.ts +2 -2
  239. package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +2 -2
  240. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +1 -1
  241. package/packages/bundled-features/src/user-data-rights/handlers/restrict-account.write.ts +4 -4
  242. package/packages/bundled-features/src/user-data-rights/handlers/run-forget-cleanup.write.ts +12 -6
  243. package/packages/bundled-features/src/user-data-rights/index.ts +2 -0
  244. package/packages/bundled-features/src/user-data-rights/lib/mail-transport-resolver.ts +1 -1
  245. package/packages/bundled-features/src/user-data-rights/lib/storage-provider-resolver.ts +1 -1
  246. package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +6 -1
  247. package/packages/bundled-features/src/user-data-rights/run-export-jobs.ts +8 -2
  248. package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +14 -3
  249. package/packages/bundled-features/src/user-data-rights/run-user-export.ts +11 -1
  250. package/packages/bundled-features/src/user-data-rights/web/constants.ts +10 -0
  251. package/packages/bundled-features/src/user-data-rights/web/index.ts +6 -1
  252. package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +8 -6
  253. package/packages/bundled-features/src/waitlist/handlers/export-purge.write.ts +1 -1
  254. package/packages/bundled-features/src/waitlist/handlers/invite.write.ts +2 -2
  255. package/packages/bundled-features/src/waitlist/handlers/reject.write.ts +1 -1
  256. package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +9 -4
  257. package/packages/bundled-features/src/waitlist/legacy-migrate.ts +6 -1
  258. package/packages/bundled-features/src/waitlist/link-on-signup.ts +7 -1
  259. package/packages/bundled-features/src/workflow-runner/changes.json +6 -0
  260. package/packages/bundled-features/src/workflow-runner/db/queries/due-runs.ts +1 -1
  261. package/packages/bundled-features/src/workflow-runner/event-subscriber.ts +4 -3
  262. package/packages/bundled-features/src/workflow-runner/event-trigger.ts +2 -1
  263. package/packages/bundled-features/src/workflow-runner/feature.ts +2 -2
  264. package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +8 -8
  265. package/packages/bundled-features/src/workflow-runner/runner.ts +6 -4
  266. package/samples/apps/cap-billing-demo/src/feature.ts +19 -5
  267. package/samples/apps/config-demo/src/app/server.ts +1 -1
  268. package/samples/apps/styleguide/src/app/server.ts +2 -2
  269. package/samples/apps/use-all-bundled/feature-manifest.json +1 -1
  270. package/samples/apps/use-all-bundled/src/app/seed.ts +12 -2
  271. package/samples/apps/user-data-rights-demo/README.md +1 -1
  272. package/samples/apps/user-data-rights-demo/src/feature.ts +3 -2
  273. package/samples/recipes/api-worker-split/README.md +2 -1
  274. package/samples/recipes/api-worker-split/src/feature.ts +2 -1
  275. package/samples/recipes/cross-feature-events/README.md +5 -5
  276. package/samples/recipes/cross-feature-events/package.json +1 -1
  277. package/samples/recipes/cross-feature-events/src/feature.ts +4 -4
  278. package/samples/recipes/event-sourcing/README.md +5 -5
  279. package/samples/recipes/event-sourcing/package.json +1 -1
  280. package/samples/recipes/event-sourcing/src/feature.ts +15 -15
  281. package/samples/recipes/files-post-processing/README.md +1 -1
  282. package/samples/recipes/files-post-processing/package.json +1 -1
  283. package/samples/recipes/files-post-processing/src/feature.ts +6 -4
  284. package/samples/recipes/lane-routing/package.json +1 -1
  285. package/samples/recipes/legal-pages/README.md +2 -2
  286. package/samples/recipes/lifecycle-hooks/README.md +2 -2
  287. package/samples/recipes/lifecycle-hooks/src/feature.ts +1 -1
  288. package/samples/recipes/managed-config/README.md +3 -3
  289. package/samples/recipes/search/README.md +1 -1
  290. package/samples/recipes/search/src/feature.ts +3 -3
  291. package/samples/recipes/secrets-demo/README.md +1 -1
  292. package/samples/recipes/secrets-demo/src/feature.ts +5 -2
  293. package/samples/recipes/state-machine/src/events.ts +1 -1
  294. package/samples/recipes/state-machine/src/reducer.ts +1 -1
  295. package/samples/recipes/workflow-engine/src/resume-loop.ts +4 -4
  296. package/samples/recipes/workflow-engine/src/workflow-runner.ts +2 -2
@@ -1,5 +1,6 @@
1
1
  import { fetchOne, selectMany } from "@cosmicdrift/kumiko-framework/db";
2
2
  import type {
3
+ BeforeTransactionContext,
3
4
  BeforeTransactionResult,
4
5
  ConfigAccessor,
5
6
  HandlerContext,
@@ -100,7 +101,7 @@ function isAttachmentKey(links: Readonly<Record<string, LinkedResourceEntry>>, k
100
101
  // so both receive the target tenant's db and identity.
101
102
  function uploadToTenant(
102
103
  args: {
103
- readonly ctx: HandlerContext;
104
+ readonly ctx: PrepareContext;
104
105
  readonly user: SessionUser;
105
106
  readonly tenantId: TenantId;
106
107
  },
@@ -110,7 +111,7 @@ function uploadToTenant(
110
111
  return uploadFileBytesBeforeTransaction(
111
112
  {
112
113
  db: requireForTenant(ctx, tenantId).db,
113
- files: fileContextForTenant(ctx, ctx.user.tenantId, tenantId),
114
+ services: { files: fileContextForTenant(ctx, ctx.user.tenantId, tenantId) },
114
115
  registry: ctx.registry,
115
116
  user: { ...user, tenantId },
116
117
  },
@@ -119,7 +120,7 @@ function uploadToTenant(
119
120
  }
120
121
 
121
122
  type TenantScope = {
122
- readonly ctx: HandlerContext;
123
+ readonly ctx: PrepareContext;
123
124
  readonly user: SessionUser;
124
125
  readonly tenantId: TenantId;
125
126
  };
@@ -133,7 +134,7 @@ function createFileRefFromPrepared(scope: TenantScope, upload: UploadedFileBytes
133
134
  }
134
135
 
135
136
  async function linkImage(args: {
136
- readonly ctx: HandlerContext;
137
+ readonly ctx: PrepareContext;
137
138
  readonly user: SessionUser;
138
139
  readonly tenantId: TenantId;
139
140
  readonly row: TemplateResourceRow;
@@ -197,7 +198,7 @@ type ImageTarget = {
197
198
 
198
199
  // Runs before the upload (reject early, store nothing) and again in the transaction on the fresh row.
199
200
  async function checkImageTarget(args: {
200
- readonly ctx: HandlerContext;
201
+ readonly ctx: PrepareContext;
201
202
  readonly tenantId: TenantId;
202
203
  readonly templateId?: string | undefined;
203
204
  readonly key?: string | undefined;
@@ -220,7 +221,7 @@ async function checkImageTarget(args: {
220
221
  // beforeTransaction half: normalizes the image and uploads it to the target tenant's storage.
221
222
  // The caller is responsible for authorization; the function trusts tenantId.
222
223
  export async function prepareTemplateResourceBeforeTransaction(args: {
223
- readonly ctx: HandlerContext;
224
+ readonly ctx: PrepareContext;
224
225
  readonly user: SessionUser;
225
226
  readonly tenantId: TenantId;
226
227
  readonly bytes: Uint8Array;
@@ -260,6 +261,10 @@ export async function prepareTemplateResourceBeforeTransaction(args: {
260
261
  };
261
262
  }
262
263
 
264
+ // The prepare phase runs before the transaction, so it must not rely on anything the in-transaction
265
+ // context adds (events, dispatch.write).
266
+ export type PrepareContext = HandlerContext | BeforeTransactionContext;
267
+
263
268
  export type Release = () => Promise<void>;
264
269
 
265
270
  // Tries every release, so one failing delete does not strand the other uploads; the first rejection is rethrown.
@@ -271,13 +276,13 @@ export async function releaseAll(releases: readonly Release[]): Promise<void> {
271
276
 
272
277
  // Failure paths: a failing release must not replace the upload error the caller needs to see.
273
278
  export async function releaseAfterFailure(
274
- ctx: HandlerContext,
279
+ ctx: PrepareContext,
275
280
  releases: readonly Release[],
276
281
  ): Promise<void> {
277
282
  try {
278
283
  await releaseAll(releases);
279
284
  } catch (releaseError) {
280
- ctx.log?.warn("template upload rollback: releasing stored files failed", {
285
+ ctx.obs.log?.warn("template upload rollback: releasing stored files failed", {
281
286
  error: releaseError instanceof Error ? releaseError.message : String(releaseError),
282
287
  });
283
288
  }
@@ -308,7 +313,7 @@ function sourceUnreadable(): WriteResult<never> {
308
313
  // Undefined: the fileRef row exists but its bytes cannot be read, which must not look like a
309
314
  // deleted file. The provider is chosen by the tenant of the fileRef, not by the caller's.
310
315
  async function readCopySource(
311
- ctx: HandlerContext,
316
+ ctx: PrepareContext,
312
317
  fileRefId: string,
313
318
  ): Promise<{ contentType: string; bytes: Uint8Array } | null | undefined> {
314
319
  try {
@@ -320,7 +325,7 @@ async function readCopySource(
320
325
  });
321
326
  return found === null ? null : { contentType: found.contentType, bytes: found.content };
322
327
  } catch (cause) {
323
- ctx.log?.warn("template copy: source file could not be read", {
328
+ ctx.obs.log?.warn("template copy: source file could not be read", {
324
329
  fileRefId,
325
330
  errorClass: cause instanceof Error ? cause.name : typeof cause,
326
331
  });
@@ -333,7 +338,7 @@ async function readCopySource(
333
338
  // attachments. An entry whose source file is gone is left out: it could not be resolved at render
334
339
  // time either. A failed or throwing upload releases the ones before it.
335
340
  export async function prepareLinkedResourceCopiesBeforeTransaction(args: {
336
- readonly ctx: HandlerContext;
341
+ readonly ctx: PrepareContext;
337
342
  readonly user: SessionUser;
338
343
  readonly tenantId: TenantId;
339
344
  readonly resources: Readonly<Record<string, LinkedResourceEntry>>;
@@ -525,7 +530,7 @@ type AttachmentTarget = {
525
530
 
526
531
  // Runs before the upload (reject early, store nothing) and again in the transaction on the fresh row.
527
532
  async function checkAttachmentTarget(args: {
528
- readonly ctx: HandlerContext;
533
+ readonly ctx: PrepareContext;
529
534
  readonly tenantId: TenantId;
530
535
  readonly templateId: string;
531
536
  readonly key: string;
@@ -550,7 +555,7 @@ async function checkAttachmentTarget(args: {
550
555
  // beforeTransaction half: validates a PDF/PNG/JPEG and uploads it unchanged to the target tenant's
551
556
  // storage. With templateId the template is checked first; a bundle import has none yet.
552
557
  export async function prepareTemplateAttachmentBeforeTransaction(args: {
553
- readonly ctx: HandlerContext;
558
+ readonly ctx: PrepareContext;
554
559
  readonly user: SessionUser;
555
560
  readonly tenantId: TenantId;
556
561
  readonly bytes: Uint8Array;
@@ -27,16 +27,16 @@ type FileRefRow = {
27
27
  // Reads the file behind a template resource. Null when the fileRef is unknown
28
28
  // to the caller's tenant or has no bytes.
29
29
  export async function readTemplateResource(
30
- ctx: Pick<HandlerContext, "db" | "files" | "user">,
30
+ ctx: Pick<HandlerContext, "db" | "services" | "user">,
31
31
  fileRefId: string,
32
32
  ): Promise<TemplateResourceFile | null> {
33
- if (!ctx.files) return null;
33
+ if (!ctx.services.files) return null;
34
34
  const row = await fetchOne<FileRefRow>(ctx.db, fileRefsTable, {
35
35
  id: fileRefId,
36
36
  isDeleted: false,
37
37
  });
38
38
  if (!row) return null;
39
- const handle = ctx.files.refFromFileRef(row);
39
+ const handle = ctx.services.files.refFromFileRef(row);
40
40
  if (!(await handle.exists())) return null;
41
41
  return { fileName: row.fileName, contentType: row.mimeType, bytes: await handle.read() };
42
42
  }
@@ -44,7 +44,7 @@ export async function readTemplateResource(
44
44
  // Replaces the whole key map of one row with files that are already stored (bundle import,
45
45
  // copy-locale). Single keys go through storePreparedTemplateResource and resource-remove.
46
46
  export async function attachTemplateResources(
47
- ctx: Pick<HandlerContext, "forTenant">,
47
+ ctx: Pick<HandlerContext, "tenants">,
48
48
  args: {
49
49
  readonly user: SessionUser;
50
50
  readonly tenantId: TenantId;
@@ -3,7 +3,7 @@
3
3
  // No bun:test here — callers register cases with their own test runner.
4
4
 
5
5
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
6
- import { seedRow } from "@cosmicdrift/kumiko-testing/helpers";
6
+ import { seedRowViaEventStore } from "@cosmicdrift/kumiko-testing/helpers";
7
7
  import type { ResolveRequest, TemplateResource } from "./api.ts";
8
8
  import { TemplateNotFoundError } from "./api.ts";
9
9
  import { templateResourcesTable } from "./table.ts";
@@ -50,7 +50,7 @@ type SeedTemplateArgs = {
50
50
  };
51
51
 
52
52
  async function seedTemplate(db: DbConnection, args: SeedTemplateArgs): Promise<void> {
53
- await seedRow(db, templateResourcesTable, {
53
+ await seedRowViaEventStore(db, templateResourcesTable, {
54
54
  tenantId: args.tenantId,
55
55
  slug: args.slug,
56
56
  kind: args.kind,
@@ -0,0 +1,222 @@
1
+ import { usePrimitives, VariableChips } from "@cosmicdrift/kumiko-renderer";
2
+ import { AlignCenter, AlignJustify, AlignLeft, AlignRight } from "lucide-react";
3
+ import type { ReactNode } from "react";
4
+ import { insertAndCloseOf, type PanelModel } from "./document-insert-model.ts";
5
+ import { DOCUMENT_TEXT_ALIGNMENTS, type DocumentTextAlignment } from "./document-schema.ts";
6
+ import { type PanelKind, variableLabelOf } from "./document-tiptap-helpers.ts";
7
+
8
+ const ALIGNMENT_ICONS: Record<DocumentTextAlignment, ReactNode> = {
9
+ left: <AlignLeft size={16} />,
10
+ center: <AlignCenter size={16} />,
11
+ right: <AlignRight size={16} />,
12
+ justify: <AlignJustify size={16} />,
13
+ };
14
+
15
+ const ALIGNMENT_LABEL_KEYS: Record<DocumentTextAlignment, string> = {
16
+ left: "template-resolver.documentEditor.alignLeft",
17
+ center: "template-resolver.documentEditor.alignCenter",
18
+ right: "template-resolver.documentEditor.alignRight",
19
+ justify: "template-resolver.documentEditor.alignJustify",
20
+ };
21
+
22
+ type OptionSelectProps = {
23
+ readonly id: string;
24
+ readonly label: string;
25
+ readonly names: readonly string[];
26
+ readonly labels: Readonly<Record<string, string>>;
27
+ readonly value: string;
28
+ readonly onChange: (name: string) => void;
29
+ };
30
+
31
+ function NameSelect({ id, label, names, labels, value, onChange }: OptionSelectProps): ReactNode {
32
+ const { Field, Input } = usePrimitives();
33
+ return (
34
+ <Field id={id} label={label}>
35
+ <Input
36
+ kind="select"
37
+ id={id}
38
+ name={id}
39
+ value={value}
40
+ onChange={onChange}
41
+ display="dropdown"
42
+ options={names.map((name) => ({ value: name, label: variableLabelOf(name, labels) }))}
43
+ />
44
+ </Field>
45
+ );
46
+ }
47
+
48
+ function renderLinkFields(m: PanelModel): ReactNode {
49
+ const { t, Field, Input } = m;
50
+ return (
51
+ <Field id={`${m.idPrefix}-href`} label={t("template-resolver.documentEditor.linkHref")}>
52
+ <Input
53
+ kind="text"
54
+ id={`${m.idPrefix}-href`}
55
+ name="href"
56
+ value={m.href}
57
+ onChange={m.setHref}
58
+ placeholder={t("template-resolver.documentEditor.linkHrefPlaceholder")}
59
+ />
60
+ </Field>
61
+ );
62
+ }
63
+
64
+ function renderPlaceholderFields(m: PanelModel): ReactNode {
65
+ return (
66
+ !m.noVariables && (
67
+ <div className="flex flex-wrap gap-1">
68
+ <VariableChips
69
+ variables={m.selectable}
70
+ labels={m.variableLabels}
71
+ onInsert={(name) => insertAndCloseOf(m)({ type: "variable", attrs: { name } })}
72
+ />
73
+ </div>
74
+ )
75
+ );
76
+ }
77
+
78
+ function renderAlignFields(m: PanelModel): ReactNode {
79
+ const { t, Button, editor } = m;
80
+ return (
81
+ <div className="flex flex-wrap gap-1">
82
+ {DOCUMENT_TEXT_ALIGNMENTS.map((alignment) => (
83
+ <Button
84
+ key={alignment}
85
+ type="button"
86
+ variant="secondary"
87
+ size="sm"
88
+ onClick={() => {
89
+ editor
90
+ .chain()
91
+ .focus()
92
+ .updateAttributes("paragraph", { textAlign: alignment })
93
+ .updateAttributes("heading", { textAlign: alignment })
94
+ .run();
95
+ m.onClose();
96
+ }}
97
+ >
98
+ {ALIGNMENT_ICONS[alignment]}
99
+ {t(ALIGNMENT_LABEL_KEYS[alignment])}
100
+ </Button>
101
+ ))}
102
+ </div>
103
+ );
104
+ }
105
+
106
+ function renderImageFields(m: PanelModel): ReactNode {
107
+ const { t, Field, Input, idPrefix } = m;
108
+ return (
109
+ <>
110
+ <Field id={`${idPrefix}-resource`} label={t("template-resolver.documentEditor.resourceKey")}>
111
+ <Input
112
+ kind="text"
113
+ id={`${idPrefix}-resource`}
114
+ name="resource"
115
+ value={m.resource}
116
+ onChange={m.setResource}
117
+ />
118
+ </Field>
119
+ <Field id={`${idPrefix}-alt`} label={t("template-resolver.documentEditor.imageAlt")}>
120
+ <Input kind="text" id={`${idPrefix}-alt`} name="alt" value={m.alt} onChange={m.setAlt} />
121
+ </Field>
122
+ </>
123
+ );
124
+ }
125
+
126
+ function renderButtonFields(m: PanelModel): ReactNode {
127
+ const { t, Field, Input, idPrefix } = m;
128
+ return (
129
+ !m.noVariables && (
130
+ <>
131
+ <NameSelect
132
+ id={`${idPrefix}-target`}
133
+ label={t("template-resolver.documentEditor.buttonTarget")}
134
+ names={m.selectable}
135
+ labels={m.variableLabels}
136
+ value={m.selectedName}
137
+ onChange={m.setChosenName}
138
+ />
139
+ <Field id={`${idPrefix}-text`} label={t("template-resolver.documentEditor.buttonText")}>
140
+ <Input
141
+ kind="text"
142
+ id={`${idPrefix}-text`}
143
+ name="text"
144
+ value={m.buttonText}
145
+ onChange={m.setButtonText}
146
+ />
147
+ </Field>
148
+ </>
149
+ )
150
+ );
151
+ }
152
+
153
+ function renderRepeatFields(m: PanelModel): ReactNode {
154
+ return (
155
+ !m.noVariables && (
156
+ <NameSelect
157
+ id={`${m.idPrefix}-list`}
158
+ label={m.t("template-resolver.documentEditor.repeatList")}
159
+ names={m.selectable}
160
+ labels={m.variableLabels}
161
+ value={m.selectedName}
162
+ onChange={m.setChosenName}
163
+ />
164
+ )
165
+ );
166
+ }
167
+
168
+ function renderImagesFields(m: PanelModel): ReactNode {
169
+ return (
170
+ !m.noVariables && (
171
+ <NameSelect
172
+ id={`${m.idPrefix}-images-variable`}
173
+ label={m.t("template-resolver.documentEditor.imagesVariable")}
174
+ names={m.selectable}
175
+ labels={m.variableLabels}
176
+ value={m.selectedName}
177
+ onChange={m.setChosenName}
178
+ />
179
+ )
180
+ );
181
+ }
182
+
183
+ function renderStepsFields(m: PanelModel): ReactNode {
184
+ const { t, Field, Input, idPrefix } = m;
185
+ return (
186
+ !m.noVariables && (
187
+ <>
188
+ <NameSelect
189
+ id={`${idPrefix}-steps-variable`}
190
+ label={t("template-resolver.documentEditor.stepsVariable")}
191
+ names={m.selectable}
192
+ labels={m.variableLabels}
193
+ value={m.selectedName}
194
+ onChange={m.setChosenName}
195
+ />
196
+ <Field
197
+ id={`${idPrefix}-steps-list`}
198
+ label={t("template-resolver.documentEditor.stepsList")}
199
+ >
200
+ <Input
201
+ kind="text"
202
+ id={`${idPrefix}-steps-list`}
203
+ name="steps"
204
+ value={m.stepsText}
205
+ onChange={m.setStepsText}
206
+ />
207
+ </Field>
208
+ </>
209
+ )
210
+ );
211
+ }
212
+
213
+ export const PANEL_FIELDS: Readonly<Record<PanelKind, (m: PanelModel) => ReactNode>> = {
214
+ link: renderLinkFields,
215
+ placeholder: renderPlaceholderFields,
216
+ align: renderAlignFields,
217
+ image: renderImageFields,
218
+ button: renderButtonFields,
219
+ repeat: renderRepeatFields,
220
+ images: renderImagesFields,
221
+ steps: renderStepsFields,
222
+ };
@@ -0,0 +1,164 @@
1
+ import type { usePrimitives } from "@cosmicdrift/kumiko-renderer";
2
+ import type { Editor, JSONContent } from "@tiptap/core";
3
+ import {
4
+ isAllowedDocumentLinkHref,
5
+ isDocumentPlaceholderHref,
6
+ isDocumentReferenceName,
7
+ isDocumentResourceKey,
8
+ } from "./document-schema.ts";
9
+ import { parseDocumentStepsInput } from "./document-steps.ts";
10
+ import { type PanelKind, repeatTableContent } from "./document-tiptap-helpers.ts";
11
+
12
+ type Translate = (key: string, params?: Readonly<Record<string, unknown>>) => string;
13
+ type Primitives = ReturnType<typeof usePrimitives>;
14
+
15
+ export type PanelModel = {
16
+ readonly t: Translate;
17
+ readonly Button: Primitives["Button"];
18
+ readonly Field: Primitives["Field"];
19
+ readonly Input: Primitives["Input"];
20
+ readonly editor: Editor;
21
+ readonly kind: PanelKind;
22
+ readonly idPrefix: string;
23
+ readonly variableLabels: Readonly<Record<string, string>>;
24
+ readonly onClose: () => void;
25
+ readonly href: string;
26
+ readonly setHref: (value: string) => void;
27
+ readonly resource: string;
28
+ readonly setResource: (value: string) => void;
29
+ readonly alt: string;
30
+ readonly setAlt: (value: string) => void;
31
+ readonly buttonText: string;
32
+ readonly setButtonText: (value: string) => void;
33
+ readonly stepsText: string;
34
+ readonly setStepsText: (value: string) => void;
35
+ readonly setChosenName: (value: string) => void;
36
+ readonly setError: (value: string | null) => void;
37
+ readonly selectable: readonly string[];
38
+ readonly selectedName: string;
39
+ readonly noVariables: boolean;
40
+ };
41
+
42
+ type ChosenNames = {
43
+ readonly selectable: readonly string[];
44
+ readonly selectedName: string;
45
+ readonly noVariables: boolean;
46
+ };
47
+
48
+ export function chosenNamesOf(
49
+ kind: PanelKind,
50
+ variables: readonly string[],
51
+ chosenName: string,
52
+ ): ChosenNames {
53
+ const referenceNames = variables.filter(isDocumentReferenceName);
54
+ const buttonTargets = referenceNames.filter((name) => isDocumentPlaceholderHref(`{{${name}}}`));
55
+ const selectable = kind === "button" ? buttonTargets : referenceNames;
56
+ const selectedName = selectable.includes(chosenName) ? chosenName : (selectable[0] ?? "");
57
+ const needsVariables =
58
+ kind === "button" ||
59
+ kind === "repeat" ||
60
+ kind === "placeholder" ||
61
+ kind === "steps" ||
62
+ kind === "images";
63
+ return { selectable, selectedName, noVariables: needsVariables && selectable.length === 0 };
64
+ }
65
+
66
+ export function insertAndCloseOf(m: PanelModel): (content: JSONContent) => void {
67
+ return (content) => {
68
+ m.editor.chain().focus().insertContent(content).run();
69
+ m.onClose();
70
+ };
71
+ }
72
+
73
+ function submitLinkOf(m: PanelModel): () => void {
74
+ const { editor, href, t } = m;
75
+ return () => {
76
+ if (!isAllowedDocumentLinkHref(href)) {
77
+ m.setError(t("template-resolver.documentEditor.invalidLink"));
78
+ // skip: invalid input is reported through the panel error
79
+ return;
80
+ }
81
+ const chain = editor.chain().focus();
82
+ if (!editor.state.selection.empty) chain.setLink({ href }).run();
83
+ else if (editor.isActive("link")) chain.extendMarkRange("link").setLink({ href }).run();
84
+ else if (isDocumentPlaceholderHref(href))
85
+ // Placeholders are never inserted as {{name}} text; the link shows the variable's value.
86
+ chain
87
+ .insertContent({
88
+ type: "variable",
89
+ attrs: { name: href.slice(2, -2) },
90
+ marks: [{ type: "link", attrs: { href } }],
91
+ })
92
+ .run();
93
+ else
94
+ chain
95
+ .insertContent({ type: "text", text: href, marks: [{ type: "link", attrs: { href } }] })
96
+ .run();
97
+ m.onClose();
98
+ };
99
+ }
100
+
101
+ function submitImageOf(m: PanelModel): () => void {
102
+ const { resource, alt, t } = m;
103
+ return () => {
104
+ if (!isDocumentResourceKey(resource)) {
105
+ m.setError(t("template-resolver.documentEditor.invalidResourceKey"));
106
+ // skip: invalid input is reported through the panel error
107
+ return;
108
+ }
109
+ if (/[<>]/.test(alt)) {
110
+ m.setError(t("template-resolver.documentEditor.invalidImageAlt"));
111
+ // skip: invalid input is reported through the panel error
112
+ return;
113
+ }
114
+ insertAndCloseOf(m)({ type: "resource", attrs: { resource, alt: alt || null } });
115
+ };
116
+ }
117
+
118
+ function submitStepsOf(m: PanelModel): () => void {
119
+ return () => {
120
+ const steps = parseDocumentStepsInput(m.stepsText);
121
+ if (steps === null) {
122
+ m.setError(m.t("template-resolver.documentEditor.invalidSteps"));
123
+ // skip: invalid input is reported through the panel error
124
+ return;
125
+ }
126
+ insertAndCloseOf(m)({ type: "steps", attrs: { variable: m.selectedName, steps } });
127
+ };
128
+ }
129
+
130
+ // Variable-bound kinds offer no insert while there is nothing to bind to.
131
+ const INSERT_HANDLERS: Partial<Record<PanelKind, (m: PanelModel) => () => void>> = {
132
+ link: submitLinkOf,
133
+ image: submitImageOf,
134
+ button: (m) => () =>
135
+ insertAndCloseOf(m)({
136
+ type: "button",
137
+ attrs: { href: `{{${m.selectedName}}}` },
138
+ content: m.buttonText ? [{ type: "text", text: m.buttonText }] : [],
139
+ }),
140
+ repeat: (m) => () => insertAndCloseOf(m)(repeatTableContent(m.selectedName)),
141
+ steps: submitStepsOf,
142
+ images: (m) => () => insertAndCloseOf(m)({ type: "images", attrs: { variable: m.selectedName } }),
143
+ };
144
+
145
+ const KINDS_WITHOUT_VARIABLE_GUARD: readonly PanelKind[] = ["link", "image"];
146
+
147
+ export function panelActionProps(m: PanelModel): {
148
+ onInsert?: () => void;
149
+ onRemoveLink?: () => void;
150
+ } {
151
+ const { editor, kind } = m;
152
+ const build = INSERT_HANDLERS[kind];
153
+ const insertAllowed = KINDS_WITHOUT_VARIABLE_GUARD.includes(kind) || !m.noVariables;
154
+ return {
155
+ ...(build !== undefined && insertAllowed && { onInsert: build(m) }),
156
+ ...(kind === "link" &&
157
+ editor.isActive("link") && {
158
+ onRemoveLink: () => {
159
+ editor.chain().focus().extendMarkRange("link").unsetLink().run();
160
+ m.onClose();
161
+ },
162
+ }),
163
+ };
164
+ }
@@ -0,0 +1,115 @@
1
+ import { usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
2
+ import type { Editor } from "@tiptap/core";
3
+ import { type ReactNode, useState } from "react";
4
+ import { PANEL_FIELDS } from "./document-insert-fields.tsx";
5
+ import { chosenNamesOf, type PanelModel, panelActionProps } from "./document-insert-model.ts";
6
+ import type { PanelKind } from "./document-tiptap-helpers.ts";
7
+
8
+ function PanelActions({
9
+ onInsert,
10
+ onRemoveLink,
11
+ onCancel,
12
+ }: {
13
+ readonly onInsert?: () => void;
14
+ readonly onRemoveLink?: () => void;
15
+ readonly onCancel: () => void;
16
+ }): ReactNode {
17
+ const { Button } = usePrimitives();
18
+ const t = useTranslation();
19
+ return (
20
+ <div className="flex gap-2">
21
+ {onInsert && (
22
+ <Button type="button" size="sm" onClick={onInsert}>
23
+ {t("template-resolver.documentEditor.insert")}
24
+ </Button>
25
+ )}
26
+ {onRemoveLink && (
27
+ <Button type="button" size="sm" variant="danger-ghost" onClick={onRemoveLink}>
28
+ {t("template-resolver.documentEditor.removeLink")}
29
+ </Button>
30
+ )}
31
+ <Button type="button" size="sm" variant="secondary" onClick={onCancel}>
32
+ {t("template-resolver.documentEditor.cancel")}
33
+ </Button>
34
+ </div>
35
+ );
36
+ }
37
+
38
+ type PanelProps = {
39
+ readonly editor: Editor;
40
+ readonly editorId: string;
41
+ readonly kind: PanelKind;
42
+ readonly variables: readonly string[];
43
+ readonly variableLabels: Readonly<Record<string, string>>;
44
+ readonly onClose: () => void;
45
+ };
46
+
47
+ export function InsertPanel({
48
+ editor,
49
+ editorId,
50
+ kind,
51
+ variables,
52
+ variableLabels,
53
+ onClose,
54
+ }: PanelProps): ReactNode {
55
+ const { Button, Field, Input } = usePrimitives();
56
+ const t = useTranslation();
57
+ const [href, setHref] = useState(() => {
58
+ const current = editor.getAttributes("link")["href"];
59
+ return typeof current === "string" ? current : "";
60
+ });
61
+ const [resource, setResource] = useState("");
62
+ const [alt, setAlt] = useState("");
63
+ const [buttonText, setButtonText] = useState(() =>
64
+ t("template-resolver.documentEditor.buttonDefaultText"),
65
+ );
66
+ const [stepsText, setStepsText] = useState(() =>
67
+ t("template-resolver.documentEditor.stepsDefault"),
68
+ );
69
+ const [chosenName, setChosenName] = useState("");
70
+ const [error, setError] = useState<string | null>(null);
71
+
72
+ const model: PanelModel = {
73
+ t,
74
+ Button,
75
+ Field,
76
+ Input,
77
+ editor,
78
+ kind,
79
+ idPrefix: `${editorId}-panel`,
80
+ variableLabels,
81
+ onClose,
82
+ href,
83
+ setHref,
84
+ resource,
85
+ setResource,
86
+ alt,
87
+ setAlt,
88
+ buttonText,
89
+ setButtonText,
90
+ stepsText,
91
+ setStepsText,
92
+ setChosenName,
93
+ setError,
94
+ ...chosenNamesOf(kind, variables, chosenName),
95
+ };
96
+
97
+ return (
98
+ <div className="flex flex-col gap-2 border-b border-input bg-muted/40 p-2">
99
+ {model.noVariables && (
100
+ <p className="text-sm text-muted-foreground">
101
+ {t("template-resolver.documentEditor.noVariables")}
102
+ </p>
103
+ )}
104
+ {PANEL_FIELDS[kind](model)}
105
+ {error !== null && (
106
+ <p role="alert" className="text-sm text-destructive">
107
+ {error}
108
+ </p>
109
+ )}
110
+ <div className="flex flex-wrap gap-2">
111
+ <PanelActions onCancel={onClose} {...panelActionProps(model)} />
112
+ </div>
113
+ </div>
114
+ );
115
+ }