@cosmicdrift/kumiko-samples 0.389.0 → 0.390.1

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 (301) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +27 -15
  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/changes.json +6 -0
  116. package/packages/bundled-features/src/inbound-provider-imap/feature.ts +4 -1
  117. package/packages/bundled-features/src/jobs/changes.json +6 -0
  118. package/packages/bundled-features/src/jobs/events.ts +1 -1
  119. package/packages/bundled-features/src/jobs/handlers/consumer.write.ts +7 -4
  120. package/packages/bundled-features/src/jobs/handlers/projection-rebuild.job.ts +1 -1
  121. package/packages/bundled-features/src/jobs/handlers/reindex-entity.job.ts +11 -5
  122. package/packages/bundled-features/src/jobs/handlers/retention-cleanup.job.ts +1 -1
  123. package/packages/bundled-features/src/jobs/handlers/retry.write.ts +1 -1
  124. package/packages/bundled-features/src/jobs/handlers/stale-run-sweep.job.ts +1 -1
  125. package/packages/bundled-features/src/jobs/handlers/trigger.write.ts +2 -2
  126. package/packages/bundled-features/src/jobs/job-run-logger.ts +139 -90
  127. package/packages/bundled-features/src/legal-pages/README.md +1 -1
  128. package/packages/bundled-features/src/legal-pages/lib/boot-check.ts +12 -8
  129. package/packages/bundled-features/src/mail-foundation/feature.ts +2 -2
  130. package/packages/bundled-features/src/mail-transport-smtp/changes.json +6 -0
  131. package/packages/bundled-features/src/mail-transport-smtp/feature.ts +4 -1
  132. package/packages/bundled-features/src/managed-pages/seeding.ts +13 -8
  133. package/packages/bundled-features/src/notes-history/handlers/add-note.write.ts +1 -1
  134. package/packages/bundled-features/src/personal-access-tokens/feature.ts +1 -1
  135. package/packages/bundled-features/src/personal-access-tokens/handlers/create.write.ts +2 -2
  136. package/packages/bundled-features/src/personal-access-tokens/handlers/revoke.write.ts +1 -1
  137. package/packages/bundled-features/src/rate-limiting/handlers/status.query.ts +2 -2
  138. package/packages/bundled-features/src/readiness/handlers/status.query.ts +4 -1
  139. package/packages/bundled-features/src/renderer-foundation/api.ts +3 -3
  140. package/packages/bundled-features/src/secrets/announce-secret-change.ts +4 -4
  141. package/packages/bundled-features/src/secrets/feature.ts +9 -9
  142. package/packages/bundled-features/src/secrets/handlers/delete.write.ts +4 -3
  143. package/packages/bundled-features/src/secrets/handlers/rotate.job.ts +13 -8
  144. package/packages/bundled-features/src/secrets/handlers/set.write.ts +4 -3
  145. package/packages/bundled-features/src/secrets/secrets-context.ts +16 -6
  146. package/packages/bundled-features/src/secrets/write-gate.ts +1 -1
  147. package/packages/bundled-features/src/seo/feature.ts +1 -1
  148. package/packages/bundled-features/src/sessions/changes.json +12 -0
  149. package/packages/bundled-features/src/sessions/feature.ts +1 -1
  150. package/packages/bundled-features/src/sessions/handlers/cleanup.job.ts +2 -2
  151. package/packages/bundled-features/src/sessions/handlers/detail.query.ts +5 -2
  152. package/packages/bundled-features/src/sessions/handlers/list.query.ts +5 -2
  153. package/packages/bundled-features/src/sessions/handlers/mine.query.ts +5 -2
  154. package/packages/bundled-features/src/sessions/handlers/revoke-all-for-user.write.ts +3 -3
  155. package/packages/bundled-features/src/sessions/handlers/revoke-all-others.write.ts +1 -1
  156. package/packages/bundled-features/src/sessions/handlers/revoke.write.ts +1 -1
  157. package/packages/bundled-features/src/sessions/screens.ts +8 -4
  158. package/packages/bundled-features/src/sessions/session-revoked-event.ts +1 -1
  159. package/packages/bundled-features/src/sessions/web/session-device-cell.tsx +10 -12
  160. package/packages/bundled-features/src/sessions/web/user-agent.ts +12 -0
  161. package/packages/bundled-features/src/shared/index.ts +15 -0
  162. package/packages/bundled-features/src/shared/purge-files-reaching-subject.ts +12 -2
  163. package/packages/bundled-features/src/shared/require-for-tenant.ts +7 -5
  164. package/packages/bundled-features/src/shared/stream-tenant-backfill.ts +201 -0
  165. package/packages/bundled-features/src/shared/stream-tenant-derived-state.ts +107 -0
  166. package/packages/bundled-features/src/step-dispatcher/feature.ts +1 -1
  167. package/packages/bundled-features/src/subscription-dodo/plugin-methods.ts +1 -1
  168. package/packages/bundled-features/src/subscription-dodo/runtime.ts +7 -7
  169. package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +5 -3
  170. package/packages/bundled-features/src/subscription-stripe/runtime.ts +7 -7
  171. package/packages/bundled-features/src/template-resolver/api.ts +3 -3
  172. package/packages/bundled-features/src/template-resolver/changes.json +18 -0
  173. package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +5 -4
  174. package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +2 -2
  175. package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +1 -1
  176. package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +2 -2
  177. package/packages/bundled-features/src/template-resolver/handlers/send-test-shared.ts +1 -1
  178. package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +2 -2
  179. package/packages/bundled-features/src/template-resolver/handlers/tenant-override-selection.ts +6 -2
  180. package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +4 -2
  181. package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +1 -1
  182. package/packages/bundled-features/src/template-resolver/seeding.ts +12 -2
  183. package/packages/bundled-features/src/template-resolver/store-template-resource.ts +18 -13
  184. package/packages/bundled-features/src/template-resolver/template-resources.ts +4 -4
  185. package/packages/bundled-features/src/template-resolver/testing.ts +2 -2
  186. package/packages/bundled-features/src/template-resolver/web/document-insert-fields.tsx +222 -0
  187. package/packages/bundled-features/src/template-resolver/web/document-insert-model.ts +164 -0
  188. package/packages/bundled-features/src/template-resolver/web/document-insert-panel.tsx +115 -0
  189. package/packages/bundled-features/src/template-resolver/web/document-tiptap-bubble.tsx +177 -0
  190. package/packages/bundled-features/src/template-resolver/web/document-tiptap-content.tsx +38 -0
  191. package/packages/bundled-features/src/template-resolver/web/document-tiptap-context.ts +119 -0
  192. package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +51 -1098
  193. package/packages/bundled-features/src/template-resolver/web/document-tiptap-helpers.ts +161 -0
  194. package/packages/bundled-features/src/template-resolver/web/document-tiptap-layout.tsx +83 -0
  195. package/packages/bundled-features/src/template-resolver/web/document-tiptap-toolbar-groups.tsx +153 -0
  196. package/packages/bundled-features/src/template-resolver/web/document-tiptap-toolbar.tsx +182 -0
  197. package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +18 -0
  198. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +19 -6
  199. package/packages/bundled-features/src/template-resolver/web/use-document-tiptap-model.ts +138 -0
  200. package/packages/bundled-features/src/tenant/changes.json +19 -0
  201. package/packages/bundled-features/src/tenant/handlers/cancel-invitation.write.ts +1 -1
  202. package/packages/bundled-features/src/tenant/handlers/memberships.query.ts +1 -1
  203. package/packages/bundled-features/src/tenant/handlers/remove-member.write.ts +2 -2
  204. package/packages/bundled-features/src/tenant/handlers/update-member-roles.write.ts +1 -1
  205. package/packages/bundled-features/src/tenant/index.ts +5 -0
  206. package/packages/bundled-features/src/tenant/invited-membership.ts +6 -1
  207. package/packages/bundled-features/src/tenant/schema/tenant.ts +4 -1
  208. package/packages/bundled-features/src/tenant/seeding.ts +65 -62
  209. package/packages/bundled-features/src/tenant/stream-tenant-backfill.ts +115 -0
  210. package/packages/bundled-features/src/tenant/testing.ts +77 -0
  211. package/packages/bundled-features/src/tenant-handover/events.ts +1 -1
  212. package/packages/bundled-features/src/tenant-handover/handlers/claim.write.ts +1 -1
  213. package/packages/bundled-features/src/tenant-lifecycle/feature.ts +5 -5
  214. package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +22 -10
  215. package/packages/bundled-features/src/tenant-lifecycle/handlers/request-destruction.write.ts +22 -11
  216. package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +23 -12
  217. package/packages/bundled-features/src/tenant-lifecycle/stages.ts +7 -3
  218. package/packages/bundled-features/src/tenant-lifecycle/testing.ts +9 -8
  219. package/packages/bundled-features/src/tier-engine/entity.ts +1 -1
  220. package/packages/bundled-features/src/tier-engine/feature.ts +8 -3
  221. package/packages/bundled-features/src/tier-engine/handlers/active-tier.query.ts +3 -3
  222. package/packages/bundled-features/src/tier-engine/handlers/get-tenant-tier.query.ts +7 -1
  223. package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +3 -3
  224. package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +71 -97
  225. package/packages/bundled-features/src/user/handlers/find-for-auth.query.ts +2 -2
  226. package/packages/bundled-features/src/user/handlers/update-roles.ts +2 -2
  227. package/packages/bundled-features/src/user/index.ts +2 -0
  228. package/packages/bundled-features/src/user/invited-global-roles.ts +6 -1
  229. package/packages/bundled-features/src/user/principal-status.ts +1 -1
  230. package/packages/bundled-features/src/user/seeding.ts +24 -6
  231. package/packages/bundled-features/src/user/testing.ts +22 -0
  232. package/packages/bundled-features/src/user-data-rights/COMPLIANCE.md +1 -1
  233. package/packages/bundled-features/src/user-data-rights/README.md +1 -1
  234. package/packages/bundled-features/src/user-data-rights/audit-download.ts +12 -2
  235. package/packages/bundled-features/src/user-data-rights/changes.json +24 -0
  236. package/packages/bundled-features/src/user-data-rights/feature.ts +29 -24
  237. package/packages/bundled-features/src/user-data-rights/handlers/confirm-deletion-by-token.write.ts +1 -1
  238. package/packages/bundled-features/src/user-data-rights/handlers/deletion-grace-period.ts +1 -1
  239. package/packages/bundled-features/src/user-data-rights/handlers/download-by-job.query.ts +2 -2
  240. package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +2 -2
  241. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +1 -1
  242. package/packages/bundled-features/src/user-data-rights/handlers/restrict-account.write.ts +4 -4
  243. package/packages/bundled-features/src/user-data-rights/handlers/run-forget-cleanup.write.ts +12 -6
  244. package/packages/bundled-features/src/user-data-rights/index.ts +2 -0
  245. package/packages/bundled-features/src/user-data-rights/lib/mail-transport-resolver.ts +1 -1
  246. package/packages/bundled-features/src/user-data-rights/lib/storage-provider-resolver.ts +1 -1
  247. package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +6 -1
  248. package/packages/bundled-features/src/user-data-rights/run-export-jobs.ts +8 -2
  249. package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +14 -3
  250. package/packages/bundled-features/src/user-data-rights/run-user-export.ts +11 -1
  251. package/packages/bundled-features/src/user-data-rights/web/constants.ts +10 -0
  252. package/packages/bundled-features/src/user-data-rights/web/index.ts +6 -1
  253. package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +8 -6
  254. package/packages/bundled-features/src/waitlist/handlers/export-purge.write.ts +1 -1
  255. package/packages/bundled-features/src/waitlist/handlers/invite.write.ts +2 -2
  256. package/packages/bundled-features/src/waitlist/handlers/reject.write.ts +1 -1
  257. package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +9 -4
  258. package/packages/bundled-features/src/waitlist/legacy-migrate.ts +6 -1
  259. package/packages/bundled-features/src/waitlist/link-on-signup.ts +7 -1
  260. package/packages/bundled-features/src/workflow-runner/changes.json +6 -0
  261. package/packages/bundled-features/src/workflow-runner/db/queries/due-runs.ts +1 -1
  262. package/packages/bundled-features/src/workflow-runner/event-subscriber.ts +4 -3
  263. package/packages/bundled-features/src/workflow-runner/event-trigger.ts +2 -1
  264. package/packages/bundled-features/src/workflow-runner/feature.ts +2 -2
  265. package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +8 -8
  266. package/packages/bundled-features/src/workflow-runner/runner.ts +6 -4
  267. package/samples/apps/cap-billing-demo/package.json +1 -1
  268. package/samples/apps/cap-billing-demo/src/feature.ts +19 -5
  269. package/samples/apps/config-demo/src/app/server.ts +1 -1
  270. package/samples/apps/styleguide/src/app/server.ts +2 -2
  271. package/samples/apps/ui-walkthrough/package.json +1 -1
  272. package/samples/apps/use-all-bundled/feature-manifest.json +1 -1
  273. package/samples/apps/use-all-bundled/package.json +1 -1
  274. package/samples/apps/use-all-bundled/src/app/seed.ts +12 -2
  275. package/samples/apps/user-data-rights-demo/README.md +1 -1
  276. package/samples/apps/user-data-rights-demo/package.json +1 -1
  277. package/samples/apps/user-data-rights-demo/src/feature.ts +3 -2
  278. package/samples/recipes/api-worker-split/README.md +2 -1
  279. package/samples/recipes/api-worker-split/src/feature.ts +2 -1
  280. package/samples/recipes/cross-feature-events/README.md +5 -5
  281. package/samples/recipes/cross-feature-events/package.json +1 -1
  282. package/samples/recipes/cross-feature-events/src/feature.ts +4 -4
  283. package/samples/recipes/event-sourcing/README.md +5 -5
  284. package/samples/recipes/event-sourcing/package.json +1 -1
  285. package/samples/recipes/event-sourcing/src/feature.ts +15 -15
  286. package/samples/recipes/files-post-processing/README.md +1 -1
  287. package/samples/recipes/files-post-processing/package.json +1 -1
  288. package/samples/recipes/files-post-processing/src/feature.ts +6 -4
  289. package/samples/recipes/lane-routing/package.json +1 -1
  290. package/samples/recipes/legal-pages/README.md +2 -2
  291. package/samples/recipes/lifecycle-hooks/README.md +2 -2
  292. package/samples/recipes/lifecycle-hooks/src/feature.ts +1 -1
  293. package/samples/recipes/managed-config/README.md +3 -3
  294. package/samples/recipes/search/README.md +1 -1
  295. package/samples/recipes/search/src/feature.ts +3 -3
  296. package/samples/recipes/secrets-demo/README.md +1 -1
  297. package/samples/recipes/secrets-demo/src/feature.ts +5 -2
  298. package/samples/recipes/state-machine/src/events.ts +1 -1
  299. package/samples/recipes/state-machine/src/reducer.ts +1 -1
  300. package/samples/recipes/workflow-engine/src/resume-loop.ts +4 -4
  301. package/samples/recipes/workflow-engine/src/workflow-runner.ts +2 -2
@@ -0,0 +1,161 @@
1
+ import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
2
+ import type { Editor, JSONContent } from "@tiptap/core";
3
+ import type { DocumentResourcePreview, DocumentToolbarBlock } from "./document-toolbar-blocks.ts";
4
+
5
+ export const NO_LABELS: Readonly<Record<string, string>> = {};
6
+ export const NO_NAMES: readonly string[] = [];
7
+ export const NO_PREVIEWS: Readonly<Record<string, DocumentResourcePreview>> = {};
8
+
9
+ export function previewsKey(previews: Readonly<Record<string, DocumentResourcePreview>>): string {
10
+ return Object.entries(previews)
11
+ .map(([key, { src, caption }]) => `${key}|${src.length}|${src.slice(-24)}|${caption ?? ""}`)
12
+ .join("\n");
13
+ }
14
+
15
+ export type PanelKind =
16
+ | "link"
17
+ | "placeholder"
18
+ | "image"
19
+ | "button"
20
+ | "repeat"
21
+ | "align"
22
+ | "steps"
23
+ | "images";
24
+
25
+ // Blocks that stay in the canvas toolbar; the rest moves into the overflow menu.
26
+ export const CANVAS_INLINE_BLOCKS: readonly DocumentToolbarBlock[] = ["image", "table"];
27
+
28
+ export const BLOCK_PANEL: Partial<Record<DocumentToolbarBlock, PanelKind>> = {
29
+ image: "image",
30
+ button: "button",
31
+ repeat: "repeat",
32
+ align: "align",
33
+ steps: "steps",
34
+ images: "images",
35
+ };
36
+
37
+ export type RepeatTableState = {
38
+ readonly lists: readonly string[];
39
+ readonly headerRepeats: boolean;
40
+ readonly firstRowPos: number;
41
+ };
42
+
43
+ // print-document moves the leading rows flagged repeatHeader into thead, so the
44
+ // toggle reads and writes the table's first row.
45
+ export function findRepeatTable(editor: Editor): RepeatTableState | null {
46
+ const { $from } = editor.state.selection;
47
+ for (let depth = $from.depth; depth > 0; depth--) {
48
+ const node = $from.node(depth);
49
+ if (node.type.name !== "table") continue;
50
+ const lists: string[] = [];
51
+ node.forEach((row) => {
52
+ const each: unknown = row.attrs["each"];
53
+ if (typeof each === "string" && each !== "" && !lists.includes(each)) lists.push(each);
54
+ });
55
+ if (lists.length === 0) return null;
56
+ return {
57
+ lists,
58
+ headerRepeats: node.firstChild?.attrs["repeatHeader"] === true,
59
+ firstRowPos: $from.start(depth),
60
+ };
61
+ }
62
+ return null;
63
+ }
64
+
65
+ export function toggleRepeatHeader(editor: Editor): void {
66
+ const found = findRepeatTable(editor);
67
+ // skip: the cursor is not inside a repeat table
68
+ if (found === null) return;
69
+ editor
70
+ .chain()
71
+ .focus()
72
+ .command(({ tr }) => {
73
+ const row = tr.doc.nodeAt(found.firstRowPos);
74
+ if (row === null) return false;
75
+ tr.setNodeMarkup(found.firstRowPos, undefined, {
76
+ ...row.attrs,
77
+ repeatHeader: !found.headerRepeats,
78
+ });
79
+ return true;
80
+ })
81
+ .run();
82
+ }
83
+
84
+ const PARAGRAPH_STYLES = ["paragraph", "1", "2", "3"] as const;
85
+ type ParagraphStyle = (typeof PARAGRAPH_STYLES)[number];
86
+
87
+ export const HEADING_LEVELS = { "1": 1, "2": 2, "3": 3 } as const;
88
+
89
+ const PLACEHOLDER_HREF = /^\{\{([^{}]+)\}\}$/;
90
+
91
+ export function variableLabelOf(name: string, labels: Readonly<Record<string, string>>): string {
92
+ return Object.hasOwn(labels, name) ? (labels[name] ?? name) : name;
93
+ }
94
+
95
+ export function buttonTargetLabel(href: string, labels: Readonly<Record<string, string>>): string {
96
+ const name = PLACEHOLDER_HREF.exec(href)?.[1];
97
+ return name === undefined ? href : variableLabelOf(name, labels);
98
+ }
99
+
100
+ export function isParagraphStyle(value: string): value is ParagraphStyle {
101
+ return isOneOf(PARAGRAPH_STYLES, value);
102
+ }
103
+
104
+ function emptyParagraph(): JSONContent {
105
+ return { type: "paragraph" };
106
+ }
107
+
108
+ export function repeatTableContent(listName: string): JSONContent {
109
+ const headerRow: JSONContent = {
110
+ type: "tableRow",
111
+ content: [
112
+ { type: "tableHeader", content: [emptyParagraph()] },
113
+ { type: "tableHeader", content: [emptyParagraph()] },
114
+ ],
115
+ };
116
+ const repeatRow: JSONContent = {
117
+ type: "tableRow",
118
+ attrs: { each: listName },
119
+ content: [
120
+ { type: "tableCell", content: [emptyParagraph()] },
121
+ { type: "tableCell", content: [emptyParagraph()] },
122
+ ],
123
+ };
124
+ return { type: "table", content: [headerRow, repeatRow] };
125
+ }
126
+
127
+ function blockStyleOf(e: Editor): "1" | "2" | "3" | "paragraph" {
128
+ if (e.isActive("heading", { level: 1 })) return "1";
129
+ if (e.isActive("heading", { level: 2 })) return "2";
130
+ if (e.isActive("heading", { level: 3 })) return "3";
131
+ return "paragraph";
132
+ }
133
+
134
+ function selectedBlockOf(
135
+ e: Editor,
136
+ ): { readonly type: "resource" | "button"; readonly detail: string } | null {
137
+ if (e.isActive("resource")) {
138
+ return { type: "resource", detail: String(e.getAttributes("resource")["resource"]) };
139
+ }
140
+ if (e.isActive("button")) {
141
+ return { type: "button", detail: String(e.getAttributes("button")["href"]) };
142
+ }
143
+ return null; // skip: no resource or button selected
144
+ }
145
+
146
+ export function activeStateOf(e: Editor, showHtml: boolean) {
147
+ return {
148
+ blockStyle: blockStyleOf(e),
149
+ bold: e.isActive("bold"),
150
+ italic: e.isActive("italic"),
151
+ underline: e.isActive("underline"),
152
+ selectedBlock: selectedBlockOf(e),
153
+ html: showHtml ? e.getHTML() : "",
154
+ bulletList: e.isActive("bulletList"),
155
+ orderedList: e.isActive("orderedList"),
156
+ link: e.isActive("link"),
157
+ repeatTable: findRepeatTable(e),
158
+ };
159
+ }
160
+
161
+ export type ActiveState = ReturnType<typeof activeStateOf>;
@@ -0,0 +1,83 @@
1
+ import { cn } from "@cosmicdrift/kumiko-renderer/web";
2
+ import type { ReactNode } from "react";
3
+ import { createPortal } from "react-dom";
4
+ import { InsertPanel } from "./document-insert-panel.tsx";
5
+ import { renderBubbleMenu, renderInlineRepeatBar } from "./document-tiptap-bubble.tsx";
6
+ import type { TiptapContext } from "./document-tiptap-context.ts";
7
+ import { renderToolbarBar } from "./document-tiptap-toolbar.tsx";
8
+
9
+ function renderInsertPanel(c: TiptapContext): ReactNode {
10
+ const { openPanel } = c;
11
+ return (
12
+ openPanel !== null &&
13
+ !c.readOnly && (
14
+ <InsertPanel
15
+ key={openPanel}
16
+ editor={c.editor}
17
+ editorId={c.id}
18
+ kind={openPanel}
19
+ variables={openPanel === "images" ? c.imageVariables : c.variables}
20
+ variableLabels={c.stableLabels}
21
+ onClose={() => c.setOpenPanel(null)}
22
+ />
23
+ )
24
+ );
25
+ }
26
+
27
+ export function renderHostedEditor(
28
+ c: TiptapContext,
29
+ toolbarHost: HTMLElement | null,
30
+ editorContent: ReactNode,
31
+ ): ReactNode {
32
+ return (
33
+ <>
34
+ {toolbarHost !== null &&
35
+ createPortal(
36
+ <>
37
+ {renderToolbarBar(c)}
38
+ {renderInsertPanel(c)}
39
+ </>,
40
+ toolbarHost,
41
+ )}
42
+ {!c.readOnly && renderBubbleMenu(c)}
43
+ {editorContent}
44
+ </>
45
+ );
46
+ }
47
+
48
+ function renderHtmlSource(c: TiptapContext): ReactNode {
49
+ return (
50
+ c.canvas &&
51
+ !c.hosted &&
52
+ c.showHtml && (
53
+ <section aria-label={c.t("template-resolver.documentEditor.htmlSource")}>
54
+ <pre className="h-40 overflow-auto whitespace-pre-wrap break-all border-b border-input bg-muted p-2 font-mono text-xs">
55
+ {c.activeState?.html ?? ""}
56
+ </pre>
57
+ </section>
58
+ )
59
+ );
60
+ }
61
+
62
+ export function renderInlineEditor(c: TiptapContext, paper: ReactNode): ReactNode {
63
+ const { canvas, hosted } = c;
64
+ return (
65
+ <div
66
+ className={cn(
67
+ canvas ? "flex min-h-0 flex-1 flex-col" : "rounded-md border border-input bg-transparent",
68
+ )}
69
+ >
70
+ {!hosted && renderToolbarBar(c)}
71
+ {canvas && !c.readOnly && renderBubbleMenu(c)}
72
+ {renderHtmlSource(c)}
73
+ {!hosted && renderInsertPanel(c)}
74
+ {renderInlineRepeatBar(c)}
75
+ <div
76
+ ref={c.setCanvasScroller}
77
+ className={cn(canvas && "min-h-0 flex-1 overflow-auto bg-muted px-8 py-6")}
78
+ >
79
+ {paper}
80
+ </div>
81
+ </div>
82
+ );
83
+ }
@@ -0,0 +1,153 @@
1
+ import { cn } from "@cosmicdrift/kumiko-renderer/web";
2
+ import {
3
+ Bold as BoldIcon,
4
+ Italic as ItalicIcon,
5
+ Link as LinkIcon,
6
+ List,
7
+ ListOrdered,
8
+ Underline as UnderlineIcon,
9
+ } from "lucide-react";
10
+ import type { ReactNode } from "react";
11
+ import type { TiptapContext } from "./document-tiptap-context.ts";
12
+
13
+ // Canvas chrome is one flat row: unframed ghost buttons, glyph plus text for the longer names.
14
+ export function iconButton(
15
+ c: TiptapContext,
16
+ label: string,
17
+ icon: ReactNode,
18
+ isActive: boolean,
19
+ onClick: () => void,
20
+ text?: string,
21
+ ): ReactNode {
22
+ const { Button, canvas, readOnly } = c;
23
+ return canvas ? (
24
+ <Button
25
+ type="button"
26
+ variant="ghost"
27
+ size="sm"
28
+ pressed={isActive}
29
+ disabled={readOnly}
30
+ onClick={onClick}
31
+ ariaLabel={label}
32
+ title={label}
33
+ >
34
+ {icon}
35
+ {text}
36
+ </Button>
37
+ ) : (
38
+ <Button
39
+ type="button"
40
+ variant={isActive ? "primary" : "secondary"}
41
+ size="icon"
42
+ pressed={isActive}
43
+ disabled={readOnly}
44
+ onClick={onClick}
45
+ ariaLabel={label}
46
+ title={label}
47
+ >
48
+ {icon}
49
+ </Button>
50
+ );
51
+ }
52
+
53
+ function markContent(
54
+ canvas: boolean,
55
+ letter: string,
56
+ icon: ReactNode,
57
+ className: string,
58
+ ): ReactNode {
59
+ return canvas ? <span className={className}>{letter}</span> : icon;
60
+ }
61
+
62
+ export function renderParagraphStyleSelect(c: TiptapContext): ReactNode {
63
+ const { t, Field, Input, id, canvas, compact, readOnly, activeState } = c;
64
+ const paragraphStyleLabel = t("template-resolver.documentEditor.paragraphStyle");
65
+ const styleOptions = [
66
+ { value: "paragraph", label: t("template-resolver.documentEditor.paragraph") },
67
+ { value: "1", label: t("kumiko.contentEditor.heading1") },
68
+ { value: "2", label: t("kumiko.contentEditor.heading2") },
69
+ { value: "3", label: t("template-resolver.documentEditor.heading3") },
70
+ ];
71
+ return (
72
+ <div
73
+ // kumiko-lint-ignore raw-classname the toolbar paragraph select sits flush in the editor toolbar; ComboboxInput has no flat trigger variant
74
+ className={cn(
75
+ !canvas && (compact ? "w-24" : "w-40"),
76
+ canvas &&
77
+ "w-auto shrink-0 [&_button[aria-haspopup=listbox]]:border-transparent [&_button[aria-haspopup=listbox]]:bg-transparent [&_button[aria-haspopup=listbox]]:shadow-none",
78
+ )}
79
+ >
80
+ <Field id={`${id}-paragraph-style`} label={paragraphStyleLabel} hideLabel>
81
+ <Input
82
+ kind="select"
83
+ id={`${id}-paragraph-style`}
84
+ name="paragraph-style"
85
+ value={activeState?.blockStyle ?? "paragraph"}
86
+ onChange={c.setBlockStyle}
87
+ options={styleOptions}
88
+ display="dropdown"
89
+ disabled={readOnly}
90
+ />
91
+ </Field>
92
+ </div>
93
+ );
94
+ }
95
+
96
+ export function renderMarkButtons(c: TiptapContext): ReactNode {
97
+ const { t, editor, canvas, activeState } = c;
98
+ return (
99
+ <>
100
+ {iconButton(
101
+ c,
102
+ t("kumiko.contentEditor.bold"),
103
+ markContent(canvas, "B", <BoldIcon size={16} />, "font-bold"),
104
+ activeState?.bold ?? false,
105
+ () => editor.chain().focus().toggleBold().run(),
106
+ )}
107
+ {iconButton(
108
+ c,
109
+ t("kumiko.contentEditor.italic"),
110
+ markContent(canvas, "I", <ItalicIcon size={16} />, "italic"),
111
+ activeState?.italic ?? false,
112
+ () => editor.chain().focus().toggleItalic().run(),
113
+ )}
114
+ {iconButton(
115
+ c,
116
+ t("template-resolver.documentEditor.underline"),
117
+ markContent(canvas, "U", <UnderlineIcon size={16} />, "underline"),
118
+ activeState?.underline ?? false,
119
+ () => editor.chain().focus().toggleUnderline().run(),
120
+ )}
121
+ </>
122
+ );
123
+ }
124
+
125
+ export function renderListButtons(c: TiptapContext): ReactNode {
126
+ const { t, editor, canvas, activeState } = c;
127
+ return (
128
+ <>
129
+ {iconButton(
130
+ c,
131
+ t("kumiko.contentEditor.bulletList"),
132
+ <List size={16} />,
133
+ activeState?.bulletList ?? false,
134
+ () => editor.chain().focus().toggleBulletList().run(),
135
+ canvas ? undefined : t("template-resolver.documentEditor.list"),
136
+ )}
137
+ {iconButton(
138
+ c,
139
+ t("kumiko.contentEditor.orderedList"),
140
+ <ListOrdered size={16} />,
141
+ activeState?.orderedList ?? false,
142
+ () => editor.chain().focus().toggleOrderedList().run(),
143
+ )}
144
+ {iconButton(
145
+ c,
146
+ t("template-resolver.documentEditor.link"),
147
+ <LinkIcon size={16} />,
148
+ activeState?.link ?? false,
149
+ () => c.togglePanel("link"),
150
+ )}
151
+ </>
152
+ );
153
+ }
@@ -0,0 +1,182 @@
1
+ import type { ActionMenuItemSpec } from "@cosmicdrift/kumiko-renderer";
2
+ import { cn } from "@cosmicdrift/kumiko-renderer/web";
3
+ import {
4
+ AlignLeft,
5
+ Braces,
6
+ ChevronDown,
7
+ GalleryThumbnails,
8
+ Image as ImageIcon,
9
+ ListChecks,
10
+ Minus,
11
+ MousePointerClick,
12
+ Repeat,
13
+ Scissors,
14
+ Table as TableIcon,
15
+ } from "lucide-react";
16
+ import { Fragment, type ReactNode } from "react";
17
+ import type { TiptapContext } from "./document-tiptap-context.ts";
18
+ import { CANVAS_INLINE_BLOCKS } from "./document-tiptap-helpers.ts";
19
+ import {
20
+ iconButton,
21
+ renderListButtons,
22
+ renderMarkButtons,
23
+ renderParagraphStyleSelect,
24
+ } from "./document-tiptap-toolbar-groups.tsx";
25
+ import type { DocumentToolbarBlock } from "./document-toolbar-blocks.ts";
26
+
27
+ type BlockButtons = Record<DocumentToolbarBlock, { label: string; icon: ReactNode }>;
28
+
29
+ function Separator({ compact = false }: { readonly compact?: boolean }): ReactNode {
30
+ return (
31
+ <span aria-hidden="true" className={cn("h-5 w-px bg-border", compact ? "mx-0.5" : "mx-2")} />
32
+ );
33
+ }
34
+
35
+ function blockButtonsOf(c: TiptapContext): BlockButtons {
36
+ const { t } = c;
37
+ const { imageLabel, pageBreakLabel, stepsLabel, imagesLabel } = c.labels;
38
+ return {
39
+ image: { label: imageLabel, icon: <ImageIcon size={16} /> },
40
+ table: { label: t("template-resolver.documentEditor.table"), icon: <TableIcon size={16} /> },
41
+ button: {
42
+ label: t("template-resolver.documentEditor.button"),
43
+ icon: <MousePointerClick size={16} />,
44
+ },
45
+ divider: { label: t("template-resolver.documentEditor.divider"), icon: <Minus size={16} /> },
46
+ repeat: { label: t("template-resolver.documentEditor.repeat"), icon: <Repeat size={16} /> },
47
+ pageBreak: { label: pageBreakLabel, icon: <Scissors size={16} /> },
48
+ align: { label: t("template-resolver.documentEditor.align"), icon: <AlignLeft size={16} /> },
49
+ steps: { label: stepsLabel, icon: <ListChecks size={16} /> },
50
+ images: { label: imagesLabel, icon: <GalleryThumbnails size={16} /> },
51
+ };
52
+ }
53
+
54
+ type LabelledButtonSpec = {
55
+ readonly key?: string;
56
+ readonly label: string;
57
+ readonly icon: ReactNode;
58
+ readonly showIcon: boolean;
59
+ readonly trailingIcon?: ReactNode;
60
+ readonly expanded?: boolean;
61
+ readonly onClick: () => void;
62
+ };
63
+
64
+ function labelledButton(c: TiptapContext, spec: LabelledButtonSpec): ReactNode {
65
+ const { Button, canvas, compact, readOnly } = c;
66
+ const flatVariant = canvas ? "ghost" : "secondary";
67
+ const shared = {
68
+ type: "button",
69
+ variant: flatVariant,
70
+ disabled: readOnly,
71
+ onClick: spec.onClick,
72
+ ...(spec.expanded !== undefined && { expanded: spec.expanded }),
73
+ } as const;
74
+ return compact ? (
75
+ <Button key={spec.key} {...shared} size="icon" ariaLabel={spec.label} title={spec.label}>
76
+ {spec.icon}
77
+ </Button>
78
+ ) : (
79
+ <Button key={spec.key} {...shared} size="sm">
80
+ {spec.showIcon && spec.icon}
81
+ {spec.label}
82
+ {spec.trailingIcon}
83
+ </Button>
84
+ );
85
+ }
86
+
87
+ function renderInlineBlock(
88
+ c: TiptapContext,
89
+ block: DocumentToolbarBlock,
90
+ blockButtons: BlockButtons,
91
+ ): ReactNode {
92
+ if (c.canvas) {
93
+ return (
94
+ <Fragment key={block}>
95
+ {iconButton(c, blockButtons[block].label, blockButtons[block].icon, false, () =>
96
+ c.runBlock(block),
97
+ )}
98
+ </Fragment>
99
+ );
100
+ }
101
+ return labelledButton(c, {
102
+ key: block,
103
+ label: blockButtons[block].label,
104
+ icon: blockButtons[block].icon,
105
+ showIcon: true,
106
+ onClick: () => c.runBlock(block),
107
+ });
108
+ }
109
+
110
+ function overflowItemsOf(
111
+ c: TiptapContext,
112
+ menuBlocks: readonly DocumentToolbarBlock[],
113
+ blockButtons: BlockButtons,
114
+ ): readonly ActionMenuItemSpec[] {
115
+ const { t, canvas, hosted, readOnly } = c;
116
+ return [
117
+ ...menuBlocks.map((block) => ({
118
+ id: block,
119
+ label: blockButtons[block].label,
120
+ disabled: readOnly,
121
+ onSelect: () => c.runBlock(block),
122
+ })),
123
+ ...(canvas && !hosted
124
+ ? [
125
+ {
126
+ id: "html",
127
+ label: t("template-resolver.documentEditor.showHtml"),
128
+ onSelect: () => c.setShowHtml((current) => !current),
129
+ },
130
+ ]
131
+ : []),
132
+ ];
133
+ }
134
+
135
+ export function renderToolbarBar(c: TiptapContext): ReactNode {
136
+ const { t, id, canvas, compact, ActionOverflowMenu } = c;
137
+ const blockButtons = blockButtonsOf(c);
138
+ // The canvas toolbar never scrolls sideways: the rarer blocks and the HTML view sit in the menu.
139
+ const menuBlocks =
140
+ canvas && ActionOverflowMenu !== undefined
141
+ ? c.blocks.filter((block) => !CANVAS_INLINE_BLOCKS.includes(block))
142
+ : [];
143
+ const inlineBlocks = c.blocks.filter((block) => !menuBlocks.includes(block));
144
+ const overflowItems = overflowItemsOf(c, menuBlocks, blockButtons);
145
+
146
+ return (
147
+ <div
148
+ className={cn(
149
+ "flex items-center border-b border-input",
150
+ canvas
151
+ ? "min-h-9 flex-nowrap gap-0 overflow-hidden bg-background px-1.5 [&>*]:shrink-0 [&_button]:h-7 [&_button]:gap-1 [&_button]:px-1 [&_button]:text-xs"
152
+ : "flex-wrap gap-1 p-1",
153
+ )}
154
+ >
155
+ {renderParagraphStyleSelect(c)}
156
+ <Separator compact={compact || canvas} />
157
+ {renderMarkButtons(c)}
158
+ <Separator compact={compact || canvas} />
159
+ {renderListButtons(c)}
160
+ <Separator compact={compact || canvas} />
161
+ {labelledButton(c, {
162
+ label: t("template-resolver.documentEditor.insertPlaceholder"),
163
+ icon: <Braces size={16} />,
164
+ showIcon: true,
165
+ trailingIcon: canvas ? <ChevronDown size={12} /> : undefined,
166
+ expanded: c.openPanel === "placeholder",
167
+ onClick: () => c.togglePanel("placeholder"),
168
+ })}
169
+ {inlineBlocks.length > 0 && <Separator compact={compact || canvas} />}
170
+ {inlineBlocks.map((block) => renderInlineBlock(c, block, blockButtons))}
171
+ {overflowItems.length > 0 && ActionOverflowMenu !== undefined && (
172
+ <span className="ml-auto shrink-0">
173
+ <ActionOverflowMenu
174
+ items={overflowItems}
175
+ label={t("template-resolver.documentEditor.more")}
176
+ testId={`${id}-toolbar-more`}
177
+ />
178
+ </span>
179
+ )}
180
+ </div>
181
+ );
182
+ }
@@ -1,4 +1,5 @@
1
1
  import type { Editor } from "@tiptap/core";
2
+ import { useEffect, useRef } from "react";
2
3
  import { isDocumentReferenceName, isDocumentResourceKey } from "./document-schema.ts";
3
4
  import type { DocumentEditorHandle } from "./document-toolbar-blocks.ts";
4
5
 
@@ -23,3 +24,20 @@ export function createEditorHandle(editor: Editor): DocumentEditorHandle {
23
24
  },
24
25
  };
25
26
  }
27
+
28
+ // Reported from the commit, not tiptap's onCreate/onDestroy: tiptap destroys an unmounted editor on
29
+ // a later timer than it announces its replacement, so the old editor's late null would unregister
30
+ // the new handle after a remount. Effect cleanups of the unmounting component run before the new report.
31
+ export function useEditorHandleReport(
32
+ editor: Editor | null,
33
+ onEditorReady: ((handle: DocumentEditorHandle | null) => void) | undefined,
34
+ ): void {
35
+ const reportRef = useRef(onEditorReady);
36
+ reportRef.current = onEditorReady;
37
+ useEffect(() => {
38
+ // skip: useEditor creates the instance in a later effect; the next run reports it
39
+ if (editor === null) return;
40
+ reportRef.current?.(createEditorHandle(editor));
41
+ return () => reportRef.current?.(null);
42
+ }, [editor]);
43
+ }
@@ -8,11 +8,11 @@ import { Extension, Node } from "@tiptap/core";
8
8
  import { Placeholder, UndoRedo } from "@tiptap/extensions";
9
9
  import { EditorContent, useEditor } from "@tiptap/react";
10
10
  import StarterKit from "@tiptap/starter-kit";
11
- import { type ReactNode, useMemo, useState } from "react";
11
+ import { type ReactNode, useMemo, useRef, useState } from "react";
12
12
  import { createVariableView } from "./document-editor-extensions.ts";
13
13
  import { DocumentVariable } from "./document-schema.ts";
14
14
  import type { DocumentEditorHandle } from "./document-toolbar-blocks.ts";
15
- import { createEditorHandle } from "./editor-handle.ts";
15
+ import { useEditorHandleReport } from "./editor-handle.ts";
16
16
  import { contentToLine, lineToContent, singleLine } from "./placeholder-text.ts";
17
17
  import { useStableByKey } from "./use-stable-by-key.ts";
18
18
 
@@ -118,10 +118,9 @@ function LineEditorBody({
118
118
  // Rich pastes would bring several paragraphs; only their text is kept.
119
119
  transformPastedHTML: (html) => singleLine(html.replace(/<[^>]*>/g, " ")),
120
120
  },
121
- onCreate: ({ editor: created }) => onEditorReady?.(createEditorHandle(created)),
122
- onDestroy: () => onEditorReady?.(null),
123
121
  onUpdate: ({ editor: e }) => onChange(contentToLine(e.getJSON().content?.[0]?.content)),
124
122
  });
123
+ useEditorHandleReport(editor, onEditorReady);
125
124
 
126
125
  return (
127
126
  <EditorContent
@@ -165,9 +164,23 @@ export default function PlaceholderLineEditor({
165
164
  setEpoch((current) => current + 1);
166
165
  }
167
166
 
167
+ const bodyKey = `${epoch}-${readOnly}-${JSON.stringify(stableLabels)}-${stableSystem.join("\n")}`;
168
+ const liveBodyKey = useRef(bodyKey);
169
+ liveBodyKey.current = bodyKey;
170
+
171
+ // TipTap destroys a replaced editor in a deferred timer that can fire after the successor's
172
+ // create; its late null must not delete the successor's handle.
173
+ const reportEditorReady =
174
+ onEditorReady === undefined
175
+ ? undefined
176
+ : (handle: DocumentEditorHandle | null): void => {
177
+ if (handle === null && liveBodyKey.current !== bodyKey) return;
178
+ onEditorReady(handle);
179
+ };
180
+
168
181
  return (
169
182
  <LineEditorBody
170
- key={`${epoch}-${readOnly}-${JSON.stringify(stableLabels)}-${stableSystem.join("\n")}`}
183
+ key={bodyKey}
171
184
  id={id}
172
185
  initialValue={value}
173
186
  onChange={(next) => {
@@ -179,7 +192,7 @@ export default function PlaceholderLineEditor({
179
192
  readOnly={readOnly}
180
193
  flat={flat}
181
194
  placeholder={placeholder}
182
- onEditorReady={onEditorReady}
195
+ onEditorReady={reportEditorReady}
183
196
  ariaLabel={ariaLabel}
184
197
  />
185
198
  );